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: |