mercurial/statprof.py
changeset 30929 cb440e7af05d
parent 30928 be3a4fde38eb
child 31074 2912b06905dc
equal deleted inserted replaced
30928:be3a4fde38eb 30929: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: