mercurial/statprof.py
changeset 43076 2372284d9457
parent 43053 9a3be115fb78
child 43077 687b865b95ad
--- a/mercurial/statprof.py	Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/statprof.py	Sun Oct 06 09:45:02 2019 -0400
@@ -144,6 +144,7 @@
 ###########################################################################
 ## Utils
 
+
 def clock():
     times = os.times()
     return (times[0] + times[1], times[4])
@@ -152,6 +153,7 @@
 ###########################################################################
 ## Collection data structures
 
+
 class ProfileState(object):
     def __init__(self, frequency=None):
         self.reset(frequency)
@@ -196,6 +198,7 @@
             return 1
         return 0
 
+
 state = ProfileState()
 
 
@@ -214,8 +217,7 @@
 
     def __eq__(self, other):
         try:
-            return (self.lineno == other.lineno and
-                    self.path == other.path)
+            return self.lineno == other.lineno and self.path == other.path
         except:
             return False
 
@@ -248,7 +250,7 @@
 
         source = self.source
         if len(source) > length:
-            source = source[:(length - 3)] + "..."
+            source = source[: (length - 3)] + "..."
         return source
 
     def filename(self):
@@ -257,6 +259,7 @@
     def skipname(self):
         return r'%s:%s' % (self.filename(), self.function)
 
+
 class Sample(object):
     __slots__ = (r'stack', r'time')
 
@@ -269,17 +272,22 @@
         stack = []
 
         while frame:
-            stack.append(CodeSite.get(
-                pycompat.sysbytes(frame.f_code.co_filename),
-                frame.f_lineno,
-                pycompat.sysbytes(frame.f_code.co_name)))
+            stack.append(
+                CodeSite.get(
+                    pycompat.sysbytes(frame.f_code.co_filename),
+                    frame.f_lineno,
+                    pycompat.sysbytes(frame.f_code.co_name),
+                )
+            )
             frame = frame.f_back
 
         return Sample(stack, time)
 
+
 ###########################################################################
 ## SIGPROF handler
 
+
 def profile_signal_handler(signum, frame):
     if state.profile_level > 0:
         now = clock()
@@ -288,11 +296,13 @@
         timestamp = state.accumulated_time[state.timeidx]
         state.samples.append(Sample.from_frame(frame, timestamp))
 
-        signal.setitimer(signal.ITIMER_PROF,
-            state.sample_interval, 0.0)
+        signal.setitimer(signal.ITIMER_PROF, state.sample_interval, 0.0)
         state.last_start_time = now
 
+
 stopthread = threading.Event()
+
+
 def samplerthread(tid):
     while not stopthread.is_set():
         now = clock()
@@ -308,16 +318,21 @@
 
     stopthread.clear()
 
+
 ###########################################################################
 ## Profiling API
 
+
 def is_active():
     return state.profile_level > 0
 
+
 lastmechanism = None
+
+
 def start(mechanism='thread', track='cpu'):
     '''Install the profiling signal handler, and start profiling.'''
-    state.track = track # note: nesting different mode won't work
+    state.track = track  # note: nesting different mode won't work
     state.profile_level += 1
     if state.profile_level == 1:
         state.last_start_time = clock()
@@ -329,15 +344,18 @@
 
         if mechanism == 'signal':
             signal.signal(signal.SIGPROF, profile_signal_handler)
-            signal.setitimer(signal.ITIMER_PROF,
-                rpt or state.sample_interval, 0.0)
+            signal.setitimer(
+                signal.ITIMER_PROF, rpt or state.sample_interval, 0.0
+            )
         elif mechanism == 'thread':
             frame = inspect.currentframe()
             tid = [k for k, f in sys._current_frames().items() if f == frame][0]
-            state.thread = threading.Thread(target=samplerthread,
-                                 args=(tid,), name="samplerthread")
+            state.thread = threading.Thread(
+                target=samplerthread, args=(tid,), name="samplerthread"
+            )
             state.thread.start()
 
+
 def stop():
     '''Stop profiling, and uninstall the profiling signal handler.'''
     state.profile_level -= 1
