|  | #!/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 ReportLib | 
|  | 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 = ReportLib() | 
|  |  | 
|  | if include_addrs: | 
|  | lib.ShowIpForUnknownSymbol() | 
|  | if symfs_dir is not None: | 
|  | lib.SetSymfs(symfs_dir) | 
|  | if record_file is not None: | 
|  | lib.SetRecordFile(record_file) | 
|  | 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() |