Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a safe API wrapping PyEval_SetProfile #4039

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

LilyFoote
Copy link
Contributor

Fixes #4008.

src/instrumentation.rs Outdated Show resolved Hide resolved
}

pub fn register_profiler<P: Profiler>(profiler: Bound<'_, P>) {
unsafe { ffi::PyEval_SetProfile(Some(profile_callback::<P>), profiler.into_ptr()) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also want to support the new PyEval_SetProfileAllThreads function?

Also, there should be a way to clear tracing. Either with a separate function, or accepting an Option argument.

Finally, why needless change the name from setprofile / SetProfile to register_profiler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think supporting PyEval_SetProfileAllThreads makes sense. But I'll get the main bit working fully first.

The register_profiler naming was also inspired by the implementation in #4008 (comment). I'd prefer to decide on final naming once the implementation is complete. setprofile could well make sense for symmetry.

register_profiler(profiler);

py.run_bound(PYTHON_CODE, None, None).unwrap();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should clear tracing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. This was just where I got to before I had to head to bed.

Copy link

codspeed-hq bot commented Apr 3, 2024

CodSpeed Performance Report

Merging #4039 will not alter performance

Comparing LilyFoote:set-profile (1c5a5ab) with main (d1a0c72)

Summary

✅ 69 untouched benchmarks

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I took so long to take a first look at this! Overall this makes sense and seems a reasonable API to expose. I had a mix of design questions and implementation nits to start the thinking off...

Return(Option<Bound<'py, PyAny>>),
/// A C function is about to be called. The contained data is the
/// function object being called.
CCall(Bound<'py, PyAny>),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be PyCFunction rather than PyAny here? (not sure, just wondering)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, maybe!

//
// `frame` is an `ffi::PyFrameObject` which can be converted safely to a `PyObject`.
let frame = frame as *mut ffi::PyObject;
Python::with_gil(|py| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the profiler callback always called with the GIL held? If so, we can probably do something more like the code in trampoline.rs (we might even want to reuse that trampoline to avoid panic unwinds escaping the callback).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can double check.

// We borrow the object so we don't break reference counting.
//
// https://docs.python.org/3/c-api/init.html#c.Py_tracefunc
let frame = unsafe { PyObject::from_borrowed_ptr(py, frame) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably can assume type is correct and do something like frame.assume_borrowed(py).downcast _unchecked().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd had that idea myself - just hadn't got around to changing it.

}

/// Trait for Rust structs that can be used with Python's profiling API.
pub trait Profiler: PyClass<Frozen = False> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Profiler trait as a subtype of PyClass is an interesting choice. Is that strictly necessary? Is that for the borrow checking? I wonder how this might interact with nogil. I also whether something like what we've got with run_closure over in the function code might work (e.g. just register a Fn() closure rather than a full type).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is necessary, but maybe there's an alternative that avoids it that I missed.

@LilyFoote
Copy link
Contributor Author

Sorry I took so long to take a first look at this!

No worries - it's more blocked on me finding the time to continue it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a safe API wrapping PyEval_SetProfile
3 participants