-
Notifications
You must be signed in to change notification settings - Fork 240
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
Class names in identifiers in rendered output for aggregated frames corresponding to method calls can be misleading #340
Comments
Ooh yes this is an excellent point. Yes I totally agree that the current approach could be improved. The current method looks for a parameter in the function called This is implemented in C, but you can see a Python version here, which is roughly similar: pyinstrument/pyinstrument/low_level/stat_profile_python.py Lines 123 to 132 in 17ce4c4
One limitation is that it does need to be fast, because it will be called 1000s of times per second, potentially. Yes if you have an opportunity to look into other approaches, I'd be very interested to see what you can find out! |
For Python 3.11 onwards, I think the code = frame.f_code
class_name = None
if hasattr(code, "co_qualname") and code.co_qualname != code.co_name:
class_name = class_name_from_qualified_name(code.co_qualname)
else:
self_or_cls = frame.f_locals.get("self", frame.f_locals.get("cls", None))
if self_or_cls and hasattr(self_or_cls, code.co_name):
method = getattr(self_or_cls, code.co_name)
if hasattr(method, "__qualname__"):
class_name = class_name_from_qualified_name(method.__qualname__) Here def class_name_from_qualified_name(qualified_name: str) -> str:
return ".".join(qualified_name.split(".")[:-1]) which should deal with also giving the 'qualified' class name for nested class definitions. It looks like and as pyinstrument/pyinstrument/low_level/stat_profile.c Lines 278 to 280 in 17ce4c4
I think we could implement something analogous in C, with Python 3.11+ implementation using |
We hit an issue when using
pyinstrument
to regularly profile a large epidemiological simulation code that the rendered profile output seemed to be grouping method calls incorrectly:UCL/TLOmodel-profiling/issues/19
Specifically we were finding calls to functions (/ methods) being grouped under class methods which we thought shouldn't ever be able to call these functions.
I've looked in to this a bit and think I've identified what is going on. The key issue seems to be that the class name used in the identifier for a (aggregated) frame corresponding to a method call will be written out as the name of particular subclass (specifically the mostly commonly observed subclass in the aggregated captured frames) even if the method being called is defined on a base class.
As a minimal reproducer, consider the following script corresponding to a very simplified version of our simulation call stack:
Running
pyinstrument
on this script (saved aspyinstrument_test.py
) gives the following output using the console renderer:Here we see that underneath the top-level
simulate
call, all eventapply
method calls are grouped under a frame with identifierEventA.run
. This corresponds in reality to therun
method defined on the baseEvent
class, which we can see from the correct line number reference in to the script. In this case it is fairly easy to spot that there is norun
method defined directly inEventA
and so that this in reality corresponds toEvent
, but in our real use case the call stack is a lot more complex and spotting when method calls actually correspond to calls to a base class method is more difficult.My understanding of how this arises, is that as the concrete classes from which the
run
method is called areEventA
andEventB
, when the corresponding frames are subsequently aggregatedpyinstrument
resolves the ambiguity of which class name to use when rendering by outputting the most commonly observed class name based on the logic in:pyinstrument/pyinstrument/frame.py
Lines 308 to 309 in 4b37f8c
pyinstrument/pyinstrument/frame.py
Lines 284 to 305 in 4b37f8c
Ideally instead of the above output, for our purposes it would be useful to instead get
that is the class name qualifying the
run
method identifier being specified as the base classEvent
it is defined in.I am not clear however how feasible it would be to output the class name where methods are defined like this, and whether the current behaviour is intentional?
@joerick if you do think it would feasible and desirable to change the behaviour of
pyinstrument
to instead produce something more like the latter output, I'd be happy to try to work on a PR to try to implement this!The text was updated successfully, but these errors were encountered: