Show More
@@ -433,6 +433,7 b' class DisplayFormats:' | |||
|
433 | 433 | Hotpath = 3 |
|
434 | 434 | FlameGraph = 4 |
|
435 | 435 | Json = 5 |
|
436 | Chrome = 6 | |
|
436 | 437 | |
|
437 | 438 | def display(fp=None, format=3, data=None, **kwargs): |
|
438 | 439 | '''Print statistics, either to stdout or the given file object.''' |
@@ -457,10 +458,12 b' def display(fp=None, format=3, data=None' | |||
|
457 | 458 | write_to_flame(data, fp, **kwargs) |
|
458 | 459 | elif format == DisplayFormats.Json: |
|
459 | 460 | write_to_json(data, fp) |
|
461 | elif format == DisplayFormats.Chrome: | |
|
462 | write_to_chrome(data, fp, **kwargs) | |
|
460 | 463 | else: |
|
461 | 464 | raise Exception("Invalid display format") |
|
462 | 465 | |
|
463 |
if format |
|
|
466 | if format not in (DisplayFormats.Json, DisplayFormats.Chrome): | |
|
464 | 467 | print('---', file=fp) |
|
465 | 468 | print('Sample count: %d' % len(data.samples), file=fp) |
|
466 | 469 | print('Total time: %f seconds' % data.accumulated_time, file=fp) |
@@ -743,6 +746,102 b' def write_to_json(data, fp):' | |||
|
743 | 746 | |
|
744 | 747 | print(json.dumps(samples), file=fp) |
|
745 | 748 | |
|
749 | def write_to_chrome(data, fp, minthreshold=0.005, maxthreshold=0.999): | |
|
750 | samples = [] | |
|
751 | laststack = collections.deque() | |
|
752 | lastseen = collections.deque() | |
|
753 | ||
|
754 | # The Chrome tracing format allows us to use a compact stack | |
|
755 | # representation to save space. It's fiddly but worth it. | |
|
756 | # We maintain a bijection between stack and ID. | |
|
757 | stack2id = {} | |
|
758 | id2stack = [] # will eventually be rendered | |
|
759 | ||
|
760 | def stackid(stack): | |
|
761 | if not stack: | |
|
762 | return | |
|
763 | if stack in stack2id: | |
|
764 | return stack2id[stack] | |
|
765 | parent = stackid(stack[1:]) | |
|
766 | myid = len(stack2id) | |
|
767 | stack2id[stack] = myid | |
|
768 | id2stack.append(dict(category=stack[0][0], name='%s %s' % stack[0])) | |
|
769 | if parent is not None: | |
|
770 | id2stack[-1].update(parent=parent) | |
|
771 | return myid | |
|
772 | ||
|
773 | def endswith(a, b): | |
|
774 | return list(a)[-len(b):] == list(b) | |
|
775 | ||
|
776 | # The sampling profiler can sample multiple times without | |
|
777 | # advancing the clock, potentially causing the Chrome trace viewer | |
|
778 | # to render single-pixel columns that we cannot zoom in on. We | |
|
779 | # work around this by pretending that zero-duration samples are a | |
|
780 | # millisecond in length. | |
|
781 | ||
|
782 | clamp = 0.001 | |
|
783 | ||
|
784 | # We provide knobs that by default attempt to filter out stack | |
|
785 | # frames that are too noisy: | |
|
786 | # | |
|
787 | # * A few take almost all execution time. These are usually boring | |
|
788 | # setup functions, giving a stack that is deep but uninformative. | |
|
789 | # | |
|
790 | # * Numerous samples take almost no time, but introduce lots of | |
|
791 | # noisy, oft-deep "spines" into a rendered profile. | |
|
792 | ||
|
793 | blacklist = set() | |
|
794 | totaltime = data.samples[-1].time - data.samples[0].time | |
|
795 | minthreshold = totaltime * minthreshold | |
|
796 | maxthreshold = max(totaltime * maxthreshold, clamp) | |
|
797 | ||
|
798 | def poplast(): | |
|
799 | oldsid = stackid(tuple(laststack)) | |
|
800 | oldcat, oldfunc = laststack.popleft() | |
|
801 | oldtime, oldidx = lastseen.popleft() | |
|
802 | duration = sample.time - oldtime | |
|
803 | if minthreshold <= duration <= maxthreshold: | |
|
804 | # ensure no zero-duration events | |
|
805 | sampletime = max(oldtime + clamp, sample.time) | |
|
806 | samples.append(dict(ph='E', name=oldfunc, cat=oldcat, sf=oldsid, | |
|
807 | ts=sampletime*1e6, pid=0)) | |
|
808 | else: | |
|
809 | blacklist.add(oldidx) | |
|
810 | ||
|
811 | # Much fiddling to synthesize correctly(ish) nested begin/end | |
|
812 | # events given only stack snapshots. | |
|
813 | ||
|
814 | for sample in data.samples: | |
|
815 | tos = sample.stack[0] | |
|
816 | name = tos.function | |
|
817 | path = simplifypath(tos.path) | |
|
818 | category = '%s:%d' % (path, tos.lineno) | |
|
819 | stack = tuple((('%s:%d' % (simplifypath(frame.path), frame.lineno), | |
|
820 | frame.function) for frame in sample.stack)) | |
|
821 | qstack = collections.deque(stack) | |
|
822 | if laststack == qstack: | |
|
823 | continue | |
|
824 | while laststack and qstack and laststack[-1] == qstack[-1]: | |
|
825 | laststack.pop() | |
|
826 | qstack.pop() | |
|
827 | while laststack: | |
|
828 | poplast() | |
|
829 | for f in reversed(qstack): | |
|
830 | lastseen.appendleft((sample.time, len(samples))) | |
|
831 | laststack.appendleft(f) | |
|
832 | path, name = f | |
|
833 | sid = stackid(tuple(laststack)) | |
|
834 | samples.append(dict(ph='B', name=name, cat=path, ts=sample.time*1e6, | |
|
835 | sf=sid, pid=0)) | |
|
836 | laststack = collections.deque(stack) | |
|
837 | while laststack: | |
|
838 | poplast() | |
|
839 | events = [s[1] for s in enumerate(samples) if s[0] not in blacklist] | |
|
840 | frames = collections.OrderedDict((str(k), v) | |
|
841 | for (k,v) in enumerate(id2stack)) | |
|
842 | json.dump(dict(traceEvents=events, stackFrames=frames), fp, indent=1) | |
|
843 | fp.write('\n') | |
|
844 | ||
|
746 | 845 | def printusage(): |
|
747 | 846 | print(""" |
|
748 | 847 | The statprof command line allows you to inspect the last profile's results in |
General Comments 0
You need to be logged in to leave comments.
Login now