Mercurial > hg-stable
comparison mercurial/statprof.py @ 30949:cb440e7af05d
statprof: allow rendering in the Chrome trace viewer format
author | Bryan O'Sullivan <bryano@fb.com> |
---|---|
date | Sun, 12 Feb 2017 22:20:20 -0800 |
parents | be3a4fde38eb |
children | 2912b06905dc |
comparison
equal
deleted
inserted
replaced
30948:be3a4fde38eb | 30949:cb440e7af05d |
---|---|
431 ByMethod = 1 | 431 ByMethod = 1 |
432 AboutMethod = 2 | 432 AboutMethod = 2 |
433 Hotpath = 3 | 433 Hotpath = 3 |
434 FlameGraph = 4 | 434 FlameGraph = 4 |
435 Json = 5 | 435 Json = 5 |
436 Chrome = 6 | |
436 | 437 |
437 def display(fp=None, format=3, data=None, **kwargs): | 438 def display(fp=None, format=3, data=None, **kwargs): |
438 '''Print statistics, either to stdout or the given file object.''' | 439 '''Print statistics, either to stdout or the given file object.''' |
439 data = data or state | 440 data = data or state |
440 | 441 |
455 display_hotpath(data, fp, **kwargs) | 456 display_hotpath(data, fp, **kwargs) |
456 elif format == DisplayFormats.FlameGraph: | 457 elif format == DisplayFormats.FlameGraph: |
457 write_to_flame(data, fp, **kwargs) | 458 write_to_flame(data, fp, **kwargs) |
458 elif format == DisplayFormats.Json: | 459 elif format == DisplayFormats.Json: |
459 write_to_json(data, fp) | 460 write_to_json(data, fp) |
461 elif format == DisplayFormats.Chrome: | |
462 write_to_chrome(data, fp, **kwargs) | |
460 else: | 463 else: |
461 raise Exception("Invalid display format") | 464 raise Exception("Invalid display format") |
462 | 465 |
463 if format != DisplayFormats.Json: | 466 if format not in (DisplayFormats.Json, DisplayFormats.Chrome): |
464 print('---', file=fp) | 467 print('---', file=fp) |
465 print('Sample count: %d' % len(data.samples), file=fp) | 468 print('Sample count: %d' % len(data.samples), file=fp) |
466 print('Total time: %f seconds' % data.accumulated_time, file=fp) | 469 print('Total time: %f seconds' % data.accumulated_time, file=fp) |
467 | 470 |
468 def display_by_line(data, fp): | 471 def display_by_line(data, fp): |
740 stack.append((frame.path, frame.lineno, frame.function)) | 743 stack.append((frame.path, frame.lineno, frame.function)) |
741 | 744 |
742 samples.append((sample.time, stack)) | 745 samples.append((sample.time, stack)) |
743 | 746 |
744 print(json.dumps(samples), file=fp) | 747 print(json.dumps(samples), file=fp) |
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') | |
745 | 844 |
746 def printusage(): | 845 def printusage(): |
747 print(""" | 846 print(""" |
748 The statprof command line allows you to inspect the last profile's results in | 847 The statprof command line allows you to inspect the last profile's results in |
749 the following forms: | 848 the following forms: |