#!/usr/bin/env python3 # # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """stackcollapse.py: convert perf.data to Brendan Gregg's "Folded Stacks" format, which can be read by https://github.com/brendangregg/FlameGraph, and many other tools. Example: ./app_profiler.py ./stackcollapse.py | ~/FlameGraph/flamegraph.pl --color=java --countname=ns > flamegraph.svg """ from collections import defaultdict from simpleperf_report_lib import GetReportLib from simpleperf_utils import BaseArgumentParser, flatten_arg_list, ReportLibOptions from typing import DefaultDict, List, Optional, Set import logging import sys def collapse_stacks( record_file: str, symfs_dir: str, kallsyms_file: str, event_filter: str, include_pid: bool, include_tid: bool, annotate_kernel: bool, annotate_jit: bool, include_addrs: bool, report_lib_options: ReportLibOptions): """read record_file, aggregate per-stack and print totals per-stack""" lib = GetReportLib(record_file) if include_addrs: lib.ShowIpForUnknownSymbol() if symfs_dir is not None: lib.SetSymfs(symfs_dir) if kallsyms_file is not None: lib.SetKallsymsFile(kallsyms_file) lib.SetReportOptions(report_lib_options) stacks: DefaultDict[str, int] = defaultdict(int) event_defaulted = False event_warning_shown = False while True: sample = lib.GetNextSample() if sample is None: lib.Close() break event = lib.GetEventOfCurrentSample() symbol = lib.GetSymbolOfCurrentSample() callchain = lib.GetCallChainOfCurrentSample() if not event_filter: event_filter = event.name event_defaulted = True elif event.name != event_filter: if event_defaulted and not event_warning_shown: logging.warning( 'Input has multiple event types. Filtering for the first event type seen: %s' % event_filter) event_warning_shown = True continue stack = [] for i in range(callchain.nr): entry = callchain.entries[i] func = entry.symbol.symbol_name if annotate_kernel and "kallsyms" in entry.symbol.dso_name or ".ko" in entry.symbol.dso_name: func += '_[k]' # kernel if annotate_jit and entry.symbol.dso_name == "[JIT app cache]": func += '_[j]' # jit stack.append(func) if include_tid: stack.append("%s-%d/%d" % (sample.thread_comm, sample.pid, sample.tid)) elif include_pid: stack.append("%s-%d" % (sample.thread_comm, sample.pid)) else: stack.append(sample.thread_comm) stack.reverse() stacks[";".join(stack)] += sample.period for k in sorted(stacks.keys()): print("%s %d" % (k, stacks[k])) def main(): parser = BaseArgumentParser(description=__doc__) parser.add_argument('--symfs', help='Set the path to find binaries with symbols and debug info.') parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.') parser.add_argument('-i', '--record_file', nargs='?', default='perf.data', help='Default is perf.data.') parser.add_argument('--pid', action='store_true', help='Include PID with process names') parser.add_argument('--tid', action='store_true', help='Include TID and PID with process names') parser.add_argument('--kernel', action='store_true', help='Annotate kernel functions with a _[k]') parser.add_argument('--jit', action='store_true', help='Annotate JIT functions with a _[j]') parser.add_argument('--addrs', action='store_true', help='include raw addresses where symbols can\'t be found') sample_filter_group = parser.add_argument_group('Sample filter options') sample_filter_group.add_argument('--event-filter', nargs='?', default='', help='Event type filter e.g. "cpu-cycles" or "instructions"') parser.add_report_lib_options(sample_filter_group=sample_filter_group, sample_filter_with_pid_shortcut=False) args = parser.parse_args() collapse_stacks( record_file=args.record_file, symfs_dir=args.symfs, kallsyms_file=args.kallsyms, event_filter=args.event_filter, include_pid=args.pid, include_tid=args.tid, annotate_kernel=args.kernel, annotate_jit=args.jit, include_addrs=args.addrs, report_lib_options=args.report_lib_options) if __name__ == '__main__': main()