16745d8eaSPaolo Bonzini# stackcollapse.py - format perf samples with one line per distinct call stack
2*b2441318SGreg Kroah-Hartman# SPDX-License-Identifier: GPL-2.0
36745d8eaSPaolo Bonzini#
46745d8eaSPaolo Bonzini# This script's output has two space-separated fields.  The first is a semicolon
56745d8eaSPaolo Bonzini# separated stack including the program name (from the "comm" field) and the
66745d8eaSPaolo Bonzini# function names from the call stack.  The second is a count:
76745d8eaSPaolo Bonzini#
86745d8eaSPaolo Bonzini#  swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 2
96745d8eaSPaolo Bonzini#
106745d8eaSPaolo Bonzini# The file is sorted according to the first field.
116745d8eaSPaolo Bonzini#
126745d8eaSPaolo Bonzini# Input may be created and processed using:
136745d8eaSPaolo Bonzini#
146745d8eaSPaolo Bonzini#  perf record -a -g -F 99 sleep 60
156745d8eaSPaolo Bonzini#  perf script report stackcollapse > out.stacks-folded
166745d8eaSPaolo Bonzini#
176745d8eaSPaolo Bonzini# (perf script record stackcollapse works too).
186745d8eaSPaolo Bonzini#
196745d8eaSPaolo Bonzini# Written by Paolo Bonzini <[email protected]>
206745d8eaSPaolo Bonzini# Based on Brendan Gregg's stackcollapse-perf.pl script.
216745d8eaSPaolo Bonzini
226745d8eaSPaolo Bonziniimport os
236745d8eaSPaolo Bonziniimport sys
246745d8eaSPaolo Bonzinifrom collections import defaultdict
256745d8eaSPaolo Bonzinifrom optparse import OptionParser, make_option
266745d8eaSPaolo Bonzini
276745d8eaSPaolo Bonzinisys.path.append(os.environ['PERF_EXEC_PATH'] + \
286745d8eaSPaolo Bonzini                '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
296745d8eaSPaolo Bonzini
306745d8eaSPaolo Bonzinifrom perf_trace_context import *
316745d8eaSPaolo Bonzinifrom Core import *
326745d8eaSPaolo Bonzinifrom EventClass import *
336745d8eaSPaolo Bonzini
346745d8eaSPaolo Bonzini# command line parsing
356745d8eaSPaolo Bonzini
366745d8eaSPaolo Bonzinioption_list = [
376745d8eaSPaolo Bonzini    # formatting options for the bottom entry of the stack
386745d8eaSPaolo Bonzini    make_option("--include-tid", dest="include_tid",
396745d8eaSPaolo Bonzini                 action="store_true", default=False,
406745d8eaSPaolo Bonzini                 help="include thread id in stack"),
416745d8eaSPaolo Bonzini    make_option("--include-pid", dest="include_pid",
426745d8eaSPaolo Bonzini                 action="store_true", default=False,
436745d8eaSPaolo Bonzini                 help="include process id in stack"),
446745d8eaSPaolo Bonzini    make_option("--no-comm", dest="include_comm",
456745d8eaSPaolo Bonzini                 action="store_false", default=True,
466745d8eaSPaolo Bonzini                 help="do not separate stacks according to comm"),
476745d8eaSPaolo Bonzini    make_option("--tidy-java", dest="tidy_java",
486745d8eaSPaolo Bonzini                 action="store_true", default=False,
496745d8eaSPaolo Bonzini                 help="beautify Java signatures"),
506745d8eaSPaolo Bonzini    make_option("--kernel", dest="annotate_kernel",
516745d8eaSPaolo Bonzini                 action="store_true", default=False,
526745d8eaSPaolo Bonzini                 help="annotate kernel functions with _[k]")
536745d8eaSPaolo Bonzini]
546745d8eaSPaolo Bonzini
556745d8eaSPaolo Bonziniparser = OptionParser(option_list=option_list)
566745d8eaSPaolo Bonzini(opts, args) = parser.parse_args()
576745d8eaSPaolo Bonzini
586745d8eaSPaolo Bonziniif len(args) != 0:
596745d8eaSPaolo Bonzini    parser.error("unexpected command line argument")
606745d8eaSPaolo Bonziniif opts.include_tid and not opts.include_comm:
616745d8eaSPaolo Bonzini    parser.error("requesting tid but not comm is invalid")
626745d8eaSPaolo Bonziniif opts.include_pid and not opts.include_comm:
636745d8eaSPaolo Bonzini    parser.error("requesting pid but not comm is invalid")
646745d8eaSPaolo Bonzini
656745d8eaSPaolo Bonzini# event handlers
666745d8eaSPaolo Bonzini
676745d8eaSPaolo Bonzinilines = defaultdict(lambda: 0)
686745d8eaSPaolo Bonzini
696745d8eaSPaolo Bonzinidef process_event(param_dict):
706745d8eaSPaolo Bonzini    def tidy_function_name(sym, dso):
716745d8eaSPaolo Bonzini        if sym is None:
726745d8eaSPaolo Bonzini            sym = '[unknown]'
736745d8eaSPaolo Bonzini
746745d8eaSPaolo Bonzini        sym = sym.replace(';', ':')
756745d8eaSPaolo Bonzini        if opts.tidy_java:
766745d8eaSPaolo Bonzini            # the original stackcollapse-perf.pl script gives the
776745d8eaSPaolo Bonzini            # example of converting this:
786745d8eaSPaolo Bonzini            #    Lorg/mozilla/javascript/MemberBox;.<init>(Ljava/lang/reflect/Method;)V
796745d8eaSPaolo Bonzini            # to this:
806745d8eaSPaolo Bonzini            #    org/mozilla/javascript/MemberBox:.init
816745d8eaSPaolo Bonzini            sym = sym.replace('<', '')
826745d8eaSPaolo Bonzini            sym = sym.replace('>', '')
836745d8eaSPaolo Bonzini            if sym[0] == 'L' and sym.find('/'):
846745d8eaSPaolo Bonzini                sym = sym[1:]
856745d8eaSPaolo Bonzini            try:
866745d8eaSPaolo Bonzini                sym = sym[:sym.index('(')]
876745d8eaSPaolo Bonzini            except ValueError:
886745d8eaSPaolo Bonzini                pass
896745d8eaSPaolo Bonzini
906745d8eaSPaolo Bonzini        if opts.annotate_kernel and dso == '[kernel.kallsyms]':
916745d8eaSPaolo Bonzini            return sym + '_[k]'
926745d8eaSPaolo Bonzini        else:
936745d8eaSPaolo Bonzini            return sym
946745d8eaSPaolo Bonzini
956745d8eaSPaolo Bonzini    stack = list()
966745d8eaSPaolo Bonzini    if 'callchain' in param_dict:
976745d8eaSPaolo Bonzini        for entry in param_dict['callchain']:
986745d8eaSPaolo Bonzini            entry.setdefault('sym', dict())
996745d8eaSPaolo Bonzini            entry['sym'].setdefault('name', None)
1006745d8eaSPaolo Bonzini            entry.setdefault('dso', None)
1016745d8eaSPaolo Bonzini            stack.append(tidy_function_name(entry['sym']['name'],
1026745d8eaSPaolo Bonzini                                            entry['dso']))
1036745d8eaSPaolo Bonzini    else:
1046745d8eaSPaolo Bonzini        param_dict.setdefault('symbol', None)
1056745d8eaSPaolo Bonzini        param_dict.setdefault('dso', None)
1066745d8eaSPaolo Bonzini        stack.append(tidy_function_name(param_dict['symbol'],
1076745d8eaSPaolo Bonzini                                        param_dict['dso']))
1086745d8eaSPaolo Bonzini
1096745d8eaSPaolo Bonzini    if opts.include_comm:
1106745d8eaSPaolo Bonzini        comm = param_dict["comm"].replace(' ', '_')
1116745d8eaSPaolo Bonzini        sep = "-"
1126745d8eaSPaolo Bonzini        if opts.include_pid:
1136745d8eaSPaolo Bonzini            comm = comm + sep + str(param_dict['sample']['pid'])
1146745d8eaSPaolo Bonzini            sep = "/"
1156745d8eaSPaolo Bonzini        if opts.include_tid:
1166745d8eaSPaolo Bonzini            comm = comm + sep + str(param_dict['sample']['tid'])
1176745d8eaSPaolo Bonzini        stack.append(comm)
1186745d8eaSPaolo Bonzini
1196745d8eaSPaolo Bonzini    stack_string = ';'.join(reversed(stack))
1206745d8eaSPaolo Bonzini    lines[stack_string] = lines[stack_string] + 1
1216745d8eaSPaolo Bonzini
1226745d8eaSPaolo Bonzinidef trace_end():
1236745d8eaSPaolo Bonzini    list = lines.keys()
1246745d8eaSPaolo Bonzini    list.sort()
1256745d8eaSPaolo Bonzini    for stack in list:
1266745d8eaSPaolo Bonzini        print "%s %d" % (stack, lines[stack])
127