mercurial/statprof.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   120 )
   120 )
   121 
   121 
   122 defaultdict = collections.defaultdict
   122 defaultdict = collections.defaultdict
   123 contextmanager = contextlib.contextmanager
   123 contextmanager = contextlib.contextmanager
   124 
   124 
   125 __all__ = ['start', 'stop', 'reset', 'display', 'profile']
   125 __all__ = [b'start', b'stop', b'reset', b'display', b'profile']
   126 
   126 
   127 skips = {
   127 skips = {
   128     r"util.py:check",
   128     r"util.py:check",
   129     r"extensions.py:closure",
   129     r"extensions.py:closure",
   130     r"color.py:colorcmd",
   130     r"color.py:colorcmd",
   155 
   155 
   156 
   156 
   157 class ProfileState(object):
   157 class ProfileState(object):
   158     def __init__(self, frequency=None):
   158     def __init__(self, frequency=None):
   159         self.reset(frequency)
   159         self.reset(frequency)
   160         self.track = 'cpu'
   160         self.track = b'cpu'
   161 
   161 
   162     def reset(self, frequency=None):
   162     def reset(self, frequency=None):
   163         # total so far
   163         # total so far
   164         self.accumulated_time = (0.0, 0.0)
   164         self.accumulated_time = (0.0, 0.0)
   165         # start_time when timer is active
   165         # start_time when timer is active
   192     def seconds_per_sample(self):
   192     def seconds_per_sample(self):
   193         return self.accumulated_time[self.timeidx] / len(self.samples)
   193         return self.accumulated_time[self.timeidx] / len(self.samples)
   194 
   194 
   195     @property
   195     @property
   196     def timeidx(self):
   196     def timeidx(self):
   197         if self.track == 'real':
   197         if self.track == b'real':
   198             return 1
   198             return 1
   199         return 0
   199         return 0
   200 
   200 
   201 
   201 
   202 state = ProfileState()
   202 state = ProfileState()
   236 
   236 
   237     def getsource(self, length):
   237     def getsource(self, length):
   238         if self.source is None:
   238         if self.source is None:
   239             lineno = self.lineno - 1
   239             lineno = self.lineno - 1
   240             try:
   240             try:
   241                 with open(self.path, 'rb') as fp:
   241                 with open(self.path, b'rb') as fp:
   242                     for i, line in enumerate(fp):
   242                     for i, line in enumerate(fp):
   243                         if i == lineno:
   243                         if i == lineno:
   244                             self.source = line.strip()
   244                             self.source = line.strip()
   245                             break
   245                             break
   246             except:
   246             except:
   247                 pass
   247                 pass
   248             if self.source is None:
   248             if self.source is None:
   249                 self.source = ''
   249                 self.source = b''
   250 
   250 
   251         source = self.source
   251         source = self.source
   252         if len(source) > length:
   252         if len(source) > length:
   253             source = source[: (length - 3)] + "..."
   253             source = source[: (length - 3)] + b"..."
   254         return source
   254         return source
   255 
   255 
   256     def filename(self):
   256     def filename(self):
   257         return os.path.basename(self.path)
   257         return os.path.basename(self.path)
   258 
   258 
   328 
   328 
   329 
   329 
   330 lastmechanism = None
   330 lastmechanism = None
   331 
   331 
   332 
   332 
   333 def start(mechanism='thread', track='cpu'):
   333 def start(mechanism=b'thread', track=b'cpu'):
   334     '''Install the profiling signal handler, and start profiling.'''
   334     '''Install the profiling signal handler, and start profiling.'''
   335     state.track = track  # note: nesting different mode won't work
   335     state.track = track  # note: nesting different mode won't work
   336     state.profile_level += 1
   336     state.profile_level += 1
   337     if state.profile_level == 1:
   337     if state.profile_level == 1:
   338         state.last_start_time = clock()
   338         state.last_start_time = clock()
   340         state.remaining_prof_time = None
   340         state.remaining_prof_time = None
   341 
   341 
   342         global lastmechanism
   342         global lastmechanism
   343         lastmechanism = mechanism
   343         lastmechanism = mechanism
   344 
   344 
   345         if mechanism == 'signal':
   345         if mechanism == b'signal':
   346             signal.signal(signal.SIGPROF, profile_signal_handler)
   346             signal.signal(signal.SIGPROF, profile_signal_handler)
   347             signal.setitimer(
   347             signal.setitimer(
   348                 signal.ITIMER_PROF, rpt or state.sample_interval, 0.0
   348                 signal.ITIMER_PROF, rpt or state.sample_interval, 0.0
   349             )
   349             )
   350         elif mechanism == 'thread':
   350         elif mechanism == b'thread':
   351             frame = inspect.currentframe()
   351             frame = inspect.currentframe()
   352             tid = [k for k, f in sys._current_frames().items() if f == frame][0]
   352             tid = [k for k, f in sys._current_frames().items() if f == frame][0]
   353             state.thread = threading.Thread(
   353             state.thread = threading.Thread(
   354                 target=samplerthread, args=(tid,), name="samplerthread"
   354                 target=samplerthread, args=(tid,), name=b"samplerthread"
   355             )
   355             )
   356             state.thread.start()
   356             state.thread.start()
   357 
   357 
   358 
   358 
   359 def stop():
   359 def stop():
   360     '''Stop profiling, and uninstall the profiling signal handler.'''
   360     '''Stop profiling, and uninstall the profiling signal handler.'''
   361     state.profile_level -= 1
   361     state.profile_level -= 1
   362     if state.profile_level == 0:
   362     if state.profile_level == 0:
   363         if lastmechanism == 'signal':
   363         if lastmechanism == b'signal':
   364             rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
   364             rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
   365             signal.signal(signal.SIGPROF, signal.SIG_IGN)
   365             signal.signal(signal.SIGPROF, signal.SIG_IGN)
   366             state.remaining_prof_time = rpt[0]
   366             state.remaining_prof_time = rpt[0]
   367         elif lastmechanism == 'thread':
   367         elif lastmechanism == b'thread':
   368             stopthread.set()
   368             stopthread.set()
   369             state.thread.join()
   369             state.thread.join()
   370 
   370 
   371         state.accumulate_time(clock())
   371         state.accumulate_time(clock())
   372         state.last_start_time = None
   372         state.last_start_time = None
   373         statprofpath = encoding.environ.get('STATPROF_DEST')
   373         statprofpath = encoding.environ.get(b'STATPROF_DEST')
   374         if statprofpath:
   374         if statprofpath:
   375             save_data(statprofpath)
   375             save_data(statprofpath)
   376 
   376 
   377     return state
   377     return state
   378 
   378 
   379 
   379 
   380 def save_data(path):
   380 def save_data(path):
   381     with open(path, 'w+') as file:
   381     with open(path, b'w+') as file:
   382         file.write("%f %f\n" % state.accumulated_time)
   382         file.write(b"%f %f\n" % state.accumulated_time)
   383         for sample in state.samples:
   383         for sample in state.samples:
   384             time = sample.time
   384             time = sample.time
   385             stack = sample.stack
   385             stack = sample.stack
   386             sites = [
   386             sites = [
   387                 '\1'.join([s.path, b'%d' % s.lineno, s.function]) for s in stack
   387                 b'\1'.join([s.path, b'%d' % s.lineno, s.function])
       
   388                 for s in stack
   388             ]
   389             ]
   389             file.write("%d\0%s\n" % (time, '\0'.join(sites)))
   390             file.write(b"%d\0%s\n" % (time, b'\0'.join(sites)))
   390 
   391 
   391 
   392 
   392 def load_data(path):
   393 def load_data(path):
   393     lines = open(path, 'rb').read().splitlines()
   394     lines = open(path, b'rb').read().splitlines()
   394 
   395 
   395     state.accumulated_time = [float(value) for value in lines[0].split()]
   396     state.accumulated_time = [float(value) for value in lines[0].split()]
   396     state.samples = []
   397     state.samples = []
   397     for line in lines[1:]:
   398     for line in lines[1:]:
   398         parts = line.split('\0')
   399         parts = line.split(b'\0')
   399         time = float(parts[0])
   400         time = float(parts[0])
   400         rawsites = parts[1:]
   401         rawsites = parts[1:]
   401         sites = []
   402         sites = []
   402         for rawsite in rawsites:
   403         for rawsite in rawsites:
   403             siteparts = rawsite.split('\1')
   404             siteparts = rawsite.split(b'\1')
   404             sites.append(
   405             sites.append(
   405                 CodeSite.get(siteparts[0], int(siteparts[1]), siteparts[2])
   406                 CodeSite.get(siteparts[0], int(siteparts[1]), siteparts[2])
   406             )
   407             )
   407 
   408 
   408         state.samples.append(Sample(sites, time))
   409         state.samples.append(Sample(sites, time))
   412     '''Clear out the state of the profiler.  Do not call while the
   413     '''Clear out the state of the profiler.  Do not call while the
   413     profiler is running.
   414     profiler is running.
   414 
   415 
   415     The optional frequency argument specifies the number of samples to
   416     The optional frequency argument specifies the number of samples to
   416     collect per second.'''
   417     collect per second.'''
   417     assert state.profile_level == 0, "Can't reset() while statprof is running"
   418     assert state.profile_level == 0, b"Can't reset() while statprof is running"
   418     CodeSite.cache.clear()
   419     CodeSite.cache.clear()
   419     state.reset(frequency)
   420     state.reset(frequency)
   420 
   421 
   421 
   422 
   422 @contextmanager
   423 @contextmanager
   512     elif format == DisplayFormats.Json:
   513     elif format == DisplayFormats.Json:
   513         write_to_json(data, fp)
   514         write_to_json(data, fp)
   514     elif format == DisplayFormats.Chrome:
   515     elif format == DisplayFormats.Chrome:
   515         write_to_chrome(data, fp, **kwargs)
   516         write_to_chrome(data, fp, **kwargs)
   516     else:
   517     else:
   517         raise Exception("Invalid display format")
   518         raise Exception(b"Invalid display format")
   518 
   519 
   519     if format not in (DisplayFormats.Json, DisplayFormats.Chrome):
   520     if format not in (DisplayFormats.Json, DisplayFormats.Chrome):
   520         fp.write(b'---\n')
   521         fp.write(b'---\n')
   521         fp.write(b'Sample count: %d\n' % len(data.samples))
   522         fp.write(b'Sample count: %d\n' % len(data.samples))
   522         fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time)
   523         fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time)
   537         % (b"time", b"seconds", b"seconds", b"name")
   538         % (b"time", b"seconds", b"seconds", b"name")
   538     )
   539     )
   539 
   540 
   540     for stat in stats:
   541     for stat in stats:
   541         site = stat.site
   542         site = stat.site
   542         sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function)
   543         sitelabel = b'%s:%d:%s' % (site.filename(), site.lineno, site.function)
   543         fp.write(
   544         fp.write(
   544             b'%6.2f %9.2f %9.2f  %s\n'
   545             b'%6.2f %9.2f %9.2f  %s\n'
   545             % (
   546             % (
   546                 stat.selfpercent(),
   547                 stat.selfpercent(),
   547                 stat.totalseconds(),
   548                 stat.totalseconds(),
   554 def display_by_method(data, fp):
   555 def display_by_method(data, fp):
   555     '''Print the profiler data with each sample function represented
   556     '''Print the profiler data with each sample function represented
   556     as one row in a table.  Important lines within that function are
   557     as one row in a table.  Important lines within that function are
   557     output as nested rows.  Sorted by self-time per line.'''
   558     output as nested rows.  Sorted by self-time per line.'''
   558     fp.write(
   559     fp.write(
   559         b'%5.5s %10.10s   %7.7s  %-8.8s\n' % ('%  ', 'cumulative', 'self', '')
   560         b'%5.5s %10.10s   %7.7s  %-8.8s\n'
       
   561         % (b'%  ', b'cumulative', b'self', b'')
   560     )
   562     )
   561     fp.write(
   563     fp.write(
   562         b'%5.5s  %9.9s  %8.8s  %-8.8s\n'
   564         b'%5.5s  %9.9s  %8.8s  %-8.8s\n'
   563         % ("time", "seconds", "seconds", "name")
   565         % (b"time", b"seconds", b"seconds", b"name")
   564     )
   566     )
   565 
   567 
   566     stats = SiteStats.buildstats(data.samples)
   568     stats = SiteStats.buildstats(data.samples)
   567 
   569 
   568     grouped = defaultdict(list)
   570     grouped = defaultdict(list)
   620                 fp.write(b'%33.0f%% %6.2f   line %d: %s\n' % stattuple)
   622                 fp.write(b'%33.0f%% %6.2f   line %d: %s\n' % stattuple)
   621 
   623 
   622 
   624 
   623 def display_about_method(data, fp, function=None, **kwargs):
   625 def display_about_method(data, fp, function=None, **kwargs):
   624     if function is None:
   626     if function is None:
   625         raise Exception("Invalid function")
   627         raise Exception(b"Invalid function")
   626 
   628 
   627     filename = None
   629     filename = None
   628     if ':' in function:
   630     if b':' in function:
   629         filename, function = function.split(':')
   631         filename, function = function.split(b':')
   630 
   632 
   631     relevant_samples = 0
   633     relevant_samples = 0
   632     parents = {}
   634     parents = {}
   633     children = {}
   635     children = {}
   634 
   636 
   683         total_cum_percent += stat.totalpercent()
   685         total_cum_percent += stat.totalpercent()
   684 
   686 
   685     fp.write(
   687     fp.write(
   686         b'\n    %s:%s    Total: %0.2fs (%0.2f%%)    Self: %0.2fs (%0.2f%%)\n\n'
   688         b'\n    %s:%s    Total: %0.2fs (%0.2f%%)    Self: %0.2fs (%0.2f%%)\n\n'
   687         % (
   689         % (
   688             pycompat.sysbytes(filename or '___'),
   690             pycompat.sysbytes(filename or b'___'),
   689             pycompat.sysbytes(function),
   691             pycompat.sysbytes(function),
   690             total_cum_sec,
   692             total_cum_sec,
   691             total_cum_percent,
   693             total_cum_percent,
   692             total_self_sec,
   694             total_self_sec,
   693             total_self_percent,
   695             total_self_percent,
   744             for c in node.children.itervalues()
   746             for c in node.children.itervalues()
   745             if c.count >= (limit * root.count)
   747             if c.count >= (limit * root.count)
   746         ]
   748         ]
   747         if site:
   749         if site:
   748             indent = depth * 2 - 1
   750             indent = depth * 2 - 1
   749             filename = ''
   751             filename = b''
   750             function = ''
   752             function = b''
   751             if len(node.children) > 0:
   753             if len(node.children) > 0:
   752                 childsite = list(node.children.itervalues())[0].site
   754                 childsite = list(node.children.itervalues())[0].site
   753                 filename = (childsite.filename() + ':').ljust(15)
   755                 filename = (childsite.filename() + b':').ljust(15)
   754                 function = childsite.function
   756                 function = childsite.function
   755 
   757 
   756             # lots of string formatting
   758             # lots of string formatting
   757             listpattern = (
   759             listpattern = (
   758                 ''.ljust(indent)
   760                 b''.ljust(indent)
   759                 + ('\\' if multiple_siblings else '|')
   761                 + (b'\\' if multiple_siblings else b'|')
   760                 + ' %4.1f%%'
   762                 + b' %4.1f%%'
   761                 + (' %5.2fs' % node.count if showtime else '')
   763                 + (b' %5.2fs' % node.count if showtime else b'')
   762                 + '  %s %s'
   764                 + b'  %s %s'
   763             )
   765             )
   764             liststring = listpattern % (
   766             liststring = listpattern % (
   765                 node.count / root.count * 100,
   767                 node.count / root.count * 100,
   766                 filename,
   768                 filename,
   767                 function,
   769                 function,
   768             )
   770             )
   769             codepattern = '%' + ('%d' % (55 - len(liststring))) + 's %d:  %s'
   771             codepattern = b'%' + (b'%d' % (55 - len(liststring))) + b's %d:  %s'
   770             codestring = codepattern % ('line', site.lineno, site.getsource(30))
   772             codestring = codepattern % (
       
   773                 b'line',
       
   774                 site.lineno,
       
   775                 site.getsource(30),
       
   776             )
   771 
   777 
   772             finalstring = liststring + codestring
   778             finalstring = liststring + codestring
   773             childrensamples = sum([c.count for c in node.children.itervalues()])
   779             childrensamples = sum([c.count for c in node.children.itervalues()])
   774             # Make frames that performed more than 10% of the operation red
   780             # Make frames that performed more than 10% of the operation red
   775             if node.count - childrensamples > (0.1 * root.count):
   781             if node.count - childrensamples > (0.1 * root.count):
   776                 finalstring = '\033[91m' + finalstring + '\033[0m'
   782                 finalstring = b'\033[91m' + finalstring + b'\033[0m'
   777             # Make frames that didn't actually perform work dark grey
   783             # Make frames that didn't actually perform work dark grey
   778             elif node.count - childrensamples == 0:
   784             elif node.count - childrensamples == 0:
   779                 finalstring = '\033[90m' + finalstring + '\033[0m'
   785                 finalstring = b'\033[90m' + finalstring + b'\033[0m'
   780             fp.write(finalstring + b'\n')
   786             fp.write(finalstring + b'\n')
   781 
   787 
   782         newdepth = depth
   788         newdepth = depth
   783         if len(visiblechildren) > 1 or multiple_siblings:
   789         if len(visiblechildren) > 1 or multiple_siblings:
   784             newdepth += 1
   790             newdepth += 1
   791         _write(root, 0, False)
   797         _write(root, 0, False)
   792 
   798 
   793 
   799 
   794 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
   800 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs):
   795     if scriptpath is None:
   801     if scriptpath is None:
   796         scriptpath = encoding.environ['HOME'] + '/flamegraph.pl'
   802         scriptpath = encoding.environ[b'HOME'] + b'/flamegraph.pl'
   797     if not os.path.exists(scriptpath):
   803     if not os.path.exists(scriptpath):
   798         fp.write(b'error: missing %s\n' % scriptpath)
   804         fp.write(b'error: missing %s\n' % scriptpath)
   799         fp.write(b'get it here: https://github.com/brendangregg/FlameGraph\n')
   805         fp.write(b'get it here: https://github.com/brendangregg/FlameGraph\n')
   800         return
   806         return
   801 
   807 
   802     lines = {}
   808     lines = {}
   803     for sample in data.samples:
   809     for sample in data.samples:
   804         sites = [s.function for s in sample.stack]
   810         sites = [s.function for s in sample.stack]
   805         sites.reverse()
   811         sites.reverse()
   806         line = ';'.join(sites)
   812         line = b';'.join(sites)
   807         if line in lines:
   813         if line in lines:
   808             lines[line] = lines[line] + 1
   814             lines[line] = lines[line] + 1
   809         else:
   815         else:
   810             lines[line] = 1
   816             lines[line] = 1
   811 
   817 
   812     fd, path = pycompat.mkstemp()
   818     fd, path = pycompat.mkstemp()
   813 
   819 
   814     with open(path, "w+") as file:
   820     with open(path, b"w+") as file:
   815         for line, count in lines.iteritems():
   821         for line, count in lines.iteritems():
   816             file.write("%s %d\n" % (line, count))
   822             file.write(b"%s %d\n" % (line, count))
   817 
   823 
   818     if outputfile is None:
   824     if outputfile is None:
   819         outputfile = '~/flamegraph.svg'
   825         outputfile = b'~/flamegraph.svg'
   820 
   826 
   821     os.system("perl ~/flamegraph.pl %s > %s" % (path, outputfile))
   827     os.system(b"perl ~/flamegraph.pl %s > %s" % (path, outputfile))
   822     fp.write(b'Written to %s\n' % outputfile)
   828     fp.write(b'Written to %s\n' % outputfile)
   823 
   829 
   824 
   830 
   825 _pathcache = {}
   831 _pathcache = {}
   826 
   832 
   981     )
   987     )
   982     data = json.dumps(dict(traceEvents=events, stackFrames=frames), indent=1)
   988     data = json.dumps(dict(traceEvents=events, stackFrames=frames), indent=1)
   983     if not isinstance(data, bytes):
   989     if not isinstance(data, bytes):
   984         data = data.encode('utf-8')
   990         data = data.encode('utf-8')
   985     fp.write(data)
   991     fp.write(data)
   986     fp.write('\n')
   992     fp.write(b'\n')
   987 
   993 
   988 
   994 
   989 def printusage():
   995 def printusage():
   990     print(
   996     print(
   991         r"""
   997         r"""
  1018         return 0
  1024         return 0
  1019 
  1025 
  1020     displayargs = {}
  1026     displayargs = {}
  1021 
  1027 
  1022     optstart = 2
  1028     optstart = 2
  1023     displayargs['function'] = None
  1029     displayargs[b'function'] = None
  1024     if argv[1] == r'hotpath':
  1030     if argv[1] == r'hotpath':
  1025         displayargs['format'] = DisplayFormats.Hotpath
  1031         displayargs[b'format'] = DisplayFormats.Hotpath
  1026     elif argv[1] == r'lines':
  1032     elif argv[1] == r'lines':
  1027         displayargs['format'] = DisplayFormats.ByLine
  1033         displayargs[b'format'] = DisplayFormats.ByLine
  1028     elif argv[1] == r'functions':
  1034     elif argv[1] == r'functions':
  1029         displayargs['format'] = DisplayFormats.ByMethod
  1035         displayargs[b'format'] = DisplayFormats.ByMethod
  1030     elif argv[1] == r'function':
  1036     elif argv[1] == r'function':
  1031         displayargs['format'] = DisplayFormats.AboutMethod
  1037         displayargs[b'format'] = DisplayFormats.AboutMethod
  1032         displayargs['function'] = argv[2]
  1038         displayargs[b'function'] = argv[2]
  1033         optstart = 3
  1039         optstart = 3
  1034     elif argv[1] == r'flame':
  1040     elif argv[1] == r'flame':
  1035         displayargs['format'] = DisplayFormats.FlameGraph
  1041         displayargs[b'format'] = DisplayFormats.FlameGraph
  1036     else:
  1042     else:
  1037         printusage()
  1043         printusage()
  1038         return 0
  1044         return 0
  1039 
  1045 
  1040     # process options
  1046     # process options
  1041     try:
  1047     try:
  1042         opts, args = pycompat.getoptb(
  1048         opts, args = pycompat.getoptb(
  1043             sys.argv[optstart:],
  1049             sys.argv[optstart:],
  1044             "hl:f:o:p:",
  1050             b"hl:f:o:p:",
  1045             ["help", "limit=", "file=", "output-file=", "script-path="],
  1051             [b"help", b"limit=", b"file=", b"output-file=", b"script-path="],
  1046         )
  1052         )
  1047     except getopt.error as msg:
  1053     except getopt.error as msg:
  1048         print(msg)
  1054         print(msg)
  1049         printusage()
  1055         printusage()
  1050         return 2
  1056         return 2
  1051 
  1057 
  1052     displayargs['limit'] = 0.05
  1058     displayargs[b'limit'] = 0.05
  1053     path = None
  1059     path = None
  1054     for o, value in opts:
  1060     for o, value in opts:
  1055         if o in (r"-l", r"--limit"):
  1061         if o in (r"-l", r"--limit"):
  1056             displayargs['limit'] = float(value)
  1062             displayargs[b'limit'] = float(value)
  1057         elif o in (r"-f", r"--file"):
  1063         elif o in (r"-f", r"--file"):
  1058             path = value
  1064             path = value
  1059         elif o in (r"-o", r"--output-file"):
  1065         elif o in (r"-o", r"--output-file"):
  1060             displayargs['outputfile'] = value
  1066             displayargs[b'outputfile'] = value
  1061         elif o in (r"-p", r"--script-path"):
  1067         elif o in (r"-p", r"--script-path"):
  1062             displayargs['scriptpath'] = value
  1068             displayargs[b'scriptpath'] = value
  1063         elif o in (r"-h", r"help"):
  1069         elif o in (r"-h", r"help"):
  1064             printusage()
  1070             printusage()
  1065             return 0
  1071             return 0
  1066         else:
  1072         else:
  1067             assert False, "unhandled option %s" % o
  1073             assert False, b"unhandled option %s" % o
  1068 
  1074 
  1069     if not path:
  1075     if not path:
  1070         print(r'must specify --file to load')
  1076         print(r'must specify --file to load')
  1071         return 1
  1077         return 1
  1072 
  1078