1*795d594fSAndroid Build Coastguard Worker#!/usr/bin/python3 2*795d594fSAndroid Build Coastguard Worker# 3*795d594fSAndroid Build Coastguard Worker# Copyright 2019, The Android Open Source Project 4*795d594fSAndroid Build Coastguard Worker# 5*795d594fSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*795d594fSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*795d594fSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*795d594fSAndroid Build Coastguard Worker# 9*795d594fSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*795d594fSAndroid Build Coastguard Worker# 11*795d594fSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*795d594fSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*795d594fSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*795d594fSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*795d594fSAndroid Build Coastguard Worker# limitations under the License. 16*795d594fSAndroid Build Coastguard Worker 17*795d594fSAndroid Build Coastguard Worker""" 18*795d594fSAndroid Build Coastguard WorkerUsage: mkflame.py <jvmti_trace_file> 19*795d594fSAndroid Build Coastguard Worker""" 20*795d594fSAndroid Build Coastguard Worker 21*795d594fSAndroid Build Coastguard Workerimport argparse 22*795d594fSAndroid Build Coastguard Workerimport sys 23*795d594fSAndroid Build Coastguard Worker 24*795d594fSAndroid Build Coastguard Workerclass TraceCollection: 25*795d594fSAndroid Build Coastguard Worker def __init__(self, args): 26*795d594fSAndroid Build Coastguard Worker self.args = args 27*795d594fSAndroid Build Coastguard Worker # A table indexed by number and containing the definition for that number. 28*795d594fSAndroid Build Coastguard Worker self.definitions = {} 29*795d594fSAndroid Build Coastguard Worker # The "weight" of a stack trace, either 1 for counting or the size of the allocation. 30*795d594fSAndroid Build Coastguard Worker self.weights = {} 31*795d594fSAndroid Build Coastguard Worker # The count for each individual allocation. 32*795d594fSAndroid Build Coastguard Worker self.allocation_count = {} 33*795d594fSAndroid Build Coastguard Worker 34*795d594fSAndroid Build Coastguard Worker def definition(self, index): 35*795d594fSAndroid Build Coastguard Worker """ 36*795d594fSAndroid Build Coastguard Worker Returns the definition for "index". 37*795d594fSAndroid Build Coastguard Worker """ 38*795d594fSAndroid Build Coastguard Worker return self.definitions[index] 39*795d594fSAndroid Build Coastguard Worker 40*795d594fSAndroid Build Coastguard Worker def set_definition(self, index, definition): 41*795d594fSAndroid Build Coastguard Worker """ 42*795d594fSAndroid Build Coastguard Worker Sets the definition for "index". 43*795d594fSAndroid Build Coastguard Worker """ 44*795d594fSAndroid Build Coastguard Worker self.definitions[index] = definition 45*795d594fSAndroid Build Coastguard Worker 46*795d594fSAndroid Build Coastguard Worker def weight(self, index): 47*795d594fSAndroid Build Coastguard Worker """ 48*795d594fSAndroid Build Coastguard Worker Returns the weight for "index". 49*795d594fSAndroid Build Coastguard Worker """ 50*795d594fSAndroid Build Coastguard Worker return self.weights[index] 51*795d594fSAndroid Build Coastguard Worker 52*795d594fSAndroid Build Coastguard Worker def set_weight(self, index, weight): 53*795d594fSAndroid Build Coastguard Worker """ 54*795d594fSAndroid Build Coastguard Worker Sets the weight for "index". 55*795d594fSAndroid Build Coastguard Worker """ 56*795d594fSAndroid Build Coastguard Worker self.weights[index] = weight 57*795d594fSAndroid Build Coastguard Worker 58*795d594fSAndroid Build Coastguard Worker def read_file(self, filename): 59*795d594fSAndroid Build Coastguard Worker """ 60*795d594fSAndroid Build Coastguard Worker Reads a file into a DefinitionTable. 61*795d594fSAndroid Build Coastguard Worker """ 62*795d594fSAndroid Build Coastguard Worker def process_definition(line): 63*795d594fSAndroid Build Coastguard Worker """ 64*795d594fSAndroid Build Coastguard Worker Adds line to the list of definitions in table. 65*795d594fSAndroid Build Coastguard Worker """ 66*795d594fSAndroid Build Coastguard Worker def expand_stack_trace(definition): 67*795d594fSAndroid Build Coastguard Worker """ 68*795d594fSAndroid Build Coastguard Worker Converts a semicolon-separated list of numbers into the text stack trace. 69*795d594fSAndroid Build Coastguard Worker """ 70*795d594fSAndroid Build Coastguard Worker def get_allocation_thread(thread_type_size): 71*795d594fSAndroid Build Coastguard Worker """ 72*795d594fSAndroid Build Coastguard Worker Returns the thread of an allocation from the thread/type/size record. 73*795d594fSAndroid Build Coastguard Worker """ 74*795d594fSAndroid Build Coastguard Worker THREAD_STRING = "thread[" 75*795d594fSAndroid Build Coastguard Worker THREAD_STRING_LEN = len(THREAD_STRING) 76*795d594fSAndroid Build Coastguard Worker thread_string = thread_type_size[thread_type_size.find(THREAD_STRING) + 77*795d594fSAndroid Build Coastguard Worker THREAD_STRING_LEN:] 78*795d594fSAndroid Build Coastguard Worker return thread_string[:thread_string.find("]")] 79*795d594fSAndroid Build Coastguard Worker 80*795d594fSAndroid Build Coastguard Worker def get_allocation_type(thread_type_size): 81*795d594fSAndroid Build Coastguard Worker """ 82*795d594fSAndroid Build Coastguard Worker Returns the type of an allocation from the thread/type/size record. 83*795d594fSAndroid Build Coastguard Worker """ 84*795d594fSAndroid Build Coastguard Worker TYPE_STRING = "jclass[" 85*795d594fSAndroid Build Coastguard Worker TYPE_STRING_LEN = len(TYPE_STRING) 86*795d594fSAndroid Build Coastguard Worker type_string = thread_type_size[thread_type_size.find(TYPE_STRING) + TYPE_STRING_LEN:] 87*795d594fSAndroid Build Coastguard Worker return type_string[:type_string.find(" ")] 88*795d594fSAndroid Build Coastguard Worker 89*795d594fSAndroid Build Coastguard Worker def get_allocation_size(thread_type_size): 90*795d594fSAndroid Build Coastguard Worker """ 91*795d594fSAndroid Build Coastguard Worker Returns the size of an allocation from the thread/type/size record. 92*795d594fSAndroid Build Coastguard Worker """ 93*795d594fSAndroid Build Coastguard Worker SIZE_STRING = "size[" 94*795d594fSAndroid Build Coastguard Worker SIZE_STRING_LEN = len(SIZE_STRING) 95*795d594fSAndroid Build Coastguard Worker size_string = thread_type_size[thread_type_size.find(SIZE_STRING) + SIZE_STRING_LEN:] 96*795d594fSAndroid Build Coastguard Worker size_string = size_string[:size_string.find(",")] 97*795d594fSAndroid Build Coastguard Worker return int(size_string) 98*795d594fSAndroid Build Coastguard Worker 99*795d594fSAndroid Build Coastguard Worker def get_top_and_weight(index): 100*795d594fSAndroid Build Coastguard Worker thread_type_size = self.definition(int(tokens[0])) 101*795d594fSAndroid Build Coastguard Worker size = get_allocation_size(thread_type_size) 102*795d594fSAndroid Build Coastguard Worker if self.args.type_only: 103*795d594fSAndroid Build Coastguard Worker thread_type_size = get_allocation_type(thread_type_size) 104*795d594fSAndroid Build Coastguard Worker elif self.args.thread_only: 105*795d594fSAndroid Build Coastguard Worker thread_type_size = get_allocation_thread(thread_type_size) 106*795d594fSAndroid Build Coastguard Worker return (thread_type_size, size) 107*795d594fSAndroid Build Coastguard Worker 108*795d594fSAndroid Build Coastguard Worker tokens = definition.split(";") 109*795d594fSAndroid Build Coastguard Worker # The first element (base) of the stack trace is the thread/type/size. 110*795d594fSAndroid Build Coastguard Worker # Get the weight (either 1 or the number of bytes allocated). 111*795d594fSAndroid Build Coastguard Worker (thread_type_size, weight) = get_top_and_weight(int(tokens[0])) 112*795d594fSAndroid Build Coastguard Worker self.set_weight(index, weight) 113*795d594fSAndroid Build Coastguard Worker # Remove the thread/type/size from the base of the stack trace. 114*795d594fSAndroid Build Coastguard Worker del tokens[0] 115*795d594fSAndroid Build Coastguard Worker # Build the stack trace list. 116*795d594fSAndroid Build Coastguard Worker expanded_definition = "" 117*795d594fSAndroid Build Coastguard Worker for i in range(len(tokens)): 118*795d594fSAndroid Build Coastguard Worker if self.args.depth_limit > 0 and i >= self.args.depth_limit: 119*795d594fSAndroid Build Coastguard Worker break 120*795d594fSAndroid Build Coastguard Worker token = tokens[i] 121*795d594fSAndroid Build Coastguard Worker # Replace semicolons by colons in the method entry signatures. 122*795d594fSAndroid Build Coastguard Worker method = self.definition(int(token)).replace(";", ":") 123*795d594fSAndroid Build Coastguard Worker if len(expanded_definition) > 0: 124*795d594fSAndroid Build Coastguard Worker expanded_definition += ";" 125*795d594fSAndroid Build Coastguard Worker expanded_definition += method 126*795d594fSAndroid Build Coastguard Worker if not self.args.ignore_type: 127*795d594fSAndroid Build Coastguard Worker # Add the thread/type/size as the top-most stack frame. 128*795d594fSAndroid Build Coastguard Worker if len(expanded_definition) > 0: 129*795d594fSAndroid Build Coastguard Worker expanded_definition += ";" 130*795d594fSAndroid Build Coastguard Worker expanded_definition += thread_type_size.replace(";", ":") 131*795d594fSAndroid Build Coastguard Worker if self.args.reverse_stack: 132*795d594fSAndroid Build Coastguard Worker def_list = expanded_definition.split(";") 133*795d594fSAndroid Build Coastguard Worker expanded_definition = ";".join(def_list[::-1]) 134*795d594fSAndroid Build Coastguard Worker return expanded_definition 135*795d594fSAndroid Build Coastguard Worker 136*795d594fSAndroid Build Coastguard Worker # If the line contains a comma, it is of the form [+=]index,definition, 137*795d594fSAndroid Build Coastguard Worker # where index is a string containing an integer, and definition is the 138*795d594fSAndroid Build Coastguard Worker # value represented by the integer whenever it is used later. 139*795d594fSAndroid Build Coastguard Worker # * Lines starting with + are either a thread/type/size record or a single 140*795d594fSAndroid Build Coastguard Worker # stack frame. These are simply interned in the table. 141*795d594fSAndroid Build Coastguard Worker # * Those starting with = are stack traces, and contain a sequence of 142*795d594fSAndroid Build Coastguard Worker # numbers separated by semicolon. These are "expanded" and then interned. 143*795d594fSAndroid Build Coastguard Worker comma_pos = line.find(",") 144*795d594fSAndroid Build Coastguard Worker index = int(line[1:comma_pos]) 145*795d594fSAndroid Build Coastguard Worker definition = line[comma_pos+1:] 146*795d594fSAndroid Build Coastguard Worker if line[0:1] == "=": 147*795d594fSAndroid Build Coastguard Worker definition = expand_stack_trace(definition) 148*795d594fSAndroid Build Coastguard Worker # Intern the definition in the table. 149*795d594fSAndroid Build Coastguard Worker #if len(definition) == 0: 150*795d594fSAndroid Build Coastguard Worker # Zero length samples are errors and are discarded. 151*795d594fSAndroid Build Coastguard Worker #print("ERROR: definition for " + str(index) + " is empty") 152*795d594fSAndroid Build Coastguard Worker #return 153*795d594fSAndroid Build Coastguard Worker self.set_definition(index, definition) 154*795d594fSAndroid Build Coastguard Worker 155*795d594fSAndroid Build Coastguard Worker def process_trace(index): 156*795d594fSAndroid Build Coastguard Worker """ 157*795d594fSAndroid Build Coastguard Worker Remembers one stack trace in the list of stack traces we have seen. 158*795d594fSAndroid Build Coastguard Worker Remembering a stack trace increments a count associated with the trace. 159*795d594fSAndroid Build Coastguard Worker """ 160*795d594fSAndroid Build Coastguard Worker trace = self.definition(index) 161*795d594fSAndroid Build Coastguard Worker if self.args.use_size: 162*795d594fSAndroid Build Coastguard Worker weight = self.weight(index) 163*795d594fSAndroid Build Coastguard Worker else: 164*795d594fSAndroid Build Coastguard Worker weight = 1 165*795d594fSAndroid Build Coastguard Worker if trace in self.allocation_count: 166*795d594fSAndroid Build Coastguard Worker self.allocation_count[trace] = self.allocation_count[trace] + weight 167*795d594fSAndroid Build Coastguard Worker else: 168*795d594fSAndroid Build Coastguard Worker self.allocation_count[trace] = weight 169*795d594fSAndroid Build Coastguard Worker 170*795d594fSAndroid Build Coastguard Worker # Read the file, processing each line as a definition or stack trace. 171*795d594fSAndroid Build Coastguard Worker tracefile = open(filename, "r") 172*795d594fSAndroid Build Coastguard Worker current_allocation_trace = "" 173*795d594fSAndroid Build Coastguard Worker for line in tracefile: 174*795d594fSAndroid Build Coastguard Worker line = line.rstrip("\n") 175*795d594fSAndroid Build Coastguard Worker if line[0:1] == "=" or line[0:1] == "+": 176*795d594fSAndroid Build Coastguard Worker # definition. 177*795d594fSAndroid Build Coastguard Worker process_definition(line) 178*795d594fSAndroid Build Coastguard Worker else: 179*795d594fSAndroid Build Coastguard Worker # stack trace. 180*795d594fSAndroid Build Coastguard Worker process_trace(int(line)) 181*795d594fSAndroid Build Coastguard Worker 182*795d594fSAndroid Build Coastguard Worker def dump_flame_graph(self): 183*795d594fSAndroid Build Coastguard Worker """ 184*795d594fSAndroid Build Coastguard Worker Prints out a stack trace format compatible with flame graph creation utilities. 185*795d594fSAndroid Build Coastguard Worker """ 186*795d594fSAndroid Build Coastguard Worker for definition, weight in self.allocation_count.items(): 187*795d594fSAndroid Build Coastguard Worker print(definition + " " + str(weight)) 188*795d594fSAndroid Build Coastguard Worker 189*795d594fSAndroid Build Coastguard Workerdef parse_options(): 190*795d594fSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description="Convert a trace to a form usable for flame graphs.") 191*795d594fSAndroid Build Coastguard Worker parser.add_argument("filename", help="The trace file as input", type=str) 192*795d594fSAndroid Build Coastguard Worker parser.add_argument("--use_size", help="Count by allocation size", action="store_true", 193*795d594fSAndroid Build Coastguard Worker default=False) 194*795d594fSAndroid Build Coastguard Worker parser.add_argument("--ignore_type", help="Ignore type of allocation", action="store_true", 195*795d594fSAndroid Build Coastguard Worker default=False) 196*795d594fSAndroid Build Coastguard Worker parser.add_argument("--reverse_stack", help="Reverse root and top of stacks", action="store_true", 197*795d594fSAndroid Build Coastguard Worker default=False) 198*795d594fSAndroid Build Coastguard Worker parser.add_argument("--type_only", help="Only consider allocation type", action="store_true", 199*795d594fSAndroid Build Coastguard Worker default=False) 200*795d594fSAndroid Build Coastguard Worker parser.add_argument("--thread_only", help="Only consider allocation thread", action="store_true", 201*795d594fSAndroid Build Coastguard Worker default=False) 202*795d594fSAndroid Build Coastguard Worker parser.add_argument("--depth_limit", help="Limit the length of a trace", type=int, default=0) 203*795d594fSAndroid Build Coastguard Worker args = parser.parse_args() 204*795d594fSAndroid Build Coastguard Worker return args 205*795d594fSAndroid Build Coastguard Worker 206*795d594fSAndroid Build Coastguard Workerdef main(argv): 207*795d594fSAndroid Build Coastguard Worker args = parse_options() 208*795d594fSAndroid Build Coastguard Worker trace_collection = TraceCollection(args) 209*795d594fSAndroid Build Coastguard Worker trace_collection.read_file(args.filename) 210*795d594fSAndroid Build Coastguard Worker trace_collection.dump_flame_graph() 211*795d594fSAndroid Build Coastguard Worker 212*795d594fSAndroid Build Coastguard Workerif __name__ == '__main__': 213*795d594fSAndroid Build Coastguard Worker sys.exit(main(sys.argv)) 214