summaryrefslogtreecommitdiff
path: root/simpleperf/scripts/inferno/data_types.py
blob: 35ef4c0b681642d1fc0c3056c7a1111bc7d2d7ba (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
137
#
# Copyright (C) 2016 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.
#


class CallSite(object):

    def __init__(self, method, dso):
        self.method = method
        self.dso = dso


class Thread(object):

    def __init__(self, tid, pid):
        self.tid = tid
        self.pid = pid
        self.name = ""
        self.samples = []
        self.flamegraph = FlameGraphCallSite("root", "", 0)
        self.num_samples = 0
        self.num_events = 0

    def add_callchain(self, callchain, symbol, sample):
        self.name = sample.thread_comm
        self.num_samples += 1
        self.num_events += sample.period
        chain = []
        for j in range(callchain.nr):
            entry = callchain.entries[callchain.nr - j - 1]
            if entry.ip == 0:
                continue
            chain.append(CallSite(entry.symbol.symbol_name, entry.symbol.dso_name))

        chain.append(CallSite(symbol.symbol_name, symbol.dso_name))
        self.flamegraph.add_callchain(chain, sample.period)


class Process(object):

    def __init__(self, name, pid):
        self.name = name
        self.pid = pid
        self.threads = {}
        self.cmd = ""
        self.props = {}
        # num_samples is the count of samples recorded in the profiling file.
        self.num_samples = 0
        # num_events is the count of events contained in all samples. Each sample contains a
        # count of events happened since last sample. If we use cpu-cycles event, the count
        # shows how many cpu-cycles have happened during recording.
        self.num_events = 0

    def get_thread(self, tid, pid):
        thread = self.threads.get(tid)
        if thread is None:
            thread = self.threads[tid] = Thread(tid, pid)
        return thread

    def add_sample(self, sample, symbol, callchain):
        thread = self.get_thread(sample.tid, sample.pid)
        thread.add_callchain(callchain, symbol, sample)
        self.num_samples += 1
        # sample.period is the count of events happened since last sample.
        self.num_events += sample.period


class FlameGraphCallSite(object):

    callsite_counter = 0
    @classmethod
    def _get_next_callsite_id(cls):
        cls.callsite_counter += 1
        return cls.callsite_counter

    def __init__(self, method, dso, callsite_id):
        # map from (dso, method) to FlameGraphCallSite. Used to speed up add_callchain().
        self.child_dict = {}
        self.children = []
        self.method = method
        self.dso = dso
        self.num_events = 0
        self.offset = 0  # Offset allows position nodes in different branches.
        self.id = callsite_id

    def weight(self):
        return float(self.num_events)

    def add_callchain(self, chain, num_events):
        self.num_events += num_events
        current = self
        for callsite in chain:
            current = current.get_child(callsite)
            current.num_events += num_events

    def get_child(self, callsite):
        key = (callsite.dso, callsite.method)
        child = self.child_dict.get(key)
        if child is None:
            child = self.child_dict[key] = FlameGraphCallSite(callsite.method, callsite.dso,
                                                              self._get_next_callsite_id())
        return child

    def trim_callchain(self, min_num_events, max_depth, depth=0):
        """ Remove call sites with num_events < min_num_events in the subtree.
            Remaining children are collected in a list.
        """
        if depth <= max_depth:
            for key in self.child_dict:
                child = self.child_dict[key]
                if child.num_events >= min_num_events:
                    child.trim_callchain(min_num_events, max_depth, depth + 1)
                    self.children.append(child)
        # Relese child_dict since it will not be used.
        self.child_dict = None

    def get_max_depth(self):
        return max([c.get_max_depth() for c in self.children]) + 1 if self.children else 1

    def generate_offset(self, start_offset):
        self.offset = start_offset
        child_offset = start_offset
        for child in self.children:
            child_offset = child.generate_offset(child_offset)
        return self.offset + self.num_events