Replies: 6 comments 22 replies
-
I think something like this is a big enough improvement that it justifies inclusion in line-profiler. But - as I've likely mentioned - because I'm just the maintainer and not the designer, and this is a widely used package, I want to be very careful when it comes to adding new features. I like the idea of passing a list of modules / classes / functions to be profiled, although I think it could get verbose quickly. I recommend having the ability to specify a root package, and recursively include it's members. I've written code to perform a similar operation in xdoctest (to collect modules to be tested), perhaps you can get some reuse out of it: https://github.com/Erotemic/xdoctest/blob/main/xdoctest/static_analysis.py#L77 I think a robust solution to this is going to require it's own submodule from which kernprof can import, and then make use of. I recommend developing this as a standalone application, and once that proof of concept is complete, we can work on integrating it into line-profiler / kernprof. |
Beta Was this translation helpful? Give feedback.
-
yea I understand. tbh my main motivation of adding this functionality is so that I can install line_profiler from pip and its up to date instead of having to maintain a fork with the changes, and ofcourse other people can benefit from it From what i can tell, the profiler can only add what it can see in the script file. It can add a module, but to recursively add its modules that would require modifying line_profiler's add_module function to include modules aswell, which shouldnt be too hard I'll can try my hand at turning this into a submodule when i get the chance |
Beta Was this translation helpful? Give feedback.
-
@Erotemic revisting this again if you're still interested 😁 just added the option to automatically profile all imported modules, previously you would have to add them manually. It's not recursive to avoid blowing up the results and possibly slowing down execution I couldn't figure out how to make it run as a standalone application as it requires kernprof's global namespace. you can check it out here if you'd like main...ta946:line_profiler:autoprofile can look into including/excluding by name and the root package recursion if you think this looks promising |
Beta Was this translation helpful? Give feedback.
-
@Erotemic got the name mapping sorted as well as the ast profiling. the only thing im stuck on is that line profiler will only start profiling an import if it was used inside a function that is being profiled. ive put together all the poc code. its all in one place so its alot to go through to understand but shouldn't be too hard to get line_profiler to profile the script, uncomment line 268 main.py
|
Beta Was this translation helpful? Give feedback.
-
Thanks for working on this! As an aside I thought I'd mention that I have a hacky local tool that adds a similar feature of profiling CLI-specified functions without needing decorators, but doesn't work unless the called module has a Here's the code for my quick-and-dirty tool, for posterity, though I think it's strictly worse than the native implementation you're working on: """Profile the given unmodified python module (normally line_profiler requires adding @profile decorators).
The module must expose a main() function, which will be executed.
"""
import argparse
import importlib
import inspect
import sys
import line_profiler
if __name__ == '__main__':
parser = argparse.ArgumentParser(
usage="%(prog)s [-h] functions module [your module's args]",
description="Profile the given module's function(s) line-by-line.\n"
"Unlike pypi's line-profiler, does not require adding decorators.",
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('functions',
help="Function(s) to be profiled from the given module, comma-separated.\n"
"Specify each relative to the module.\n"
"E.g.: `my_public_function,MyPublicClass.my_method,my_submodule.MyInternalClass.my_other_method`.")
parser.add_argument('module',
help="Py module to profile. module.main() will be executed.\n"
"If it is a package and doesn't expose a `main`, but its __main__.py has one, that will be used instead.")
# Assume any unknown arguments are intended for the module being profiled
args, unused_args = parser.parse_known_args()
sys.argv = sys.argv[:1] + unused_args
# Import the module, sanitizing if it was passed as a file name
module = importlib.import_module(args.module.strip('.py'))
# Set up the line profiler and add the specified module functions to it
profiler = line_profiler.LineProfiler()
for fn in args.functions.split(','):
# Handle arbitrary length 'a.b.c' specification, e.g. 'some_submodule.SomeClass.some_method' from module
obj = module
for attr in fn.split('.'):
try:
obj = getattr(obj, attr)
except AttributeError as e:
raise AttributeError(f"Couldn't find {fn}, maybe you need <submodule>.{fn} based on its file?") from e
profiler.add_function(obj)
# If `main` is missing, do a last-ditch check if the module is a package with __main__.py and check it instead
if not hasattr(module, 'main'):
try:
main_module = importlib.import_module(args.module.strip('.py') + '.__main__')
if hasattr(main_module, 'main'):
module = main_module
except ModuleNotFoundError:
sys.tracebacklimit = 0 # Hide traceback to prett-ify what will be the most common error
raise AttributeError(f"Module `{args.module}` has no function `main`. Please move your __name__ == '__main__' block into one.") from None # Hide the failed __main__.py check via `from None`
# Run with asyncio if main() is async
if inspect.iscoroutinefunction(module.main):
import asyncio
profiler.run('asyncio.run(module.main())')
else:
profiler.run('module.main()')
profiler.print_stats() Sample usage: |
Beta Was this translation helpful? Give feedback.
-
@Erotemic i've solved the issue i was referring to before, where I've created a new branch in my fork if you want to take a look at the code Theres a few things I wanted to bring up.
|
Beta Was this translation helpful? Give feedback.
-
@Erotemic after having to write a module scraper, i got abit more understanding of ast and tried it out like you suggested.
i finally found a solution to automatically profile a script without magically injecting decorators in the script file!
Its so simple I actually feel bad after spending all that time fixing that hack i made
in kernprof.py replace line 220
with
so it parses the script file into an ast. For every function, it adds
profile
as a decorator, which line_profiler adds to the global space.inserting
profile
at the beginning ofdecorator_list
makes it work for methodsHaven't properly tested all edge cases, but seems to work for functions, methods, decorated functions and methods.
This is just a v0.1 that i would add more options
-a
to choose automatic profiling.@profile
was already used in the file, if so then skip adding the decorator to the whole file. use case being you only want to profile only 1 function without having to change the cmd.i already have solutions for most of these, but thought id get this past you first before spending time on those
Beta Was this translation helpful? Give feedback.
All reactions