@@ -358,16 +376,19 @@
 
     return state
 
+
 def save_data(path):
     with open(path, 'w+') as file:
         file.write("%f %f\n" % state.accumulated_time)
         for sample in state.samples:
             time = sample.time
             stack = sample.stack
-            sites = ['\1'.join([s.path, b'%d' % s.lineno, s.function])
-                     for s in stack]
+            sites = [
+                '\1'.join([s.path, b'%d' % s.lineno, s.function]) for s in stack
+            ]
             file.write("%d\0%s\n" % (time, '\0'.join(sites)))
 
+
 def load_data(path):
     lines = open(path, 'rb').read().splitlines()
 
@@ -380,13 +401,13 @@
         sites = []
         for rawsite in rawsites:
             siteparts = rawsite.split('\1')
-            sites.append(CodeSite.get(siteparts[0], int(siteparts[1]),
-                        siteparts[2]))
+            sites.append(
+                CodeSite.get(siteparts[0], int(siteparts[1]), siteparts[2])
+            )
 
         state.samples.append(Sample(sites, time))
 
 
-
 def reset(frequency=None):
     '''Clear out the state of the profiler.  Do not call while the
     profiler is running.
@@ -411,6 +432,7 @@
 ###########################################################################
 ## Reporting API
 
+
 class SiteStats(object):
     def __init__(self, site):
         self.site = site
@@ -453,6 +475,7 @@
 
         return [s for s in stats.itervalues()]
 
+
 class DisplayFormats:
     ByLine = 0
     ByMethod = 1
@@ -462,6 +485,7 @@
     Json = 5
     Chrome = 6
 
+
 def display(fp=None, format=3, data=None, **kwargs):
     '''Print statistics, either to stdout or the given file object.'''
     if data is None:
@@ -469,6 +493,7 @@
 
     if fp is None:
         import sys
+
         fp = sys.stdout
     if len(data.samples) == 0:
         fp.write(b'No samples recorded.\n')
@@ -496,34 +521,47 @@
         fp.write(b'Sample count: %d\n' % len(data.samples))
         fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time)
 
+
 def display_by_line(data, fp):
     '''Print the profiler data with each sample line represented
     as one row in a table.  Sorted by self-time per line.'''
     stats = SiteStats.buildstats(data.samples)
     stats.sort(reverse=True, key=lambda x: x.selfseconds())
 
-    fp.write(b'%5.5s %10.10s   %7.7s  %-8.8s\n' % (
-        b'%  ', b'cumulative', b'self', b''))
-    fp.write(b'%5.5s  %9.9s  %8.8s  %-8.8s\n' % (
-        b"time", b"seconds", b"seconds", b"name"))
+    fp.write(
+        b'%5.5s %10.10s   %7.7s  %-8.8s\n'
+        % (b'%  ', b'cumulative', b'self', b'')
+    )
+    fp.write(
+        b'%5.5s  %9.9s  %8.8s  %-8.8s\n'
+        % (b"time", b"seconds", b"seconds", b"name")
+    )
 
     for stat in stats:
         site = stat.site
-        sitelabel = '%s:%d:%s' % (site.filename(),
-                                  site.lineno,
-                                  site.function)
-        fp.write(b'%6.2f %9.2f %9.2f  %s\n' % (
-            stat.selfpercent(), stat.totalseconds(),
-            stat.selfseconds(), sitelabel))
+        sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
+        fp.write(
+            b'%6.2f %9.2f %9.2f  %s\n'
+            % (
+                stat.selfpercent(),
+                stat.totalseconds(),
+                stat.selfseconds(),
+                sitelabel,
+            )
+        )
+
 
 def display_by_method(data, fp):
     '''Print the profiler data with each sample function represented
     as one row in a table.  Important lines within that function are
     output as nested rows.  Sorted by self-time per line.'''
-    fp.write(b'%5.5s %10.10s   %7.7s  %-8.8s\n' %
-          ('%  ', 'cumulative', 'self', ''))
-    fp.write(b'%5.5s  %9.9s  %8.8s  %-8.8s\n' %
-          ("time", "seconds", "seconds", "name"))
+    fp.write(
+        b'%5.5s %10.10s   %7.7s  %-8.8s\n' % ('%  ', 'cumulative', 'self', '')
+    )
+    fp.write(
+        b'%5.5s  %9.9s  %8.8s  %-8.8s\n'
+        % ("time", "seconds", "seconds", "name")
+    )
 
     stats = SiteStats.buildstats(data.samples)
 
@@ -542,11 +580,9 @@
             total_self_sec += stat.selfseconds()
             total_percent += stat.selfpercent()
 
-        functiondata.append((fname,
-                             total_cum_sec,
-                             total_self_sec,
-                             total_percent,
-                             sitestats))
+        functiondata.append(
+            (fname, total_cum_sec, total_self_sec, total_percent, sitestats)
+        )
 
     # sort by total self sec
     functiondata.sort(reverse=True, key=lambda x: x[2])
@@ -554,25 +590,36 @@
     for function in functiondata:
         if function[3] < 0.05:
             continue
-        fp.write(b'%6.2f %9.2f %9.2f  %s\n' % (
-            function[3], # total percent
-            function[1], # total cum sec
-            function[2], # total self sec
-            function[0])) # file:function
+        fp.write(
+            b'%6.2f %9.2f %9.2f  %s\n'
+            % (
+                function[3],  # total percent
+                function[1],  # total cum sec
+                function[2],  # total self sec
+                function[0],
+            )
+        )  # file:function
 
         function[4].sort(reverse=True, key=lambda i: i.selfseconds())
         for stat in function[4]:
             # only show line numbers for significant locations (>1% time spent)
             if stat.selfpercent() > 1:
                 source = stat.site.getsource(25)
-                if sys.version_info.major >= 3 and not isinstance(source, bytes):
+                if sys.version_info.major >= 3 and not isinstance(
+                    source, bytes
+                ):
                     source = pycompat.bytestr(source)
 
-                stattuple = (stat.selfpercent(), stat.selfseconds(),
-                             stat.site.lineno, source)
+                stattuple = (
+                    stat.selfpercent(),
+                    stat.selfseconds(),
+                    stat.site.lineno,
+                    source,
+                )
 
                 fp.write(b'%33.0f%% %6.2f   line %d: %s\n' % stattuple)
 
+
 def display_about_method(data, fp, function=None, **kwargs):
     if function is None:
         raise Exception("Invalid function")
@@ -587,8 +634,9 @@
 
     for sample in data.samples:
         for i, site in enumerate(sample.stack):
-            if site.function == function and (not filename
-                or site.filename() == filename):
+            if site.function == function and (
+                not filename or site.filename() == filename
+            ):
                 relevant_samples += 1
                 if i != len(sample.stack) - 1:
                     parent = sample.stack[i + 1]
@@ -605,17 +653,24 @@
     parents = [(parent, count) for parent, count in parents.iteritems()]
     parents.sort(reverse=True, key=lambda x: x[1])
     for parent, count in parents:
-        fp.write(b'%6.2f%%   %s:%s   line %s: %s\n' %
-            (count / relevant_samples * 100,
-             pycompat.fsencode(parent.filename()),
-             pycompat.sysbytes(parent.function),
-             parent.lineno,
-             pycompat.sysbytes(parent.getsource(50))))
+        fp.write(
+            b'%6.2f%%   %s:%s   line %s: %s\n'
+            % (
+                count / relevant_samples * 100,
+                pycompat.fsencode(parent.filename()),
+                pycompat.sysbytes(parent.function),
+                parent.lineno,
+                pycompat.sysbytes(parent.getsource(50)),
+            )
+        )
 
     stats = SiteStats.buildstats(data.samples)
-    stats = [s for s in stats
-               if s.site.function == function and
-               (not filename or s.site.filename() == filename)]
+    stats = [
+        s
+        for s in stats
+        if s.site.function == function
+        and (not filename or s.site.filename() == filename)
+    ]
 
     total_cum_sec = 0
     total_self_sec = 0
@@ -630,20 +685,27 @@
     fp.write(
         b'\n    %s:%s    Total: %0.2fs (%0.2f%%)    Self: %0.2fs (%0.2f%%)\n\n'
         % (
-        pycompat.sysbytes(filename or '___'),
-        pycompat.sysbytes(function),
-        total_cum_sec,
-        total_cum_percent,
-        total_self_sec,
-        total_self_percent
-        ))
+            pycompat.sysbytes(filename or '___'),
+            pycompat.sysbytes(function),
+            total_cum_sec,
+            total_cum_percent,
+            total_self_sec,
+            total_self_percent,
+        )
+    )
 
     children = [(child, count) for child, count in children.iteritems()]
     children.sort(reverse=True, key=lambda x: x[1])
     for child, count in children:
-        fp.write(b'        %6.2f%%   line %s: %s\n' %
-              (count / relevant_samples * 100, child.lineno,
-               pycompat.sysbytes(child.getsource(50))))
+        fp.write(
+            b'        %6.2f%%   line %s: %s\n'
+            % (
+                count / relevant_samples * 100,
+                child.lineno,
+                pycompat.sysbytes(child.getsource(50)),
+            )
+        )
+
 
 def display_hotpath(data, fp, limit=0.05, **kwargs):
     class HotNode(object):
@@ -677,8 +739,11 @@
 
     def _write(node, depth, multiple_siblings):
         site = node.site
-        visiblechildren = [c for c in node.children.itervalues()
-                             if c.count >= (limit * root.count)]
+        visiblechildren = [
+            c
+            for c in node.children.itervalues()
+            if c.count >= (limit * root.count)
+        ]
         if site:
             indent = depth * 2 - 1
             filename = ''
@@ -689,13 +754,18 @@
                 function = childsite.function
 
             # lots of string formatting
-            listpattern = ''.ljust(indent) +\
-                          ('\\' if multiple_siblings else '|') +\
-                          ' %4.1f%%' +\
-                          (' %5.2fs' % node.count if showtime else '') +\
-                          '  %s %s'
-            liststring = listpattern % (node.count / root.count * 100,
-                                        filename, function)
+            listpattern = (
+                ''.ljust(indent)
+                + ('\\' if multiple_siblings else '|')
+                + ' %4.1f%%'
+                + (' %5.2fs' % node.count if showtime else '')
+                + '  %s %s'
+            )
+            liststring = listpattern % (
+                node.count / root.count * 100,
+                filename,
+                function,
+            )
             codepattern = '%' + ('%d' % (55 - len(liststring))) + 's %d:  %s'
             codestring = codepattern % ('line', site.lineno, site.getsource(30))
 
@@ -720,6 +790,7 @@
     if root.count > 0:
         _write(root, 0, False)
 
+
 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
     if scriptpath is None:
         scriptpath = encoding.environ['HOME'] + '/flamegraph.pl'
@@ -750,7 +821,10 @@
     os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
     fp.write(b'Written to %s\n' % outputfile)
 
+
 _pathcache = {}
+
+
 def simplifypath(path):
     '''Attempt to make the path to a Python module easier to read by
     removing whatever part of the Python search path it was found
