1
1
#!/usr/bin/env python
2
2
"""
3
- Script to conveniently run profilers on code in a variety of circumstances.
3
+ Script to conveniently run profilers on code in a variety of
4
+ circumstances.
4
5
5
- To profile a script, decorate the functions of interest with ``@profile``
6
+ To profile a script, decorate the functions of interest with
7
+ :py:deco:`profile`:
6
8
7
9
.. code:: bash
8
10
@@ -15,25 +17,27 @@ def main():
15
17
16
18
NOTE:
17
19
18
- New in 4.1.0: Instead of relying on injecting ``profile`` into the builtins
19
- you can now ``import line_profiler`` and use ``line_profiler.profile`` to
20
- decorate your functions. This allows the script to remain functional even
21
- if it is not actively profiled. See :py:mod:`line_profiler` for details.
20
+ New in 4.1.0: Instead of relying on injecting :py:deco:`profile`
21
+ into the builtins you can now ``import line_profiler`` and use
22
+ :py:deco:`line_profiler.profile` to decorate your functions. This
23
+ allows the script to remain functional even if it is not actively
24
+ profiled. See :py:mod:`line_profiler` for details.
22
25
23
26
24
- Then run the script using kernprof:
27
+ Then run the script using :command:` kernprof` :
25
28
26
29
.. code:: bash
27
30
28
31
kernprof -b script_to_profile.py
29
32
30
- By default this runs with the default :py:mod:`cProfile` profiler and does not
31
- require compiled modules. Instructions to view the results will be given in the
32
- output. Alternatively, adding `` -v`` to the command line will write results to
33
- stdout.
33
+ By default this runs with the default :py:mod:`cProfile` profiler and
34
+ does not require compiled modules. Instructions to view the results will
35
+ be given in the output. Alternatively, adding :option:` -v` to the
36
+ command line will write results to stdout.
34
37
35
- To enable line-by-line profiling, then :py:mod:`line_profiler` must be
36
- available and compiled. Add the ``-l`` argument to the kernprof invocation.
38
+ To enable line-by-line profiling, :py:mod:`line_profiler` must be
39
+ available and compiled, and the :option:`-l` argument should be added to
40
+ the :command:`kernprof` invocation:
37
41
38
42
.. code:: bash
39
43
@@ -43,17 +47,21 @@ def main():
43
47
44
48
New in 4.3.0: More code execution options are added:
45
49
46
- * ``kernprof <options> -m some.module <args to module>`` parallels
47
- ``python -m`` and runs the provided module as ``__main__``.
48
- * ``kernprof <options> -c "some code" <args to code>`` parallels
49
- ``python -c`` and executes the provided literal code.
50
- * ``kernprof <options> - <args to code>`` parallels ``python -`` and
51
- executes literal code passed via the ``stdin``.
50
+ * :command:`kernprof <options> -m some.module <args to module>`
51
+ parallels :command:`python -m` and runs the provided module as
52
+ ``__main__``.
53
+ * :command:`kernprof <options> -c "some code" <args to code>`
54
+ parallels :command:`python -c` and executes the provided literal
55
+ code.
56
+ * :command:`kernprof <options> - <args to code>` parallels
57
+ :command:`python -` and executes literal code passed via the
58
+ ``stdin``.
52
59
53
- See also :doc:`kernprof invocations </manual/examples/example_kernprof>`.
60
+ See also
61
+ :doc:`kernprof invocations </manual/examples/example_kernprof>`.
54
62
55
63
For more details and options, refer to the CLI help.
56
- To view kernprof help run:
64
+ To view :command:` kernprof` help run:
57
65
58
66
.. code:: bash
59
67
@@ -98,12 +106,13 @@ def main():
98
106
NOTE:
99
107
100
108
New in 4.3.0: For more intuitive profiling behavior, profiling
101
- targets in `` --prof-mod`` (except the profiled script/code) are now
102
- eagerly pre-imported to be profiled
109
+ targets in :option:` --prof-mod` (except the profiled script/code)
110
+ are now eagerly pre-imported to be profiled
103
111
(see :py:mod:`line_profiler.autoprofile.eager_preimports`),
104
112
regardless of whether those imports directly occur in the profiled
105
113
script/module/code.
106
- To restore the old behavior, pass the ``--no-preimports`` flag.
114
+ To restore the old behavior, pass the :option:`--no-preimports`
115
+ flag.
107
116
"""
108
117
import builtins
109
118
import functools
@@ -134,15 +143,15 @@ def main():
134
143
135
144
136
145
def execfile (filename , globals = None , locals = None ):
137
- """ Python 3.x doesn't have ' execfile' builtin """
146
+ """ Python 3.x doesn't have :py:func:` execfile` builtin """
138
147
with open (filename , 'rb' ) as f :
139
148
exec (compile (f .read (), filename , 'exec' ), globals , locals )
140
149
# =====================================
141
150
142
151
143
152
class ContextualProfile (ByCountProfilerMixin , Profile ):
144
- """ A subclass of Profile that adds a context manager for Python
145
- 2.5 with: statements and a decorator.
153
+ """ A subclass of :py:class:` Profile` that adds a context manager
154
+ for Python 2.5 with: statements and a decorator.
146
155
"""
147
156
def __init__ (self , * args , ** kwds ):
148
157
super (ByCountProfilerMixin , self ).__init__ (* args , ** kwds )
@@ -174,7 +183,7 @@ def disable_by_count(self):
174
183
175
184
class RepeatedTimer :
176
185
"""
177
- Background timer for outputting file every n seconds.
186
+ Background timer for outputting file every ``n`` seconds.
178
187
179
188
Adapted from [SO474528]_.
180
189
@@ -223,7 +232,7 @@ def find_module_script(module_name):
223
232
def find_script (script_name , exit_on_error = True ):
224
233
""" Find the script.
225
234
226
- If the input is not a file, then $ PATH will be searched.
235
+ If the input is not a file, then :envvar:` PATH` will be searched.
227
236
"""
228
237
if os .path .isfile (script_name ):
229
238
return script_name
@@ -245,7 +254,7 @@ def find_script(script_name, exit_on_error=True):
245
254
246
255
def _python_command ():
247
256
"""
248
- Return a command that corresponds to :py:obj :`sys.executable`.
257
+ Return a command that corresponds to :py:data :`sys.executable`.
249
258
"""
250
259
import shutil
251
260
for abbr in 'python' , 'python3' :
@@ -256,7 +265,7 @@ def _python_command():
256
265
257
266
def _normalize_profiling_targets (targets ):
258
267
"""
259
- Normalize the parsed `` --prof-mod` ` by:
268
+ Normalize the parsed :option:` --prof-mod` by:
260
269
261
270
* Normalizing file paths with :py:func:`find_script()`, and
262
271
subsequently to absolute paths.
@@ -285,7 +294,7 @@ def find(path):
285
294
286
295
class _restore_list :
287
296
"""
288
- Restore a list like `` sys.path` ` after running code which
297
+ Restore a list like :py:data:` sys.path` after running code which
289
298
potentially modifies it.
290
299
291
300
Example
@@ -329,7 +338,8 @@ def wrapper(*args, **kwargs):
329
338
def pre_parse_single_arg_directive (args , flag , sep = '--' ):
330
339
"""
331
340
Pre-parse high-priority single-argument directives like
332
- ``-m module`` to emulate the behavior of ``python [...]``.
341
+ :option:`-m module` to emulate the behavior of
342
+ :command:`python [...]`.
333
343
334
344
Examples
335
345
--------
@@ -521,7 +531,8 @@ def positive_float(value):
521
531
522
532
def _write_tempfile (source , content , options , tmpdir ):
523
533
"""
524
- Called by ``main()`` to handle ``kernprof -c`` and ``kernprof -``;
534
+ Called by :py:func:`main()` to handle :command:`kernprof -c` and
535
+ :command:`kernprof -`;
525
536
not to be invoked on its own.
526
537
"""
527
538
import textwrap
@@ -548,9 +559,81 @@ def _write_tempfile(source, content, options, tmpdir):
548
559
suffix = '.' + extension )
549
560
550
561
562
+ def _write_preimports (prof , prof_mod , exclude ):
563
+ """
564
+ Called by :py:func:`main()` to handle eager pre-imports;
565
+ not to be invoked on its own.
566
+ """
567
+ from line_profiler .autoprofile .eager_preimports import (
568
+ is_dotted_path , propose_names , write_eager_import_module )
569
+ from line_profiler .autoprofile .util_static import modpath_to_modname
570
+ from line_profiler .autoprofile .autoprofile import (
571
+ _extend_line_profiler_for_profiling_imports as upgrade_profiler )
572
+
573
+ filtered_targets = []
574
+ invalid_targets = []
575
+ for target in prof_mod :
576
+ if is_dotted_path (target ):
577
+ filtered_targets .append (target )
578
+ continue
579
+ # Paths already normalized by `_normalize_profiling_targets()`
580
+ if not os .path .exists (target ):
581
+ invalid_targets .append (target )
582
+ continue
583
+ if any (os .path .samefile (target , excluded ) for excluded in exclude ):
584
+ # Ignore the script to be run in eager importing
585
+ # (`line_profiler.autoprofile.autoprofile.run()` will handle
586
+ # it)
587
+ continue
588
+ modname = modpath_to_modname (target )
589
+ if modname is None : # Not import-able
590
+ invalid_targets .append (target )
591
+ continue
592
+ filtered_targets .append (modname )
593
+ if invalid_targets :
594
+ invalid_targets = sorted (set (invalid_targets ))
595
+ msg = ('{} profile-on-import target{} cannot be converted to '
596
+ 'dotted-path form: {!r}'
597
+ .format (len (invalid_targets ),
598
+ '' if len (invalid_targets ) == 1 else 's' ,
599
+ invalid_targets ))
600
+ warnings .warn (msg )
601
+ if not filtered_targets :
602
+ return
603
+ # - We could've done everything in-memory with `io.StringIO` and
604
+ # `exec()`, but that results in indecipherable tracebacks should
605
+ # anything goes wrong;
606
+ # so we write to a tempfile and `execfile()` it
607
+ # - While this works theoretically for preserving traceback, the
608
+ # catch is that the tempfile will already have been deleted by the
609
+ # time the traceback is formatted;
610
+ # so we have to format the traceback and manually print the
611
+ # context before re-raising the error
612
+ upgrade_profiler (prof )
613
+ temp_mod_name = next (
614
+ name for name in propose_names (['_kernprof_eager_preimports' ])
615
+ if name not in sys .modules )
616
+ with tempfile .TemporaryDirectory () as tmpdir :
617
+ temp_mod_path = os .path .join (tmpdir , temp_mod_name + '.py' )
618
+ with open (temp_mod_path , mode = 'w' ) as fobj :
619
+ write_eager_import_module (filtered_targets , stream = fobj )
620
+ ns = {} # Use a fresh namespace
621
+ try :
622
+ execfile (temp_mod_path , ns , ns )
623
+ except Exception as e :
624
+ tb_lines = traceback .format_tb (e .__traceback__ )
625
+ i_last_temp_frame = max (
626
+ i for i , line in enumerate (tb_lines )
627
+ if temp_mod_path in line )
628
+ print ('\n Context:' , '' .join (tb_lines [i_last_temp_frame :]),
629
+ end = '' , sep = '\n ' , file = sys .stderr )
630
+ raise
631
+
632
+
551
633
def _main (options , module = False ):
552
634
"""
553
- Called by ``main()`` for the actual execution and profiling of code;
635
+ Called by :py:func:`main()` for the actual execution and profiling
636
+ of code;
554
637
not to be invoked on its own.
555
638
"""
556
639
if not options .outfile :
@@ -626,70 +709,8 @@ def _main(options, module=False):
626
709
# contains the script file to be run. E.g. the script may not
627
710
# even have a `if __name__ == '__main__': ...` guard. So don't
628
711
# eager-import it.
629
- from line_profiler .autoprofile .eager_preimports import (
630
- is_dotted_path , propose_names , write_eager_import_module )
631
- from line_profiler .autoprofile .util_static import modpath_to_modname
632
- from line_profiler .autoprofile .autoprofile import (
633
- _extend_line_profiler_for_profiling_imports as upgrade_profiler )
634
-
635
- filtered_targets = []
636
- invalid_targets = []
637
- for target in options .prof_mod :
638
- if is_dotted_path (target ):
639
- filtered_targets .append (target )
640
- continue
641
- # Filenames are already normalized in
642
- # `_normalize_profiling_targets()`
643
- if not os .path .exists (target ):
644
- invalid_targets .append (target )
645
- continue
646
- if not module and os .path .samefile (target , script_file ):
647
- # Ignore the script to be run in eager importing
648
- # (`line_profiler.autoprofile.autoprofile.run()` will
649
- # handle it)
650
- continue
651
- modname = modpath_to_modname (target )
652
- if modname is None : # Not import-able
653
- invalid_targets .append (target )
654
- continue
655
- filtered_targets .append (modname )
656
- if invalid_targets :
657
- invalid_targets = sorted (set (invalid_targets ))
658
- msg = ('{} profile-on-import target{} cannot be converted to '
659
- 'dotted-path form: {!r}'
660
- .format (len (invalid_targets ),
661
- '' if len (invalid_targets ) == 1 else 's' ,
662
- invalid_targets ))
663
- warnings .warn (msg )
664
- if filtered_targets :
665
- # - We could've done everything in-memory with `io.StringIO`
666
- # and `exec()`, but that results in indecipherable
667
- # tracebacks should anything goes wrong;
668
- # so we write to a tempfile and `execfile()` it
669
- # - While this works theoretically for preserving traceback,
670
- # the catch is that the tempfile will already have been
671
- # deleted by the time the traceback is formatted;
672
- # so we have to format the traceback and manually print
673
- # the context before re-raising the error
674
- upgrade_profiler (prof )
675
- temp_mod_name = next (
676
- name for name in propose_names (['_kernprof_eager_preimports' ])
677
- if name not in sys .modules )
678
- with tempfile .TemporaryDirectory () as tmpdir :
679
- temp_mod_path = os .path .join (tmpdir , temp_mod_name + '.py' )
680
- with open (temp_mod_path , mode = 'w' ) as fobj :
681
- write_eager_import_module (filtered_targets , stream = fobj )
682
- ns = {} # Use a fresh namespace
683
- try :
684
- execfile (temp_mod_path , ns , ns )
685
- except Exception as e :
686
- tb_lines = traceback .format_tb (e .__traceback__ )
687
- i_last_temp_frame = max (
688
- i for i , line in enumerate (tb_lines )
689
- if temp_mod_path in line )
690
- print ('\n Context:' , '' .join (tb_lines [i_last_temp_frame :]),
691
- end = '' , sep = '\n ' , file = sys .stderr )
692
- raise
712
+ exclude = set () if module else {script_file }
713
+ _write_preimports (prof , options .prof_mod , exclude )
693
714
694
715
if options .output_interval :
695
716
rt = RepeatedTimer (max (options .output_interval , 1 ), prof .dump_stats , options .outfile )
0 commit comments