summaryrefslogtreecommitdiff
path: root/simpleperf/scripts/stackcollapse.py
blob: 94ffaec0edb7e9a646dcafd942687c2e5b86376f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/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()