@@ -762,11 +836,12 @@
     for p in [hgpath] + sys.path:
         prefix = p + os.sep
         if path.startswith(prefix):
-            path = path[len(prefix):]
+            path = path[len(prefix) :]
             break
     _pathcache[path] = path
     return path
 
+
 def write_to_json(data, fp):
     samples = []
 
@@ -775,9 +850,12 @@
 
         for frame in sample.stack:
             stack.append(
-                (pycompat.sysstr(frame.path),
-                 frame.lineno,
-                 pycompat.sysstr(frame.function)))
+                (
+                    pycompat.sysstr(frame.path),
+                    frame.lineno,
+                    pycompat.sysstr(frame.function),
+                )
+            )
 
         samples.append((sample.time, stack))
 
@@ -787,6 +865,7 @@
 
     fp.write(data)
 
+
 def write_to_chrome(data, fp, minthreshold=0.005, maxthreshold=0.999):
     samples = []
     laststack = collections.deque()
@@ -796,7 +875,7 @@
     # representation to save space. It's fiddly but worth it.
     # We maintain a bijection between stack and ID.
     stack2id = {}
-    id2stack = [] # will eventually be rendered
+    id2stack = []  # will eventually be rendered
 
     def stackid(stack):
         if not stack:
@@ -841,8 +920,16 @@
         if minthreshold <= duration <= maxthreshold:
             # ensure no zero-duration events
             sampletime = max(oldtime + clamp, sample.time)
