1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17 18class CallSite(object): 19 20 def __init__(self, method, dso): 21 self.method = method 22 self.dso = dso 23 24 25class Thread(object): 26 27 def __init__(self, tid, pid): 28 self.tid = tid 29 self.pid = pid 30 self.name = "" 31 self.samples = [] 32 self.flamegraph = FlameGraphCallSite("root", "", 0) 33 self.num_samples = 0 34 self.num_events = 0 35 36 def add_callchain(self, callchain, symbol, sample): 37 self.name = sample.thread_comm 38 self.num_samples += 1 39 self.num_events += sample.period 40 chain = [] 41 for j in range(callchain.nr): 42 entry = callchain.entries[callchain.nr - j - 1] 43 if entry.ip == 0: 44 continue 45 chain.append(CallSite(entry.symbol.symbol_name, entry.symbol.dso_name)) 46 47 chain.append(CallSite(symbol.symbol_name, symbol.dso_name)) 48 self.flamegraph.add_callchain(chain, sample.period) 49 50 51class Process(object): 52 53 def __init__(self, name, pid): 54 self.name = name 55 self.pid = pid 56 self.threads = {} 57 self.cmd = "" 58 self.props = {} 59 # num_samples is the count of samples recorded in the profiling file. 60 self.num_samples = 0 61 # num_events is the count of events contained in all samples. Each sample contains a 62 # count of events happened since last sample. If we use cpu-cycles event, the count 63 # shows how many cpu-cycles have happened during recording. 64 self.num_events = 0 65 66 def get_thread(self, tid, pid): 67 thread = self.threads.get(tid) 68 if thread is None: 69 thread = self.threads[tid] = Thread(tid, pid) 70 return thread 71 72 def add_sample(self, sample, symbol, callchain): 73 thread = self.get_thread(sample.tid, sample.pid) 74 thread.add_callchain(callchain, symbol, sample) 75 self.num_samples += 1 76 # sample.period is the count of events happened since last sample. 77 self.num_events += sample.period 78 79 80class FlameGraphCallSite(object): 81 82 callsite_counter = 0 83 @classmethod 84 def _get_next_callsite_id(cls): 85 cls.callsite_counter += 1 86 return cls.callsite_counter 87 88 def __init__(self, method, dso, callsite_id): 89 # map from (dso, method) to FlameGraphCallSite. Used to speed up add_callchain(). 90 self.child_dict = {} 91 self.children = [] 92 self.method = method 93 self.dso = dso 94 self.num_events = 0 95 self.offset = 0 # Offset allows position nodes in different branches. 96 self.id = callsite_id 97 98 def weight(self): 99 return float(self.num_events) 100 101 def add_callchain(self, chain, num_events): 102 self.num_events += num_events 103 current = self 104 for callsite in chain: 105 current = current.get_child(callsite) 106 current.num_events += num_events 107 108 def get_child(self, callsite): 109 key = (callsite.dso, callsite.method) 110 child = self.child_dict.get(key) 111 if child is None: 112 child = self.child_dict[key] = FlameGraphCallSite(callsite.method, callsite.dso, 113 self._get_next_callsite_id()) 114 return child 115 116 def trim_callchain(self, min_num_events, max_depth, depth=0): 117 """ Remove call sites with num_events < min_num_events in the subtree. 118 Remaining children are collected in a list. 119 """ 120 if depth <= max_depth: 121 for key in self.child_dict: 122 child = self.child_dict[key] 123 if child.num_events >= min_num_events: 124 child.trim_callchain(min_num_events, max_depth, depth + 1) 125 self.children.append(child) 126 # Relese child_dict since it will not be used. 127 self.child_dict = None 128 129 def get_max_depth(self): 130 return max([c.get_max_depth() for c in self.children]) + 1 if self.children else 1 131 132 def generate_offset(self, start_offset): 133 self.offset = start_offset 134 child_offset = start_offset 135 for child in self.children: 136 child_offset = child.generate_offset(child_offset) 137 return self.offset + self.num_events 138