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