-            samples.append(dict(ph=r'E', name=oldfunc, cat=oldcat, sf=oldsid,
-                                ts=sampletime*1e6, pid=0))
+            samples.append(
+                dict(
+                    ph=r'E',
+                    name=oldfunc,
+                    cat=oldcat,
+                    sf=oldsid,
+                    ts=sampletime * 1e6,
+                    pid=0,
+                )
+            )
         else:
             blacklist.add(oldidx)
 
@@ -850,10 +937,16 @@
     # events given only stack snapshots.
 
     for sample in data.samples:
-        stack = tuple(((r'%s:%d' % (simplifypath(pycompat.sysstr(frame.path)),
-                                    frame.lineno),
-                        pycompat.sysstr(frame.function))
-                       for frame in sample.stack))
+        stack = tuple(
+            (
+                (
+                    r'%s:%d'
+                    % (simplifypath(pycompat.sysstr(frame.path)), frame.lineno),
+                    pycompat.sysstr(frame.function),
+                )
+                for frame in sample.stack
+            )
+        )
         qstack = collections.deque(stack)
         if laststack == qstack:
             continue
@@ -867,23 +960,35 @@
             laststack.appendleft(f)
             path, name = f
             sid = stackid(tuple(laststack))
-            samples.append(dict(ph=r'B', name=name, cat=path,
-                                ts=sample.time*1e6, sf=sid, pid=0))
+            samples.append(
+                dict(
+                    ph=r'B',
+                    name=name,
+                    cat=path,
+                    ts=sample.time * 1e6,
+                    sf=sid,
+                    pid=0,
+                )
+            )
         laststack = collections.deque(stack)
     while laststack:
         poplast()
