pebble/tools/parse_dump_malloc.py
2025-01-27 11:38:16 -08:00

200 lines
5.3 KiB
Python

# Copyright 2024 Google LLC
#
# 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.
import argparse
import os
import sh
import sys
total_alloc_size = 0
alloc_count = 0
total_free_size = 0
free_count = 0
largest_free_block = 0
per_file_dict = {}
alloc_list = []
root_path = os.path.join(os.path.dirname(sys.argv[0]), '..')
min_addr = 0
max_addr = 0
current_size = 0
high_water_mark = 0
def get_filename_linenumber(addr_str):
try:
line = sh.arm_none_eabi_addr2line(addr_str, exe=elf_path)
except:
return ("?", 0)
line = line.strip()
index = line.rfind(':')
filename = line[:index]
linenumber = line[index + 1:]
if ':' not in filename:
filename = os.path.relpath(filename, root_path)
return (filename, linenumber)
def handle_line(line, verbose):
parts = line.split(' ')
# Skip gdb continuation prompts
if parts[0].startswith('---'):
return;
if parts[0] == 'Heap':
metadata_name = ' '.join(parts[1:-1])
if metadata_name == 'start':
global min_addr
min_addr = int(parts[-1].strip(), 16)
elif metadata_name == 'end':
global max_addr
max_addr = int(parts[-1].strip(), 16)
elif metadata_name == 'current size':
global current_size
current_size = int(parts[-1].strip())
elif metadata_name == 'high water mark':
global high_water_mark
high_water_mark = int(parts[-1].strip())
return
def get_part_value(index):
return parts[index].split(':')[1]
pc = get_part_value(0)
addr = get_part_value(1)
actual_size = int(get_part_value(2))
filename, linenumber = get_filename_linenumber(pc)
is_free = (int(pc, 0) == 0)
if verbose:
if is_free:
print "Size: %6u, Addr: 0x%08x PC: 0x%08x FREE" % (actual_size, int(addr, 0), int(pc, 0))
else:
print "Size: %6u, Addr: 0x%08x PC: 0x%08x %s:%s" % (actual_size, int(addr, 0), int(pc, 0), filename,
linenumber)
global total_alloc_size
global alloc_count
global total_free_size
global free_count
global largest_free_block
# Capture largest free block size
if is_free:
total_free_size += actual_size
free_count += 1
if actual_size > largest_free_block:
largest_free_block = actual_size
return
total_alloc_size += actual_size
alloc_count += 1
filename = filename
try:
per_file_dict[filename] += actual_size
except:
per_file_dict[filename] = actual_size
alloc_list.append([int(addr, base=16), actual_size])
def draw_image():
import cairo
WIDTH, HEIGHT = 2048, 256
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
ctx = cairo.Context(surface)
ctx.set_source_rgb(0.8, 0.8, 0.8)
ctx.set_line_width(0)
ctx.rectangle(0, 0, WIDTH, HEIGHT)
ctx.fill()
def translate_to_pixels(addr):
return (float(addr - min_addr) / float(max_addr - min_addr)) * WIDTH
def draw_section(start, end):
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(0)
ctx.rectangle(start, 0, end - start, HEIGHT)
ctx.fill()
for alloc in alloc_list:
addr = alloc[0]
size = alloc[1]
pixel_start = translate_to_pixels(addr)
pixel_end = translate_to_pixels(addr + size)
draw_section(pixel_start, pixel_end)
ctx.stroke()
surface.write_to_png("dump_malloc.png")
print "Drew image to dump_malloc.png"
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--image', action='store_true')
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--elf_file', required=True)
parser.add_argument('in_file')
args = parser.parse_args()
verbose = False
if args.verbose is not None:
verbose = args.verbose
elf_path = args.elf_file
with open(args.in_file, 'r') as f:
infile = f.readlines()
for line in infile:
line = line.strip()
if len(line) > 0:
handle_line(line, verbose)
if min_addr == 0 or max_addr == 0:
min_addr = alloc_list[0][0]
max_addr = alloc_list[0][0] + alloc_list[0][1]
for alloc in alloc_list:
low_addr = alloc[0]
if low_addr < min_addr: min_addr = low_addr
high_addr = low_addr + alloc[1]
if high_addr > max_addr: max_addr = high_addr
if verbose:
print ""
print "Heap start: 0x%x" % min_addr
print "Heap end: 0x%x" % max_addr
print "Heap size: %u bytes" % (max_addr - min_addr)
print "Total allocated: %u bytes, %u blocks" % (total_alloc_size, alloc_count);
print "High water mark: %u" % high_water_mark
print "Total free: %u bytes, %u blocks" % (total_free_size, free_count);
print "Largest free block: %u" % largest_free_block
print
per_file_list = [[k, v] for k, v in per_file_dict.iteritems()]
sorted_per_file_list = sorted(per_file_list, key=lambda v: v[1], reverse=True)
for k, v in sorted_per_file_list:
print "%s: %u bytes" % (k, v)
if args.image:
draw_image()