142 } |
142 } |
143 |
143 |
144 ########################################################################### |
144 ########################################################################### |
145 ## Utils |
145 ## Utils |
146 |
146 |
|
147 |
147 def clock(): |
148 def clock(): |
148 times = os.times() |
149 times = os.times() |
149 return (times[0] + times[1], times[4]) |
150 return (times[0] + times[1], times[4]) |
150 |
151 |
151 |
152 |
152 ########################################################################### |
153 ########################################################################### |
153 ## Collection data structures |
154 ## Collection data structures |
|
155 |
154 |
156 |
155 class ProfileState(object): |
157 class ProfileState(object): |
156 def __init__(self, frequency=None): |
158 def __init__(self, frequency=None): |
157 self.reset(frequency) |
159 self.reset(frequency) |
158 self.track = 'cpu' |
160 self.track = 'cpu' |
246 if self.source is None: |
248 if self.source is None: |
247 self.source = '' |
249 self.source = '' |
248 |
250 |
249 source = self.source |
251 source = self.source |
250 if len(source) > length: |
252 if len(source) > length: |
251 source = source[:(length - 3)] + "..." |
253 source = source[: (length - 3)] + "..." |
252 return source |
254 return source |
253 |
255 |
254 def filename(self): |
256 def filename(self): |
255 return os.path.basename(self.path) |
257 return os.path.basename(self.path) |
256 |
258 |
257 def skipname(self): |
259 def skipname(self): |
258 return r'%s:%s' % (self.filename(), self.function) |
260 return r'%s:%s' % (self.filename(), self.function) |
|
261 |
259 |
262 |
260 class Sample(object): |
263 class Sample(object): |
261 __slots__ = (r'stack', r'time') |
264 __slots__ = (r'stack', r'time') |
262 |
265 |
263 def __init__(self, stack, time): |
266 def __init__(self, stack, time): |
267 @classmethod |
270 @classmethod |
268 def from_frame(cls, frame, time): |
271 def from_frame(cls, frame, time): |
269 stack = [] |
272 stack = [] |
270 |
273 |
271 while frame: |
274 while frame: |
272 stack.append(CodeSite.get( |
275 stack.append( |
273 pycompat.sysbytes(frame.f_code.co_filename), |
276 CodeSite.get( |
274 frame.f_lineno, |
277 pycompat.sysbytes(frame.f_code.co_filename), |
275 pycompat.sysbytes(frame.f_code.co_name))) |
278 frame.f_lineno, |
|
279 pycompat.sysbytes(frame.f_code.co_name), |
|
280 ) |
|
281 ) |
276 frame = frame.f_back |
282 frame = frame.f_back |
277 |
283 |
278 return Sample(stack, time) |
284 return Sample(stack, time) |
|
285 |
279 |
286 |
280 ########################################################################### |
287 ########################################################################### |
281 ## SIGPROF handler |
288 ## SIGPROF handler |
|
289 |
282 |
290 |
283 def profile_signal_handler(signum, frame): |
291 def profile_signal_handler(signum, frame): |
284 if state.profile_level > 0: |
292 if state.profile_level > 0: |
285 now = clock() |
293 now = clock() |
286 state.accumulate_time(now) |
294 state.accumulate_time(now) |
287 |
295 |
288 timestamp = state.accumulated_time[state.timeidx] |
296 timestamp = state.accumulated_time[state.timeidx] |
289 state.samples.append(Sample.from_frame(frame, timestamp)) |
297 state.samples.append(Sample.from_frame(frame, timestamp)) |
290 |
298 |
291 signal.setitimer(signal.ITIMER_PROF, |
299 signal.setitimer(signal.ITIMER_PROF, state.sample_interval, 0.0) |
292 state.sample_interval, 0.0) |
|
293 state.last_start_time = now |
300 state.last_start_time = now |
294 |
301 |
|
302 |
295 stopthread = threading.Event() |
303 stopthread = threading.Event() |
|
304 |
|
305 |
296 def samplerthread(tid): |
306 def samplerthread(tid): |
297 while not stopthread.is_set(): |
307 while not stopthread.is_set(): |
298 now = clock() |
308 now = clock() |
299 state.accumulate_time(now) |
309 state.accumulate_time(now) |
300 |
310 |
306 state.last_start_time = now |
316 state.last_start_time = now |
307 time.sleep(state.sample_interval) |
317 time.sleep(state.sample_interval) |
308 |
318 |
309 stopthread.clear() |
319 stopthread.clear() |
310 |
320 |
|
321 |
311 ########################################################################### |
322 ########################################################################### |
312 ## Profiling API |
323 ## Profiling API |
313 |
324 |
|
325 |
314 def is_active(): |
326 def is_active(): |
315 return state.profile_level > 0 |
327 return state.profile_level > 0 |
316 |
328 |
|
329 |
317 lastmechanism = None |
330 lastmechanism = None |
|
331 |
|
332 |
318 def start(mechanism='thread', track='cpu'): |
333 def start(mechanism='thread', track='cpu'): |
319 '''Install the profiling signal handler, and start profiling.''' |
334 '''Install the profiling signal handler, and start profiling.''' |
320 state.track = track # note: nesting different mode won't work |
335 state.track = track # note: nesting different mode won't work |
321 state.profile_level += 1 |
336 state.profile_level += 1 |
322 if state.profile_level == 1: |
337 if state.profile_level == 1: |
323 state.last_start_time = clock() |
338 state.last_start_time = clock() |
324 rpt = state.remaining_prof_time |
339 rpt = state.remaining_prof_time |
325 state.remaining_prof_time = None |
340 state.remaining_prof_time = None |
327 global lastmechanism |
342 global lastmechanism |
328 lastmechanism = mechanism |
343 lastmechanism = mechanism |
329 |
344 |
330 if mechanism == 'signal': |
345 if mechanism == 'signal': |
331 signal.signal(signal.SIGPROF, profile_signal_handler) |
346 signal.signal(signal.SIGPROF, profile_signal_handler) |
332 signal.setitimer(signal.ITIMER_PROF, |
347 signal.setitimer( |
333 rpt or state.sample_interval, 0.0) |
348 signal.ITIMER_PROF, rpt or state.sample_interval, 0.0 |
|
349 ) |
334 elif mechanism == 'thread': |
350 elif mechanism == 'thread': |
335 frame = inspect.currentframe() |
351 frame = inspect.currentframe() |
336 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] |
337 state.thread = threading.Thread(target=samplerthread, |
353 state.thread = threading.Thread( |
338 args=(tid,), name="samplerthread") |
354 target=samplerthread, args=(tid,), name="samplerthread" |
|
355 ) |
339 state.thread.start() |
356 state.thread.start() |
|
357 |
340 |
358 |
341 def stop(): |
359 def stop(): |
342 '''Stop profiling, and uninstall the profiling signal handler.''' |
360 '''Stop profiling, and uninstall the profiling signal handler.''' |
343 state.profile_level -= 1 |
361 state.profile_level -= 1 |
344 if state.profile_level == 0: |
362 if state.profile_level == 0: |
356 if statprofpath: |
374 if statprofpath: |
357 save_data(statprofpath) |
375 save_data(statprofpath) |
358 |
376 |
359 return state |
377 return state |
360 |
378 |
|
379 |
361 def save_data(path): |
380 def save_data(path): |
362 with open(path, 'w+') as file: |
381 with open(path, 'w+') as file: |
363 file.write("%f %f\n" % state.accumulated_time) |
382 file.write("%f %f\n" % state.accumulated_time) |
364 for sample in state.samples: |
383 for sample in state.samples: |
365 time = sample.time |
384 time = sample.time |
366 stack = sample.stack |
385 stack = sample.stack |
367 sites = ['\1'.join([s.path, b'%d' % s.lineno, s.function]) |
386 sites = [ |
368 for s in stack] |
387 '\1'.join([s.path, b'%d' % s.lineno, s.function]) for s in stack |
|
388 ] |
369 file.write("%d\0%s\n" % (time, '\0'.join(sites))) |
389 file.write("%d\0%s\n" % (time, '\0'.join(sites))) |
|
390 |
370 |
391 |
371 def load_data(path): |
392 def load_data(path): |
372 lines = open(path, 'rb').read().splitlines() |
393 lines = open(path, 'rb').read().splitlines() |
373 |
394 |
374 state.accumulated_time = [float(value) for value in lines[0].split()] |
395 state.accumulated_time = [float(value) for value in lines[0].split()] |
378 time = float(parts[0]) |
399 time = float(parts[0]) |
379 rawsites = parts[1:] |
400 rawsites = parts[1:] |
380 sites = [] |
401 sites = [] |
381 for rawsite in rawsites: |
402 for rawsite in rawsites: |
382 siteparts = rawsite.split('\1') |
403 siteparts = rawsite.split('\1') |
383 sites.append(CodeSite.get(siteparts[0], int(siteparts[1]), |
404 sites.append( |
384 siteparts[2])) |
405 CodeSite.get(siteparts[0], int(siteparts[1]), siteparts[2]) |
|
406 ) |
385 |
407 |
386 state.samples.append(Sample(sites, time)) |
408 state.samples.append(Sample(sites, time)) |
387 |
|
388 |
409 |
389 |
410 |
390 def reset(frequency=None): |
411 def reset(frequency=None): |
391 '''Clear out the state of the profiler. Do not call while the |
412 '''Clear out the state of the profiler. Do not call while the |
392 profiler is running. |
413 profiler is running. |
450 |
472 |
451 if i == 0: |
473 if i == 0: |
452 sitestat.addself() |
474 sitestat.addself() |
453 |
475 |
454 return [s for s in stats.itervalues()] |
476 return [s for s in stats.itervalues()] |
|
477 |
455 |
478 |
456 class DisplayFormats: |
479 class DisplayFormats: |
457 ByLine = 0 |
480 ByLine = 0 |
458 ByMethod = 1 |
481 ByMethod = 1 |
459 AboutMethod = 2 |
482 AboutMethod = 2 |
460 Hotpath = 3 |
483 Hotpath = 3 |
461 FlameGraph = 4 |
484 FlameGraph = 4 |
462 Json = 5 |
485 Json = 5 |
463 Chrome = 6 |
486 Chrome = 6 |
464 |
487 |
|
488 |
465 def display(fp=None, format=3, data=None, **kwargs): |
489 def display(fp=None, format=3, data=None, **kwargs): |
466 '''Print statistics, either to stdout or the given file object.''' |
490 '''Print statistics, either to stdout or the given file object.''' |
467 if data is None: |
491 if data is None: |
468 data = state |
492 data = state |
469 |
493 |
470 if fp is None: |
494 if fp is None: |
471 import sys |
495 import sys |
|
496 |
472 fp = sys.stdout |
497 fp = sys.stdout |
473 if len(data.samples) == 0: |
498 if len(data.samples) == 0: |
474 fp.write(b'No samples recorded.\n') |
499 fp.write(b'No samples recorded.\n') |
475 return |
500 return |
476 |
501 |
494 if format not in (DisplayFormats.Json, DisplayFormats.Chrome): |
519 if format not in (DisplayFormats.Json, DisplayFormats.Chrome): |
495 fp.write(b'---\n') |
520 fp.write(b'---\n') |
496 fp.write(b'Sample count: %d\n' % len(data.samples)) |
521 fp.write(b'Sample count: %d\n' % len(data.samples)) |
497 fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time) |
522 fp.write(b'Total time: %f seconds (%f wall)\n' % data.accumulated_time) |
498 |
523 |
|
524 |
499 def display_by_line(data, fp): |
525 def display_by_line(data, fp): |
500 '''Print the profiler data with each sample line represented |
526 '''Print the profiler data with each sample line represented |
501 as one row in a table. Sorted by self-time per line.''' |
527 as one row in a table. Sorted by self-time per line.''' |
502 stats = SiteStats.buildstats(data.samples) |
528 stats = SiteStats.buildstats(data.samples) |
503 stats.sort(reverse=True, key=lambda x: x.selfseconds()) |
529 stats.sort(reverse=True, key=lambda x: x.selfseconds()) |
504 |
530 |
505 fp.write(b'%5.5s %10.10s %7.7s %-8.8s\n' % ( |
531 fp.write( |
506 b'% ', b'cumulative', b'self', b'')) |
532 b'%5.5s %10.10s %7.7s %-8.8s\n' |
507 fp.write(b'%5.5s %9.9s %8.8s %-8.8s\n' % ( |
533 % (b'% ', b'cumulative', b'self', b'') |
508 b"time", b"seconds", b"seconds", b"name")) |
534 ) |
|
535 fp.write( |
|
536 b'%5.5s %9.9s %8.8s %-8.8s\n' |
|
537 % (b"time", b"seconds", b"seconds", b"name") |
|
538 ) |
509 |
539 |
510 for stat in stats: |
540 for stat in stats: |
511 site = stat.site |
541 site = stat.site |
512 sitelabel = '%s:%d:%s' % (site.filename(), |
542 sitelabel = '%s:%d:%s' % (site.filename(), site.lineno, site.function) |
513 site.lineno, |
543 fp.write( |
514 site.function) |
544 b'%6.2f %9.2f %9.2f %s\n' |
515 fp.write(b'%6.2f %9.2f %9.2f %s\n' % ( |
545 % ( |
516 stat.selfpercent(), stat.totalseconds(), |
546 stat.selfpercent(), |
517 stat.selfseconds(), sitelabel)) |
547 stat.totalseconds(), |
|
548 stat.selfseconds(), |
|
549 sitelabel, |
|
550 ) |
|
551 ) |
|
552 |
518 |
553 |
519 def display_by_method(data, fp): |
554 def display_by_method(data, fp): |
520 '''Print the profiler data with each sample function represented |
555 '''Print the profiler data with each sample function represented |
521 as one row in a table. Important lines within that function are |
556 as one row in a table. Important lines within that function are |
522 output as nested rows. Sorted by self-time per line.''' |
557 output as nested rows. Sorted by self-time per line.''' |
523 fp.write(b'%5.5s %10.10s %7.7s %-8.8s\n' % |
558 fp.write( |
524 ('% ', 'cumulative', 'self', '')) |
559 b'%5.5s %10.10s %7.7s %-8.8s\n' % ('% ', 'cumulative', 'self', '') |
525 fp.write(b'%5.5s %9.9s %8.8s %-8.8s\n' % |
560 ) |
526 ("time", "seconds", "seconds", "name")) |
561 fp.write( |
|
562 b'%5.5s %9.9s %8.8s %-8.8s\n' |
|
563 % ("time", "seconds", "seconds", "name") |
|
564 ) |
527 |
565 |
528 stats = SiteStats.buildstats(data.samples) |
566 stats = SiteStats.buildstats(data.samples) |
529 |
567 |
530 grouped = defaultdict(list) |
568 grouped = defaultdict(list) |
531 for stat in stats: |
569 for stat in stats: |
540 for stat in sitestats: |
578 for stat in sitestats: |
541 total_cum_sec += stat.totalseconds() |
579 total_cum_sec += stat.totalseconds() |
542 total_self_sec += stat.selfseconds() |
580 total_self_sec += stat.selfseconds() |
543 total_percent += stat.selfpercent() |
581 total_percent += stat.selfpercent() |
544 |
582 |
545 functiondata.append((fname, |
583 functiondata.append( |
546 total_cum_sec, |
584 (fname, total_cum_sec, total_self_sec, total_percent, sitestats) |
547 total_self_sec, |
585 ) |
548 total_percent, |
|
549 sitestats)) |
|
550 |
586 |
551 # sort by total self sec |
587 # sort by total self sec |
552 functiondata.sort(reverse=True, key=lambda x: x[2]) |
588 functiondata.sort(reverse=True, key=lambda x: x[2]) |
553 |
589 |
554 for function in functiondata: |
590 for function in functiondata: |
555 if function[3] < 0.05: |
591 if function[3] < 0.05: |
556 continue |
592 continue |
557 fp.write(b'%6.2f %9.2f %9.2f %s\n' % ( |
593 fp.write( |
558 function[3], # total percent |
594 b'%6.2f %9.2f %9.2f %s\n' |
559 function[1], # total cum sec |
595 % ( |
560 function[2], # total self sec |
596 function[3], # total percent |
561 function[0])) # file:function |
597 function[1], # total cum sec |
|
598 function[2], # total self sec |
|
599 function[0], |
|
600 ) |
|
601 ) # file:function |
562 |
602 |
563 function[4].sort(reverse=True, key=lambda i: i.selfseconds()) |
603 function[4].sort(reverse=True, key=lambda i: i.selfseconds()) |
564 for stat in function[4]: |
604 for stat in function[4]: |
565 # only show line numbers for significant locations (>1% time spent) |
605 # only show line numbers for significant locations (>1% time spent) |
566 if stat.selfpercent() > 1: |
606 if stat.selfpercent() > 1: |
567 source = stat.site.getsource(25) |
607 source = stat.site.getsource(25) |
568 if sys.version_info.major >= 3 and not isinstance(source, bytes): |
608 if sys.version_info.major >= 3 and not isinstance( |
|
609 source, bytes |
|
610 ): |
569 source = pycompat.bytestr(source) |
611 source = pycompat.bytestr(source) |
570 |
612 |
571 stattuple = (stat.selfpercent(), stat.selfseconds(), |
613 stattuple = ( |
572 stat.site.lineno, source) |
614 stat.selfpercent(), |
|
615 stat.selfseconds(), |
|
616 stat.site.lineno, |
|
617 source, |
|
618 ) |
573 |
619 |
574 fp.write(b'%33.0f%% %6.2f line %d: %s\n' % stattuple) |
620 fp.write(b'%33.0f%% %6.2f line %d: %s\n' % stattuple) |
|
621 |
575 |
622 |
576 def display_about_method(data, fp, function=None, **kwargs): |
623 def display_about_method(data, fp, function=None, **kwargs): |
577 if function is None: |
624 if function is None: |
578 raise Exception("Invalid function") |
625 raise Exception("Invalid function") |
579 |
626 |
585 parents = {} |
632 parents = {} |
586 children = {} |
633 children = {} |
587 |
634 |
588 for sample in data.samples: |
635 for sample in data.samples: |
589 for i, site in enumerate(sample.stack): |
636 for i, site in enumerate(sample.stack): |
590 if site.function == function and (not filename |
637 if site.function == function and ( |
591 or site.filename() == filename): |
638 not filename or site.filename() == filename |
|
639 ): |
592 relevant_samples += 1 |
640 relevant_samples += 1 |
593 if i != len(sample.stack) - 1: |
641 if i != len(sample.stack) - 1: |
594 parent = sample.stack[i + 1] |
642 parent = sample.stack[i + 1] |
595 if parent in parents: |
643 if parent in parents: |
596 parents[parent] = parents[parent] + 1 |
644 parents[parent] = parents[parent] + 1 |
603 children[site] = 1 |
651 children[site] = 1 |
604 |
652 |
605 parents = [(parent, count) for parent, count in parents.iteritems()] |
653 parents = [(parent, count) for parent, count in parents.iteritems()] |
606 parents.sort(reverse=True, key=lambda x: x[1]) |
654 parents.sort(reverse=True, key=lambda x: x[1]) |
607 for parent, count in parents: |
655 for parent, count in parents: |
608 fp.write(b'%6.2f%% %s:%s line %s: %s\n' % |
656 fp.write( |
609 (count / relevant_samples * 100, |
657 b'%6.2f%% %s:%s line %s: %s\n' |
610 pycompat.fsencode(parent.filename()), |
658 % ( |
611 pycompat.sysbytes(parent.function), |
659 count / relevant_samples * 100, |
612 parent.lineno, |
660 pycompat.fsencode(parent.filename()), |
613 pycompat.sysbytes(parent.getsource(50)))) |
661 pycompat.sysbytes(parent.function), |
|
662 parent.lineno, |
|
663 pycompat.sysbytes(parent.getsource(50)), |
|
664 ) |
|
665 ) |
614 |
666 |
615 stats = SiteStats.buildstats(data.samples) |
667 stats = SiteStats.buildstats(data.samples) |
616 stats = [s for s in stats |
668 stats = [ |
617 if s.site.function == function and |
669 s |
618 (not filename or s.site.filename() == filename)] |
670 for s in stats |
|
671 if s.site.function == function |
|
672 and (not filename or s.site.filename() == filename) |
|
673 ] |
619 |
674 |
620 total_cum_sec = 0 |
675 total_cum_sec = 0 |
621 total_self_sec = 0 |
676 total_self_sec = 0 |
622 total_self_percent = 0 |
677 total_self_percent = 0 |
623 total_cum_percent = 0 |
678 total_cum_percent = 0 |
628 total_cum_percent += stat.totalpercent() |
683 total_cum_percent += stat.totalpercent() |
629 |
684 |
630 fp.write( |
685 fp.write( |
631 b'\n %s:%s Total: %0.2fs (%0.2f%%) Self: %0.2fs (%0.2f%%)\n\n' |
686 b'\n %s:%s Total: %0.2fs (%0.2f%%) Self: %0.2fs (%0.2f%%)\n\n' |
632 % ( |
687 % ( |
633 pycompat.sysbytes(filename or '___'), |
688 pycompat.sysbytes(filename or '___'), |
634 pycompat.sysbytes(function), |
689 pycompat.sysbytes(function), |
635 total_cum_sec, |
690 total_cum_sec, |
636 total_cum_percent, |
691 total_cum_percent, |
637 total_self_sec, |
692 total_self_sec, |
638 total_self_percent |
693 total_self_percent, |
639 )) |
694 ) |
|
695 ) |
640 |
696 |
641 children = [(child, count) for child, count in children.iteritems()] |
697 children = [(child, count) for child, count in children.iteritems()] |
642 children.sort(reverse=True, key=lambda x: x[1]) |
698 children.sort(reverse=True, key=lambda x: x[1]) |
643 for child, count in children: |
699 for child, count in children: |
644 fp.write(b' %6.2f%% line %s: %s\n' % |
700 fp.write( |
645 (count / relevant_samples * 100, child.lineno, |
701 b' %6.2f%% line %s: %s\n' |
646 pycompat.sysbytes(child.getsource(50)))) |
702 % ( |
|
703 count / relevant_samples * 100, |
|
704 child.lineno, |
|
705 pycompat.sysbytes(child.getsource(50)), |
|
706 ) |
|
707 ) |
|
708 |
647 |
709 |
648 def display_hotpath(data, fp, limit=0.05, **kwargs): |
710 def display_hotpath(data, fp, limit=0.05, **kwargs): |
649 class HotNode(object): |
711 class HotNode(object): |
650 def __init__(self, site): |
712 def __init__(self, site): |
651 self.site = site |
713 self.site = site |
675 lasttime = sample.time |
737 lasttime = sample.time |
676 showtime = kwargs.get(r'showtime', True) |
738 showtime = kwargs.get(r'showtime', True) |
677 |
739 |
678 def _write(node, depth, multiple_siblings): |
740 def _write(node, depth, multiple_siblings): |
679 site = node.site |
741 site = node.site |
680 visiblechildren = [c for c in node.children.itervalues() |
742 visiblechildren = [ |
681 if c.count >= (limit * root.count)] |
743 c |
|
744 for c in node.children.itervalues() |
|
745 if c.count >= (limit * root.count) |
|
746 ] |
682 if site: |
747 if site: |
683 indent = depth * 2 - 1 |
748 indent = depth * 2 - 1 |
684 filename = '' |
749 filename = '' |
685 function = '' |
750 function = '' |
686 if len(node.children) > 0: |
751 if len(node.children) > 0: |
687 childsite = list(node.children.itervalues())[0].site |
752 childsite = list(node.children.itervalues())[0].site |
688 filename = (childsite.filename() + ':').ljust(15) |
753 filename = (childsite.filename() + ':').ljust(15) |
689 function = childsite.function |
754 function = childsite.function |
690 |
755 |
691 # lots of string formatting |
756 # lots of string formatting |
692 listpattern = ''.ljust(indent) +\ |
757 listpattern = ( |
693 ('\\' if multiple_siblings else '|') +\ |
758 ''.ljust(indent) |
694 ' %4.1f%%' +\ |
759 + ('\\' if multiple_siblings else '|') |
695 (' %5.2fs' % node.count if showtime else '') +\ |
760 + ' %4.1f%%' |
696 ' %s %s' |
761 + (' %5.2fs' % node.count if showtime else '') |
697 liststring = listpattern % (node.count / root.count * 100, |
762 + ' %s %s' |
698 filename, function) |
763 ) |
|
764 liststring = listpattern % ( |
|
765 node.count / root.count * 100, |
|
766 filename, |
|
767 function, |
|
768 ) |
699 codepattern = '%' + ('%d' % (55 - len(liststring))) + 's %d: %s' |
769 codepattern = '%' + ('%d' % (55 - len(liststring))) + 's %d: %s' |
700 codestring = codepattern % ('line', site.lineno, site.getsource(30)) |
770 codestring = codepattern % ('line', site.lineno, site.getsource(30)) |
701 |
771 |
702 finalstring = liststring + codestring |
772 finalstring = liststring + codestring |
703 childrensamples = sum([c.count for c in node.children.itervalues()]) |
773 childrensamples = sum([c.count for c in node.children.itervalues()]) |
718 _write(child, newdepth, len(visiblechildren) > 1) |
788 _write(child, newdepth, len(visiblechildren) > 1) |
719 |
789 |
720 if root.count > 0: |
790 if root.count > 0: |
721 _write(root, 0, False) |
791 _write(root, 0, False) |
722 |
792 |
|
793 |
723 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs): |
794 def write_to_flame(data, fp, scriptpath=None, outputfile=None, **kwargs): |
724 if scriptpath is None: |
795 if scriptpath is None: |
725 scriptpath = encoding.environ['HOME'] + '/flamegraph.pl' |
796 scriptpath = encoding.environ['HOME'] + '/flamegraph.pl' |
726 if not os.path.exists(scriptpath): |
797 if not os.path.exists(scriptpath): |
727 fp.write(b'error: missing %s\n' % scriptpath) |
798 fp.write(b'error: missing %s\n' % scriptpath) |
760 return _pathcache[path] |
834 return _pathcache[path] |
761 hgpath = encoding.__file__.rsplit(os.sep, 2)[0] |
835 hgpath = encoding.__file__.rsplit(os.sep, 2)[0] |
762 for p in [hgpath] + sys.path: |
836 for p in [hgpath] + sys.path: |
763 prefix = p + os.sep |
837 prefix = p + os.sep |
764 if path.startswith(prefix): |
838 if path.startswith(prefix): |
765 path = path[len(prefix):] |
839 path = path[len(prefix) :] |
766 break |
840 break |
767 _pathcache[path] = path |
841 _pathcache[path] = path |
768 return path |
842 return path |
769 |
843 |
|
844 |
770 def write_to_json(data, fp): |
845 def write_to_json(data, fp): |
771 samples = [] |
846 samples = [] |
772 |
847 |
773 for sample in data.samples: |
848 for sample in data.samples: |
774 stack = [] |
849 stack = [] |
775 |
850 |
776 for frame in sample.stack: |
851 for frame in sample.stack: |
777 stack.append( |
852 stack.append( |
778 (pycompat.sysstr(frame.path), |
853 ( |
779 frame.lineno, |
854 pycompat.sysstr(frame.path), |
780 pycompat.sysstr(frame.function))) |
855 frame.lineno, |
|
856 pycompat.sysstr(frame.function), |
|
857 ) |
|
858 ) |
781 |
859 |
782 samples.append((sample.time, stack)) |
860 samples.append((sample.time, stack)) |
783 |
861 |
784 data = json.dumps(samples) |
862 data = json.dumps(samples) |
785 if not isinstance(data, bytes): |
863 if not isinstance(data, bytes): |
786 data = data.encode('utf-8') |
864 data = data.encode('utf-8') |
787 |
865 |
788 fp.write(data) |
866 fp.write(data) |
|
867 |
789 |
868 |
790 def write_to_chrome(data, fp, minthreshold=0.005, maxthreshold=0.999): |
869 def write_to_chrome(data, fp, minthreshold=0.005, maxthreshold=0.999): |
791 samples = [] |
870 samples = [] |
792 laststack = collections.deque() |
871 laststack = collections.deque() |
793 lastseen = collections.deque() |
872 lastseen = collections.deque() |
794 |
873 |
795 # The Chrome tracing format allows us to use a compact stack |
874 # The Chrome tracing format allows us to use a compact stack |
796 # representation to save space. It's fiddly but worth it. |
875 # representation to save space. It's fiddly but worth it. |
797 # We maintain a bijection between stack and ID. |
876 # We maintain a bijection between stack and ID. |
798 stack2id = {} |
877 stack2id = {} |
799 id2stack = [] # will eventually be rendered |
878 id2stack = [] # will eventually be rendered |
800 |
879 |
801 def stackid(stack): |
880 def stackid(stack): |
802 if not stack: |
881 if not stack: |
803 return |
882 return |
804 if stack in stack2id: |
883 if stack in stack2id: |
839 oldtime, oldidx = lastseen.popleft() |
918 oldtime, oldidx = lastseen.popleft() |
840 duration = sample.time - oldtime |
919 duration = sample.time - oldtime |
841 if minthreshold <= duration <= maxthreshold: |
920 if minthreshold <= duration <= maxthreshold: |
842 # ensure no zero-duration events |
921 # ensure no zero-duration events |
843 sampletime = max(oldtime + clamp, sample.time) |
922 sampletime = max(oldtime + clamp, sample.time) |
844 samples.append(dict(ph=r'E', name=oldfunc, cat=oldcat, sf=oldsid, |
923 samples.append( |
845 ts=sampletime*1e6, pid=0)) |
924 dict( |
|
925 ph=r'E', |
|
926 name=oldfunc, |
|
927 cat=oldcat, |
|
928 sf=oldsid, |
|
929 ts=sampletime * 1e6, |
|
930 pid=0, |
|
931 ) |
|
932 ) |
846 else: |
933 else: |
847 blacklist.add(oldidx) |
934 blacklist.add(oldidx) |
848 |
935 |
849 # Much fiddling to synthesize correctly(ish) nested begin/end |
936 # Much fiddling to synthesize correctly(ish) nested begin/end |
850 # events given only stack snapshots. |
937 # events given only stack snapshots. |
851 |
938 |
852 for sample in data.samples: |
939 for sample in data.samples: |
853 stack = tuple(((r'%s:%d' % (simplifypath(pycompat.sysstr(frame.path)), |
940 stack = tuple( |
854 frame.lineno), |
941 ( |
855 pycompat.sysstr(frame.function)) |
942 ( |
856 for frame in sample.stack)) |
943 r'%s:%d' |
|
944 % (simplifypath(pycompat.sysstr(frame.path)), frame.lineno), |
|
945 pycompat.sysstr(frame.function), |
|
946 ) |
|
947 for frame in sample.stack |
|
948 ) |
|
949 ) |
857 qstack = collections.deque(stack) |
950 qstack = collections.deque(stack) |
858 if laststack == qstack: |
951 if laststack == qstack: |
859 continue |
952 continue |
860 while laststack and qstack and laststack[-1] == qstack[-1]: |
953 while laststack and qstack and laststack[-1] == qstack[-1]: |
861 laststack.pop() |
954 laststack.pop() |
865 for f in reversed(qstack): |
958 for f in reversed(qstack): |
866 lastseen.appendleft((sample.time, len(samples))) |
959 lastseen.appendleft((sample.time, len(samples))) |
867 laststack.appendleft(f) |
960 laststack.appendleft(f) |
868 path, name = f |
961 path, name = f |
869 sid = stackid(tuple(laststack)) |
962 sid = stackid(tuple(laststack)) |
870 samples.append(dict(ph=r'B', name=name, cat=path, |
963 samples.append( |
871 ts=sample.time*1e6, sf=sid, pid=0)) |
964 dict( |
|
965 ph=r'B', |
|
966 name=name, |
|
967 cat=path, |
|
968 ts=sample.time * 1e6, |
|
969 sf=sid, |
|
970 pid=0, |
|
971 ) |
|
972 ) |
872 laststack = collections.deque(stack) |
973 laststack = collections.deque(stack) |
873 while laststack: |
974 while laststack: |
874 poplast() |
975 poplast() |
875 events = [sample for idx, sample in enumerate(samples) |
976 events = [ |
876 if idx not in blacklist] |
977 sample for idx, sample in enumerate(samples) if idx not in blacklist |
877 frames = collections.OrderedDict((str(k), v) |
978 ] |
878 for (k,v) in enumerate(id2stack)) |
979 frames = collections.OrderedDict( |
|
980 (str(k), v) for (k, v) in enumerate(id2stack) |
|
981 ) |
879 data = json.dumps(dict(traceEvents=events, stackFrames=frames), indent=1) |
982 data = json.dumps(dict(traceEvents=events, stackFrames=frames), indent=1) |
880 if not isinstance(data, bytes): |
983 if not isinstance(data, bytes): |
881 data = data.encode('utf-8') |
984 data = data.encode('utf-8') |
882 fp.write(data) |
985 fp.write(data) |
883 fp.write('\n') |
986 fp.write('\n') |
884 |
987 |
|
988 |
885 def printusage(): |
989 def printusage(): |
886 print(r""" |
990 print( |
|
991 r""" |
887 The statprof command line allows you to inspect the last profile's results in |
992 The statprof command line allows you to inspect the last profile's results in |
888 the following forms: |
993 the following forms: |
889 |
994 |
890 usage: |
995 usage: |
891 hotpath [-l --limit percent] |
996 hotpath [-l --limit percent] |
898 function [filename:]functionname |
1003 function [filename:]functionname |
899 Shows the callers and callees of a particular function. |
1004 Shows the callers and callees of a particular function. |
900 flame [-s --script-path] [-o --output-file path] |
1005 flame [-s --script-path] [-o --output-file path] |
901 Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg) |
1006 Writes out a flamegraph to output-file (defaults to ~/flamegraph.svg) |
902 Requires that ~/flamegraph.pl exist. |
1007 Requires that ~/flamegraph.pl exist. |
903 (Specify alternate script path with --script-path.)""") |
1008 (Specify alternate script path with --script-path.)""" |
|
1009 ) |
|
1010 |
904 |
1011 |
905 def main(argv=None): |
1012 def main(argv=None): |
906 if argv is None: |
1013 if argv is None: |
907 argv = sys.argv |
1014 argv = sys.argv |
908 |
1015 |