-    events = [sample for idx, sample in enumerate(samples)
-              if idx not in blacklist]
-    frames = collections.OrderedDict((str(k), v)
-                                     for (k,v) in enumerate(id2stack))
+    events = [
+        sample for idx, sample in enumerate(samples) if idx not in blacklist
+    ]
+    frames = collections.OrderedDict(
+        (str(k), v) for (k, v) in enumerate(id2stack)
+    )
     data = json.dumps(dict(traceEvents=events, stackFrames=frames), indent=1)
     if not isinstance(data, bytes):
         data = data.encode('utf-8')
     fp.write(data)
     fp.write('\n')
 
+
 def printusage():
-    print(r"""
+    print(
+        r"""
 The statprof command line allows you to inspect the last profile's results in
 the following forms:
 
@@ -900,7 +1005,9 @@
     flame [-s --script-path] [-o --output-file path]
         Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg)
         Requires that ~/flamegraph.pl exist.
-        (Specify alternate script path with --script-path.)""")
+        (Specify alternate script path with --script-path.)"""
+    )
+
 
 def main(argv=None):
     if argv is None:
@@ -932,8 +1039,11 @@
 
     # process options
     try:
-        opts, args = pycompat.getoptb(sys.argv[optstart:], "hl:f:o:p:",
-                                   ["help", "limit=", "file=", "output-file=", "script-path="])
+        opts, args = pycompat.getoptb(
+            sys.argv[optstart:],
+            "hl:f:o:p:",
+            ["help", "limit=", "file=", "output-file=", "script-path="],
+        )
     except getopt.error as msg:
         print(msg)
         printusage()
@@ -966,5 +1076,6 @@
 
     return 0
 
+
 if __name__ == r"__main__":
     sys.exit(main())