blob: 35ef4c0b681642d1fc0c3056c7a1111bc7d2d7ba [file] [log] [blame]
#
# 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