1#!/usr/bin/env python3 2# 3# Copyright (C) 2021 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18"""stackcollapse.py: convert perf.data to Brendan Gregg's "Folded Stacks" format, 19 which can be read by https://github.com/brendangregg/FlameGraph, and many 20 other tools. 21 22 Example: 23 ./app_profiler.py 24 ./stackcollapse.py | ~/FlameGraph/flamegraph.pl --color=java --countname=ns > flamegraph.svg 25""" 26 27from collections import defaultdict 28from simpleperf_report_lib import GetReportLib 29from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions 30from typing import DefaultDict, List, Optional, Set 31 32import logging 33import sys 34 35 36def collapse_stacks( 37 record_file: str, 38 symfs_dir: str, 39 kallsyms_file: str, 40 event_filter: str, 41 include_pid: bool, 42 include_tid: bool, 43 annotate_kernel: bool, 44 annotate_jit: bool, 45 include_addrs: bool, 46 report_lib_options: ReportLibOptions): 47 """read record_file, aggregate per-stack and print totals per-stack""" 48 lib = GetReportLib(record_file) 49 50 if include_addrs: 51 lib.ShowIpForUnknownSymbol() 52 if symfs_dir is not None: 53 lib.SetSymfs(symfs_dir) 54 if kallsyms_file is not None: 55 lib.SetKallsymsFile(kallsyms_file) 56 lib.SetReportOptions(report_lib_options) 57 58 stacks: DefaultDict[str, int] = defaultdict(int) 59 event_defaulted = False 60 event_warning_shown = False 61 while True: 62 sample = lib.GetNextSample() 63 if sample is None: 64 lib.Close() 65 break 66 event = lib.GetEventOfCurrentSample() 67 symbol = lib.GetSymbolOfCurrentSample() 68 callchain = lib.GetCallChainOfCurrentSample() 69 if not event_filter: 70 event_filter = event.name 71 event_defaulted = True 72 elif event.name != event_filter: 73 if event_defaulted and not event_warning_shown: 74 logging.warning( 75 'Input has multiple event types. Filtering for the first event type seen: %s' % 76 event_filter) 77 event_warning_shown = True 78 continue 79 80 stack = [] 81 for i in range(callchain.nr): 82 entry = callchain.entries[i] 83 func = entry.symbol.symbol_name 84 if annotate_kernel and "kallsyms" in entry.symbol.dso_name or ".ko" in entry.symbol.dso_name: 85 func += '_[k]' # kernel 86 if annotate_jit and entry.symbol.dso_name == "[JIT app cache]": 87 func += '_[j]' # jit 88 stack.append(func) 89 if include_tid: 90 stack.append("%s-%d/%d" % (sample.thread_comm, sample.pid, sample.tid)) 91 elif include_pid: 92 stack.append("%s-%d" % (sample.thread_comm, sample.pid)) 93 else: 94 stack.append(sample.thread_comm) 95 stack.reverse() 96 stacks[";".join(stack)] += sample.period 97 98 for k in sorted(stacks.keys()): 99 print("%s %d" % (k, stacks[k])) 100 101 102def main(): 103 parser = BaseArgumentParser(description=__doc__) 104 parser.add_argument('--symfs', 105 help='Set the path to find binaries with symbols and debug info.') 106 parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.') 107 parser.add_argument('-i', '--record_file', nargs='?', default='perf.data', 108 help='Default is perf.data.') 109 parser.add_argument('--pid', action='store_true', help='Include PID with process names') 110 parser.add_argument('--tid', action='store_true', help='Include TID and PID with process names') 111 parser.add_argument('--kernel', action='store_true', 112 help='Annotate kernel functions with a _[k]') 113 parser.add_argument('--jit', action='store_true', help='Annotate JIT functions with a _[j]') 114 parser.add_argument('--addrs', action='store_true', 115 help='include raw addresses where symbols can\'t be found') 116 sample_filter_group = parser.add_argument_group('Sample filter options') 117 sample_filter_group.add_argument('--event-filter', nargs='?', default='', 118 help='Event type filter e.g. "cpu-cycles" or "instructions"') 119 parser.add_report_lib_options(sample_filter_group=sample_filter_group, 120 sample_filter_with_pid_shortcut=False) 121 args = parser.parse_args() 122 collapse_stacks( 123 record_file=args.record_file, 124 symfs_dir=args.symfs, 125 kallsyms_file=args.kallsyms, 126 event_filter=args.event_filter, 127 include_pid=args.pid, 128 include_tid=args.tid, 129 annotate_kernel=args.kernel, 130 annotate_jit=args.jit, 131 include_addrs=args.addrs, 132 report_lib_options=args.report_lib_options) 133 134 135if __name__ == '__main__': 136 main() 137