xref: /aosp_15_r20/art/tools/jvmti-agents/ti-alloc-sample/mkflame.py (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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