FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[Original HTTPS Page]
cpython/Lib/pstats.py at 3.15 · python/cpython · GitHub
python
cpython
Repository navigation
Code
Issues
5k+
(5k+)
Pull requests
2.7k
(2.7k)
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
cpython
/
Lib
/
pstats.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
840 lines (737 loc) · 31.2 KB
Breadcrumbs
cpython
/
Lib
/
pstats.py
Copy path
File metadata and controls
840 lines (737 loc) · 31.2 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
"""Class for printing reports on profiled python code."""
# Written by James Roskind
# Based on prior profile module by Sjoerd Mullender...
# which was hacked somewhat by: Guido van Rossum
# Copyright Disney Enterprises, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
import
sys
import
os
import
time
import
marshal
import
re
from
enum
import
StrEnum
,
_simple_enum
from
functools
import
cmp_to_key
from
dataclasses
import
dataclass
__all__
=
[
"Stats"
,
"SortKey"
,
"FunctionProfile"
,
"StatsProfile"
]
@
_simple_enum
(
StrEnum
)
class
SortKey
:
CALLS
=
'calls'
,
'ncalls'
CUMULATIVE
=
'cumulative'
,
'cumtime'
FILENAME
=
'filename'
,
'module'
LINE
=
'line'
NAME
=
'name'
NFL
=
'nfl'
PCALLS
=
'pcalls'
STDNAME
=
'stdname'
TIME
=
'time'
,
'tottime'
def
__new__
(
cls
,
*
values
):
value
=
values
[
0
]
obj
=
str
.
__new__
(
cls
,
value
)
obj
.
_value_
=
value
for
other_value
in
values
[
1
:]:
cls
.
_value2member_map_
[
other_value
]
=
obj
obj
.
_all_values
=
values
return
obj
@
dataclass
(
unsafe_hash
=
True
)
class
FunctionProfile
:
ncalls
:
str
tottime
:
float
percall_tottime
:
float
cumtime
:
float
percall_cumtime
:
float
file_name
:
str
line_number
:
int
@
dataclass
(
unsafe_hash
=
True
)
class
StatsProfile
:
'''Class for keeping track of an item in inventory.'''
total_tt
:
float
func_profiles
:
dict
[
str
,
FunctionProfile
]
class
Stats
:
"""This class is used for creating reports from data generated by the
Profile class. It is a "friend" of that class, and imports data either
by direct access to members of Profile class, or by reading in a dictionary
that was emitted (via marshal) from the Profile class.
The big change from the previous Profiler (in terms of raw functionality)
is that an "add()" method has been provided to combine Stats from
several distinct profile runs. Both the constructor and the add()
method now take arbitrarily many file names as arguments.
All the print methods now take an argument that indicates how many lines
to print. If the arg is a floating-point number between 0 and 1.0, then
it is taken as a decimal percentage of the available lines to be printed
(e.g., .1 means print 10% of all available lines). If it is an integer,
it is taken to mean the number of lines of data that you wish to have
printed.
The sort_stats() method now processes some additional options (i.e., in
addition to the old -1, 0, 1, or 2 that are respectively interpreted as
'stdname', 'calls', 'time', and 'cumulative'). It takes either an
arbitrary number of quoted strings or SortKey enum to select the sort
order.
For example sort_stats('time', 'name') or sort_stats(SortKey.TIME,
SortKey.NAME) sorts on the major key of 'internal function time', and on
the minor key of 'the name of the function'. Look at the two tables in
sort_stats() and get_sort_arg_defs(self) for more examples.
All methods return self, so you can string together commands like:
Stats('foo', 'goo').strip_dirs().sort_stats('calls').
\
print_stats(5).print_callers(5)
"""
def
__init__
(
self
,
*
args
,
stream
=
None
):
self
.
stream
=
stream
or
sys
.
stdout
if
not
len
(
args
):
arg
=
None
else
:
arg
=
args
[
0
]
args
=
args
[
1
:]
self
.
init
(
arg
)
self
.
add
(
*
args
)
def
init
(
self
,
arg
):
self
.
all_callees
=
None
# calc only if needed
self
.
files
=
[]
self
.
fcn_list
=
None
self
.
total_tt
=
0
self
.
total_calls
=
0
self
.
prim_calls
=
0
self
.
max_name_len
=
0
self
.
top_level
=
set
()
self
.
stats
=
{}
self
.
sort_arg_dict
=
{}
self
.
load_stats
(
arg
)
try
:
self
.
get_top_level_stats
()
except
Exception
:
print
(
"Invalid timing data %s"
%
(
self
.
files
[
-
1
]
if
self
.
files
else
''
),
file
=
self
.
stream
)
raise
def
load_stats
(
self
,
arg
):
if
arg
is
None
:
self
.
stats
=
{}
return
elif
isinstance
(
arg
,
str
):
with
open
(
arg
,
'rb'
)
as
f
:
stats
=
marshal
.
load
(
f
)
if
((
'__sampled__'
,))
in
stats
:
stats
.
pop
(((
'__sampled__'
,)))
self
.
__class__
=
SampledStats
self
.
stats
=
stats
try
:
file_stats
=
os
.
stat
(
arg
)
arg
=
time
.
ctime
(
file_stats
.
st_mtime
)
+
" "
+
arg
except
:
# in case this is not unix
pass
self
.
files
=
[
arg
]
elif
hasattr
(
arg
,
'create_stats'
):
arg
.
create_stats
()
self
.
stats
=
arg
.
stats
arg
.
stats
=
{}
return
if
not
self
.
stats
:
raise
TypeError
(
"Cannot create or construct a %r object from %r"
%
(
self
.
__class__
,
arg
))
return
def
get_top_level_stats
(
self
):
for
func
, (
cc
,
nc
,
tt
,
ct
,
callers
)
in
self
.
stats
.
items
():
self
.
total_calls
+=
nc
self
.
prim_calls
+=
cc
self
.
total_tt
+=
tt
if
(
"jprofile"
,
0
,
"profiler"
)
in
callers
:
self
.
top_level
.
add
(
func
)
if
len
(
func_std_string
(
func
))
>
self
.
max_name_len
:
self
.
max_name_len
=
len
(
func_std_string
(
func
))
def
add
(
self
,
*
arg_list
):
if
not
arg_list
:
return
self
for
item
in
reversed
(
arg_list
):
if
type
(
self
)
!=
type
(
item
):
item
=
Stats
(
item
)
self
.
files
+=
item
.
files
self
.
total_calls
+=
item
.
total_calls
self
.
prim_calls
+=
item
.
prim_calls
self
.
total_tt
+=
item
.
total_tt
for
func
in
item
.
top_level
:
self
.
top_level
.
add
(
func
)
if
self
.
max_name_len
<
item
.
max_name_len
:
self
.
max_name_len
=
item
.
max_name_len
self
.
fcn_list
=
None
for
func
,
stat
in
item
.
stats
.
items
():
if
func
in
self
.
stats
:
old_func_stat
=
self
.
stats
[
func
]
else
:
old_func_stat
=
(
0
,
0
,
0
,
0
, {},)
self
.
stats
[
func
]
=
add_func_stats
(
old_func_stat
,
stat
)
return
self
def
dump_stats
(
self
,
filename
):
"""Write the profile data to a file we know how to load back."""
with
open
(
filename
,
'wb'
)
as
f
:
marshal
.
dump
(
self
.
stats
,
f
)
# list the tuple indices and directions for sorting,
# along with some printable description
sort_arg_dict_default
=
{
"calls"
: (((
1
,
-
1
), ),
"call count"
),
"ncalls"
: (((
1
,
-
1
), ),
"call count"
),
"cumtime"
: (((
3
,
-
1
), ),
"cumulative time"
),
"cumulative"
: (((
3
,
-
1
), ),
"cumulative time"
),
"filename"
: (((
4
,
1
), ),
"file name"
),
"line"
: (((
5
,
1
), ),
"line number"
),
"module"
: (((
4
,
1
), ),
"file name"
),
"name"
: (((
6
,
1
), ),
"function name"
),
"nfl"
: (((
6
,
1
),(
4
,
1
),(
5
,
1
),),
"name/file/line"
),
"pcalls"
: (((
0
,
-
1
), ),
"primitive call count"
),
"stdname"
: (((
7
,
1
), ),
"standard name"
),
"time"
: (((
2
,
-
1
), ),
"internal time"
),
"tottime"
: (((
2
,
-
1
), ),
"internal time"
),
}
def
get_sort_arg_defs
(
self
):
"""Expand all abbreviations that are unique."""
if
not
self
.
sort_arg_dict
:
self
.
sort_arg_dict
=
dict
=
{}
bad_list
=
{}
for
word
,
tup
in
self
.
sort_arg_dict_default
.
items
():
fragment
=
word
while
fragment
:
if
fragment
in
dict
:
bad_list
[
fragment
]
=
0
break
dict
[
fragment
]
=
tup
fragment
=
fragment
[:
-
1
]
for
word
in
bad_list
:
del
dict
[
word
]
return
self
.
sort_arg_dict
def
sort_stats
(
self
,
*
field
):
if
not
field
:
self
.
fcn_list
=
0
return
self
if
len
(
field
)
==
1
and
isinstance
(
field
[
0
],
int
):
# Be compatible with old profiler
field
=
[ {
-
1
:
"stdname"
,
0
:
"calls"
,
1
:
"time"
,
2
:
"cumulative"
}[
field
[
0
]] ]
elif
len
(
field
)
>=
2
:
for
arg
in
field
[
1
:]:
if
type
(
arg
)
!=
type
(
field
[
0
]):
raise
TypeError
(
"Can't have mixed argument type"
)
sort_arg_defs
=
self
.
get_sort_arg_defs
()
sort_tuple
=
()
self
.
sort_type
=
""
connector
=
""
for
word
in
field
:
if
isinstance
(
word
,
SortKey
):
word
=
word
.
value
sort_tuple
=
sort_tuple
+
sort_arg_defs
[
word
][
0
]
self
.
sort_type
+=
connector
+
sort_arg_defs
[
word
][
1
]
connector
=
", "
stats_list
=
[]
for
func
, (
cc
,
nc
,
tt
,
ct
,
callers
)
in
self
.
stats
.
items
():
stats_list
.
append
((
cc
,
nc
,
tt
,
ct
)
+
func
+
(
func_std_string
(
func
),
func
))
stats_list
.
sort
(
key
=
cmp_to_key
(
TupleComp
(
sort_tuple
).
compare
))
self
.
fcn_list
=
fcn_list
=
[]
for
tuple
in
stats_list
:
fcn_list
.
append
(
tuple
[
-
1
])
return
self
def
reverse_order
(
self
):
if
self
.
fcn_list
:
self
.
fcn_list
.
reverse
()
return
self
def
strip_dirs
(
self
):
oldstats
=
self
.
stats
self
.
stats
=
newstats
=
{}
max_name_len
=
0
for
func
, (
cc
,
nc
,
tt
,
ct
,
callers
)
in
oldstats
.
items
():
newfunc
=
func_strip_path
(
func
)
if
len
(
func_std_string
(
newfunc
))
>
max_name_len
:
max_name_len
=
len
(
func_std_string
(
newfunc
))
newcallers
=
{}
for
func2
,
caller
in
callers
.
items
():
newcallers
[
func_strip_path
(
func2
)]
=
caller
if
newfunc
in
newstats
:
newstats
[
newfunc
]
=
add_func_stats
(
newstats
[
newfunc
],
(
cc
,
nc
,
tt
,
ct
,
newcallers
))
else
:
newstats
[
newfunc
]
=
(
cc
,
nc
,
tt
,
ct
,
newcallers
)
old_top
=
self
.
top_level
self
.
top_level
=
new_top
=
set
()
for
func
in
old_top
:
new_top
.
add
(
func_strip_path
(
func
))
self
.
max_name_len
=
max_name_len
self
.
fcn_list
=
None
self
.
all_callees
=
None
return
self
def
calc_callees
(
self
):
if
self
.
all_callees
:
return
self
.
all_callees
=
all_callees
=
{}
for
func
, (
cc
,
nc
,
tt
,
ct
,
callers
)
in
self
.
stats
.
items
():
if
not
func
in
all_callees
:
all_callees
[
func
]
=
{}
for
func2
,
caller
in
callers
.
items
():
if
not
func2
in
all_callees
:
all_callees
[
func2
]
=
{}
all_callees
[
func2
][
func
]
=
caller
return
#******************************************************************
# The following functions support actual printing of reports
#******************************************************************
# Optional "amount" is either a line count, or a percentage of lines.
def
eval_print_amount
(
self
,
sel
,
list
,
msg
):
new_list
=
list
if
isinstance
(
sel
,
str
):
try
:
rex
=
re
.
compile
(
sel
)
except
re
.
PatternError
:
msg
+=
" <Invalid regular expression %r>
\n
"
%
sel
return
new_list
,
msg
new_list
=
[]
for
func
in
list
:
if
rex
.
search
(
func_std_string
(
func
)):
new_list
.
append
(
func
)
else
:
count
=
len
(
list
)
if
isinstance
(
sel
,
float
)
and
0.0
<=
sel
<
1.0
:
count
=
int
(
count
*
sel
+
.5
)
new_list
=
list
[:
count
]
elif
isinstance
(
sel
,
int
)
and
0
<=
sel
<
count
:
count
=
sel
new_list
=
list
[:
count
]
if
len
(
list
)
!=
len
(
new_list
):
msg
+=
" List reduced from %r to %r due to restriction <%r>
\n
"
%
(
len
(
list
),
len
(
new_list
),
sel
)
return
new_list
,
msg
def
get_stats_profile
(
self
):
"""This method returns an instance of StatsProfile, which contains a mapping
of function names to instances of FunctionProfile. Each FunctionProfile
instance holds information related to the function's profile such as how
long the function took to run, how many times it was called, etc...
"""
func_list
=
self
.
fcn_list
[:]
if
self
.
fcn_list
else
list
(
self
.
stats
.
keys
())
if
not
func_list
:
return
StatsProfile
(
0
, {})
total_tt
=
float
(
f8
(
self
.
total_tt
))
func_profiles
=
{}
stats_profile
=
StatsProfile
(
total_tt
,
func_profiles
)
for
func
in
func_list
:
cc
,
nc
,
tt
,
ct
,
callers
=
self
.
stats
[
func
]
file_name
,
line_number
,
func_name
=
func
ncalls
=
str
(
nc
)
if
nc
==
cc
else
(
str
(
nc
)
+
'/'
+
str
(
cc
))
tottime
=
float
(
f8
(
tt
))
percall_tottime
=
-
1
if
nc
==
0
else
float
(
f8
(
tt
/
nc
))
cumtime
=
float
(
f8
(
ct
))
percall_cumtime
=
-
1
if
cc
==
0
else
float
(
f8
(
ct
/
cc
))
func_profile
=
FunctionProfile
(
ncalls
,
tottime
,
# time spent in this function alone
percall_tottime
,
cumtime
,
# time spent in the function plus all functions that this function called,
percall_cumtime
,
file_name
,
line_number
)
func_profiles
[
func_name
]
=
func_profile
return
stats_profile
def
get_print_list
(
self
,
sel_list
):
width
=
self
.
max_name_len
if
self
.
fcn_list
:
stat_list
=
self
.
fcn_list
[:]
msg
=
" Ordered by: "
+
self
.
sort_type
+
'
\n
'
else
:
stat_list
=
list
(
self
.
stats
.
keys
())
msg
=
" Random listing order was used
\n
"
for
selection
in
sel_list
:
stat_list
,
msg
=
self
.
eval_print_amount
(
selection
,
stat_list
,
msg
)
count
=
len
(
stat_list
)
if
not
stat_list
:
return
0
,
stat_list
print
(
msg
,
file
=
self
.
stream
)
if
count
<
len
(
self
.
stats
):
width
=
0
for
func
in
stat_list
:
if
len
(
func_std_string
(
func
))
>
width
:
width
=
len
(
func_std_string
(
func
))
return
width
+
2
,
stat_list
def
print_stats
(
self
,
*
amount
):
for
filename
in
self
.
files
:
print
(
filename
,
file
=
self
.
stream
)
if
self
.
files
:
print
(
file
=
self
.
stream
)
indent
=
' '
*
8
for
func
in
self
.
top_level
:
print
(
indent
,
func_get_function_name
(
func
),
file
=
self
.
stream
)
print
(
indent
,
self
.
total_calls
,
"function calls"
,
end
=
' '
,
file
=
self
.
stream
)
if
self
.
total_calls
!=
self
.
prim_calls
:
print
(
"(%d primitive calls)"
%
self
.
prim_calls
,
end
=
' '
,
file
=
self
.
stream
)
print
(
"in %.3f seconds"
%
self
.
total_tt
,
file
=
self
.
stream
)
print
(
file
=
self
.
stream
)
width
,
list
=
self
.
get_print_list
(
amount
)
if
list
:
self
.
print_title
()
for
func
in
list
:
self
.
print_line
(
func
)
print
(
file
=
self
.
stream
)
print
(
file
=
self
.
stream
)
return
self
def
print_callees
(
self
,
*
amount
):
width
,
list
=
self
.
get_print_list
(
amount
)
if
list
:
self
.
calc_callees
()
self
.
print_call_heading
(
width
,
"called..."
)
for
func
in
list
:
if
func
in
self
.
all_callees
:
self
.
print_call_line
(
width
,
func
,
self
.
all_callees
[
func
])
else
:
self
.
print_call_line
(
width
,
func
, {})
print
(
file
=
self
.
stream
)
print
(
file
=
self
.
stream
)
return
self
def
print_callers
(
self
,
*
amount
):
width
,
list
=
self
.
get_print_list
(
amount
)
if
list
:
self
.
print_call_heading
(
width
,
"was called by..."
)
for
func
in
list
:
cc
,
nc
,
tt
,
ct
,
callers
=
self
.
stats
[
func
]
self
.
print_call_line
(
width
,
func
,
callers
,
"<-"
)
print
(
file
=
self
.
stream
)
print
(
file
=
self
.
stream
)
return
self
def
print_call_heading
(
self
,
name_size
,
column_title
):
print
(
"Function "
.
ljust
(
name_size
)
+
column_title
,
file
=
self
.
stream
)
# print sub-header only if we have new-style callers
subheader
=
False
for
cc
,
nc
,
tt
,
ct
,
callers
in
self
.
stats
.
values
():
if
callers
:
value
=
next
(
iter
(
callers
.
values
()))
subheader
=
isinstance
(
value
,
tuple
)
break
if
subheader
:
self
.
print_call_subheading
(
name_size
)
def
print_call_subheading
(
self
,
name_size
):
print
(
" "
*
name_size
+
" ncalls tottime cumtime"
,
file
=
self
.
stream
)
def
print_call_line
(
self
,
name_size
,
source
,
call_dict
,
arrow
=
"->"
):
print
(
func_std_string
(
source
).
ljust
(
name_size
)
+
arrow
,
end
=
' '
,
file
=
self
.
stream
)
if
not
call_dict
:
print
(
file
=
self
.
stream
)
return
clist
=
sorted
(
call_dict
.
keys
())
indent
=
""
for
func
in
clist
:
name
=
func_std_string
(
func
)
value
=
call_dict
[
func
]
if
isinstance
(
value
,
tuple
):
nc
,
cc
,
tt
,
ct
=
value
if
nc
!=
cc
:
substats
=
'%d/%d'
%
(
nc
,
cc
)
else
:
substats
=
'%d'
%
(
nc
,)
substats
=
'%s %s %s %s'
%
(
substats
.
rjust
(
7
+
2
*
len
(
indent
)),
f8
(
tt
),
f8
(
ct
),
name
)
left_width
=
name_size
+
1
else
:
substats
=
'%s(%r) %s'
%
(
name
,
value
,
f8
(
self
.
stats
[
func
][
3
]))
left_width
=
name_size
+
3
print
(
indent
*
left_width
+
substats
,
file
=
self
.
stream
)
indent
=
" "
def
print_title
(
self
):
print
(
' ncalls tottime percall cumtime percall'
,
end
=
' '
,
file
=
self
.
stream
)
print
(
'filename:lineno(function)'
,
file
=
self
.
stream
)
def
print_line
(
self
,
func
):
# hack: should print percentages
cc
,
nc
,
tt
,
ct
,
callers
=
self
.
stats
[
func
]
c
=
str
(
nc
)
if
nc
!=
cc
:
c
=
c
+
'/'
+
str
(
cc
)
print
(
c
.
rjust
(
9
),
end
=
' '
,
file
=
self
.
stream
)
print
(
f8
(
tt
),
end
=
' '
,
file
=
self
.
stream
)
if
nc
==
0
:
print
(
' '
*
8
,
end
=
' '
,
file
=
self
.
stream
)
else
:
print
(
f8
(
tt
/
nc
),
end
=
' '
,
file
=
self
.
stream
)
print
(
f8
(
ct
),
end
=
' '
,
file
=
self
.
stream
)
if
cc
==
0
:
print
(
' '
*
8
,
end
=
' '
,
file
=
self
.
stream
)
else
:
print
(
f8
(
ct
/
cc
),
end
=
' '
,
file
=
self
.
stream
)
print
(
func_std_string
(
func
),
file
=
self
.
stream
)
class
SampledStats
(
Stats
):
def
__init__
(
self
,
*
args
,
stream
=
None
):
super
().
__init__
(
*
args
,
stream
=
stream
)
self
.
sort_arg_dict
=
{
"samples"
: (((
1
,
-
1
), ),
"sample count"
),
"nsamples"
: (((
1
,
-
1
), ),
"sample count"
),
"cumtime"
: (((
3
,
-
1
), ),
"cumulative time"
),
"cumulative"
: (((
3
,
-
1
), ),
"cumulative time"
),
"filename"
: (((
4
,
1
), ),
"file name"
),
"line"
: (((
5
,
1
), ),
"line number"
),
"module"
: (((
4
,
1
), ),
"file name"
),
"name"
: (((
6
,
1
), ),
"function name"
),
"nfl"
: (((
6
,
1
),(
4
,
1
),(
5
,
1
),),
"name/file/line"
),
"psamples"
: (((
0
,
-
1
), ),
"primitive call count"
),
"stdname"
: (((
7
,
1
), ),
"standard name"
),
"time"
: (((
2
,
-
1
), ),
"internal time"
),
"tottime"
: (((
2
,
-
1
), ),
"internal time"
),
}
def
print_call_subheading
(
self
,
name_size
):
print
(
" "
*
name_size
+
" nsamples tottime cumtime"
,
file
=
self
.
stream
)
def
print_title
(
self
):
print
(
' nsamples tottime persample cumtime persample'
,
end
=
' '
,
file
=
self
.
stream
)
print
(
'filename:lineno(function)'
,
file
=
self
.
stream
)
class
TupleComp
:
"""This class provides a generic function for comparing any two tuples.
Each instance records a list of tuple-indices (from most significant
to least significant), and sort direction (ascending or descending) for
each tuple-index. The compare functions can then be used as the function
argument to the system sort() function when a list of tuples need to be
sorted in the instances order."""
def
__init__
(
self
,
comp_select_list
):
self
.
comp_select_list
=
comp_select_list
def
compare
(
self
,
left
,
right
):
for
index
,
direction
in
self
.
comp_select_list
:
l
=
left
[
index
]
r
=
right
[
index
]
if
l
<
r
:
return
-
direction
if
l
>
r
:
return
direction
return
0
#**************************************************************************
# func_name is a triple (file:string, line:int, name:string)
def
func_strip_path
(
func_name
):
filename
,
line
,
name
=
func_name
return
os
.
path
.
basename
(
filename
),
line
,
name
def
func_get_function_name
(
func
):
return
func
[
2
]
def
func_std_string
(
func_name
):
# match what old profile produced
if
func_name
[:
2
]
==
(
'~'
,
0
):
# special case for built-in functions
name
=
func_name
[
2
]
if
name
.
startswith
(
'<'
)
and
name
.
endswith
(
'>'
):
return
'{%s}'
%
name
[
1
:
-
1
]
else
:
return
name
else
:
return
"%s:%d(%s)"
%
func_name
#**************************************************************************
# The following functions combine statistics for pairs functions.
# The bulk of the processing involves correctly handling "call" lists,
# such as callers and callees.
#**************************************************************************
def
add_func_stats
(
target
,
source
):
"""Add together all the stats for two profile entries."""
cc
,
nc
,
tt
,
ct
,
callers
=
source
t_cc
,
t_nc
,
t_tt
,
t_ct
,
t_callers
=
target
return
(
cc
+
t_cc
,
nc
+
t_nc
,
tt
+
t_tt
,
ct
+
t_ct
,
add_callers
(
t_callers
,
callers
))
def
add_callers
(
target
,
source
):
"""Combine two caller lists in a single list."""
new_callers
=
{}
for
func
,
caller
in
target
.
items
():
new_callers
[
func
]
=
caller
for
func
,
caller
in
source
.
items
():
if
func
in
new_callers
:
if
isinstance
(
caller
,
tuple
):
# format used by cProfile
new_callers
[
func
]
=
tuple
(
i
+
j
for
i
,
j
in
zip
(
caller
,
new_callers
[
func
]))
else
:
# format used by profile
new_callers
[
func
]
+=
caller
else
:
new_callers
[
func
]
=
caller
return
new_callers
def
count_calls
(
callers
):
"""Sum the caller statistics to get total number of calls received."""
nc
=
0
for
calls
in
callers
.
values
():
nc
+=
calls
return
nc
#**************************************************************************
# The following functions support printing of reports
#**************************************************************************
def
f8
(
x
):
return
"%8.3f"
%
x
#**************************************************************************
# Statistics browser added by ESR, April 2001
#**************************************************************************
class
StatsLoaderShim
:
"""Compatibility shim implementing 'create_stats' needed by Stats classes
to handle already unmarshalled data."""
def
__init__
(
self
,
raw_stats
):
self
.
stats
=
raw_stats
def
create_stats
(
self
):
pass
def
stats_factory
(
raw_stats
):
"""Return a Stats or SampledStats instance based on the marker in raw_stats."""
if
((
'__sampled__'
,))
in
raw_stats
:
raw_stats
=
dict
(
raw_stats
)
# avoid mutating caller's dict
raw_stats
.
pop
(((
'__sampled__'
,)))
return
SampledStats
(
StatsLoaderShim
(
raw_stats
))
else
:
return
Stats
(
StatsLoaderShim
(
raw_stats
))
if
__name__
==
'__main__'
:
import
cmd
try
:
import
readline
# noqa: F401
except
ImportError
:
pass
class
ProfileBrowser
(
cmd
.
Cmd
):
def
__init__
(
self
,
profile
=
None
):
cmd
.
Cmd
.
__init__
(
self
)
self
.
prompt
=
"% "
self
.
stats
=
None
self
.
stream
=
sys
.
stdout
if
profile
is
not
None
:
self
.
do_read
(
profile
)
def
generic
(
self
,
fn
,
line
):
args
=
line
.
split
()
processed
=
[]
for
term
in
args
:
try
:
processed
.
append
(
int
(
term
))
continue
except
ValueError
:
pass
try
:
frac
=
float
(
term
)
if
frac
>
1
or
frac
<
0
:
print
(
"Fraction argument must be in [0, 1]"
,
file
=
self
.
stream
)
continue
processed
.
append
(
frac
)
continue
except
ValueError
:
pass
processed
.
append
(
term
)
if
self
.
stats
:
getattr
(
self
.
stats
,
fn
)(
*
processed
)
else
:
print
(
"No statistics object is loaded."
,
file
=
self
.
stream
)
return
0
def
generic_help
(
self
):
print
(
"Arguments may be:"
,
file
=
self
.
stream
)
print
(
"* An integer maximum number of entries to print."
,
file
=
self
.
stream
)
print
(
"* A decimal fractional number between 0 and 1, controlling"
,
file
=
self
.
stream
)
print
(
" what fraction of selected entries to print."
,
file
=
self
.
stream
)
print
(
"* A regular expression; only entries with function names"
,
file
=
self
.
stream
)
print
(
" that match it are printed."
,
file
=
self
.
stream
)
def
do_add
(
self
,
line
):
if
self
.
stats
:
try
:
self
.
stats
.
add
(
line
)
except
OSError
as
e
:
print
(
"Failed to load statistics for %s: %s"
%
(
line
,
e
),
file
=
self
.
stream
)
else
:
print
(
"No statistics object is loaded."
,
file
=
self
.
stream
)
return
0
def
help_add
(
self
):
print
(
"Add profile info from given file to current statistics object."
,
file
=
self
.
stream
)
def
do_callees
(
self
,
line
):
return
self
.
generic
(
'print_callees'
,
line
)
def
help_callees
(
self
):
print
(
"Print callees statistics from the current stat object."
,
file
=
self
.
stream
)
self
.
generic_help
()
def
do_callers
(
self
,
line
):
return
self
.
generic
(
'print_callers'
,
line
)
def
help_callers
(
self
):
print
(
"Print callers statistics from the current stat object."
,
file
=
self
.
stream
)
self
.
generic_help
()
def
do_EOF
(
self
,
line
):
print
(
""
,
file
=
self
.
stream
)
return
1
def
help_EOF
(
self
):
print
(
"Leave the profile browser."
,
file
=
self
.
stream
)
def
do_quit
(
self
,
line
):
return
1
def
help_quit
(
self
):
print
(
"Leave the profile browser."
,
file
=
self
.
stream
)
def
do_read
(
self
,
line
):
if
line
:
try
:
with
open
(
line
,
'rb'
)
as
f
:
raw_stats
=
marshal
.
load
(
f
)
self
.
stats
=
stats_factory
(
raw_stats
)
try
:
file_stats
=
os
.
stat
(
line
)
arg
=
time
.
ctime
(
file_stats
.
st_mtime
)
+
" "
+
line
except
Exception
:
arg
=
line
self
.
stats
.
files
=
[
arg
]
except
OSError
as
err
:
print
(
err
.
args
[
1
],
file
=
self
.
stream
)
return
except
Exception
as
err
:
print
(
err
.
__class__
.
__name__
+
':'
,
err
,
file
=
self
.
stream
)
return
self
.
prompt
=
line
+
"% "
elif
len
(
self
.
prompt
)
>
2
:
line
=
self
.
prompt
[:
-
2
]
self
.
do_read
(
line
)
else
:
print
(
"No statistics object is current -- cannot reload."
,
file
=
self
.
stream
)
return
0
def
help_read
(
self
):
print
(
"Read in profile data from a specified file."
,
file
=
self
.
stream
)
print
(
"Without argument, reload the current file."
,
file
=
self
.
stream
)
def
do_reverse
(
self
,
line
):
if
self
.
stats
:
self
.
stats
.
reverse_order
()
else
:
print
(
"No statistics object is loaded."
,
file
=
self
.
stream
)
return
0
def
help_reverse
(
self
):
print
(
"Reverse the sort order of the profiling report."
,
file
=
self
.
stream
)
def
do_sort
(
self
,
line
):
if
not
self
.
stats
:
print
(
"No statistics object is loaded."
,
file
=
self
.
stream
)
return
abbrevs
=
self
.
stats
.
get_sort_arg_defs
()
if
line
and
all
((
x
in
abbrevs
)
for
x
in
line
.
split
()):
self
.
stats
.
sort_stats
(
*
line
.
split
())
else
:
print
(
"Valid sort keys (unique prefixes are accepted):"
,
file
=
self
.
stream
)
for
(
key
,
value
)
in
Stats
.
sort_arg_dict_default
.
items
():
print
(
"%s -- %s"
%
(
key
,
value
[
1
]),
file
=
self
.
stream
)
return
0
def
help_sort
(
self
):
print
(
"Sort profile data according to specified keys."
,
file
=
self
.
stream
)
print
(
"(Typing `sort' without arguments lists valid keys.)"
,
file
=
self
.
stream
)
def
complete_sort
(
self
,
text
,
*
args
):
return
[
a
for
a
in
Stats
.
sort_arg_dict_default
if
a
.
startswith
(
text
)]
def
do_stats
(
self
,
line
):
return
self
.
generic
(
'print_stats'
,
line
)
def
help_stats
(
self
):
print
(
"Print statistics from the current stat object."
,
file
=
self
.
stream
)
self
.
generic_help
()
def
do_strip
(
self
,
line
):
if
self
.
stats
:
self
.
stats
.
strip_dirs
()
else
:
print
(
"No statistics object is loaded."
,
file
=
self
.
stream
)
def
help_strip
(
self
):
print
(
"Strip leading path information from filenames in the report."
,
file
=
self
.
stream
)
def
help_help
(
self
):
print
(
"Show help for a given command."
,
file
=
self
.
stream
)
def
postcmd
(
self
,
stop
,
line
):
if
stop
:
return
stop
return
None
if
len
(
sys
.
argv
)
>
1
:
initprofile
=
sys
.
argv
[
1
]
else
:
initprofile
=
None
try
:
browser
=
ProfileBrowser
(
initprofile
)
for
profile
in
sys
.
argv
[
2
:]:
browser
.
do_add
(
profile
)
print
(
"Welcome to the profile statistics browser."
,
file
=
browser
.
stream
)
browser
.
cmdloop
()
print
(
"Goodbye."
,
file
=
browser
.
stream
)
except
KeyboardInterrupt
:
pass
# That's all, folks.
Back
|
FazBrowse Home
|
New Git URL