1b1994897Sopenharmony_ci#!/usr/bin/env python3 2b1994897Sopenharmony_ci# -*- coding: utf-8 -*- 3b1994897Sopenharmony_ci# Copyright (c) 2021-2022 Huawei Device Co., Ltd. 4b1994897Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); 5b1994897Sopenharmony_ci# you may not use this file except in compliance with the License. 6b1994897Sopenharmony_ci# You may obtain a copy of the License at 7b1994897Sopenharmony_ci# 8b1994897Sopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 9b1994897Sopenharmony_ci# 10b1994897Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software 11b1994897Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, 12b1994897Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b1994897Sopenharmony_ci# See the License for the specific language governing permissions and 14b1994897Sopenharmony_ci# limitations under the License. 15b1994897Sopenharmony_ci 16b1994897Sopenharmony_ci""" 17b1994897Sopenharmony_ciA tool to view memory usage reports. 18b1994897Sopenharmony_ci 19b1994897Sopenharmony_ciTo get a memory usage report build Panda with -DPANDA_TRACK_INTERNAL_ALLOCATIONS=2 cmake option. 20b1994897Sopenharmony_ciPanda runtime writes memory reports into memdump.bin file at runtime destruction and at SIGUSR2 21b1994897Sopenharmony_cisignal. This script is aimed to analyse memdump.bin file. 22b1994897Sopenharmony_ciTo view the report run the script as follow: 23b1994897Sopenharmony_ci python3 scripts/memdump.py memdump.bin 24b1994897Sopenharmony_ciThe report contains: 25b1994897Sopenharmony_ci * total allocated memory (not considering free) 26b1994897Sopenharmony_ci * peak allocated memory (maximum amount of allocated memory) 27b1994897Sopenharmony_ci * detailed information about each allocation point: 28b1994897Sopenharmony_ci * total number of bytes allocated (not considering free) 29b1994897Sopenharmony_ci * number of allocations 30b1994897Sopenharmony_ci * range of allocation sizes 31b1994897Sopenharmony_ci * stacktrace 32b1994897Sopenharmony_ci 33b1994897Sopenharmony_ciTo view only live allocations (allocations which are not free at the moment of dump) --live 34b1994897Sopenharmony_cioption is used. If the dump is collected during runtime destruction this report will contains 35b1994897Sopenharmony_cimemory leaks 36b1994897Sopenharmony_ci 37b1994897Sopenharmony_ciIt is possible to filter and sort data (run the script with -h option) 38b1994897Sopenharmony_ci""" 39b1994897Sopenharmony_ci 40b1994897Sopenharmony_ciimport sys 41b1994897Sopenharmony_ciimport argparse 42b1994897Sopenharmony_ciimport struct 43b1994897Sopenharmony_ci 44b1994897Sopenharmony_ci# must be aligned with SpaceType enum in 45b1994897Sopenharmony_ci# libpandabase/mem/space.h 46b1994897Sopenharmony_ciSPACES = { 47b1994897Sopenharmony_ci 1: 'object', 48b1994897Sopenharmony_ci 2: 'humongous', 49b1994897Sopenharmony_ci 3: 'nonmovable', 50b1994897Sopenharmony_ci 4: 'internal', 51b1994897Sopenharmony_ci 5: 'code', 52b1994897Sopenharmony_ci 6: 'compiler' 53b1994897Sopenharmony_ci} 54b1994897Sopenharmony_ci 55b1994897Sopenharmony_ciTAG_ALLOC = 1 56b1994897Sopenharmony_ciTAG_FREE = 2 57b1994897Sopenharmony_ci 58b1994897Sopenharmony_ci 59b1994897Sopenharmony_ciclass AllocInfo: 60b1994897Sopenharmony_ci """Contains information about all allocated memory""" 61b1994897Sopenharmony_ci 62b1994897Sopenharmony_ci def __init__(self, stacktrace): 63b1994897Sopenharmony_ci self.stacktrace = stacktrace 64b1994897Sopenharmony_ci self.allocated_size = 0 65b1994897Sopenharmony_ci self.sizes = [] 66b1994897Sopenharmony_ci 67b1994897Sopenharmony_ci def alloc(self, size): 68b1994897Sopenharmony_ci """Handles allocation of size bytes""" 69b1994897Sopenharmony_ci 70b1994897Sopenharmony_ci self.allocated_size += size 71b1994897Sopenharmony_ci self.sizes.append(size) 72b1994897Sopenharmony_ci 73b1994897Sopenharmony_ci def free(self, size): 74b1994897Sopenharmony_ci """Handles deallocation of size bytes""" 75b1994897Sopenharmony_ci 76b1994897Sopenharmony_ci self.allocated_size -= size 77b1994897Sopenharmony_ci 78b1994897Sopenharmony_ci 79b1994897Sopenharmony_ci# pylint: disable=too-few-public-methods 80b1994897Sopenharmony_ciclass Filter: 81b1994897Sopenharmony_ci """Filter by space and substring""" 82b1994897Sopenharmony_ci 83b1994897Sopenharmony_ci def __init__(self, space, strfilter): 84b1994897Sopenharmony_ci self.space = space 85b1994897Sopenharmony_ci self.strfilter = strfilter 86b1994897Sopenharmony_ci 87b1994897Sopenharmony_ci def filter(self, space, stacktrace): 88b1994897Sopenharmony_ci """Checks that space and stacktrace matches filter""" 89b1994897Sopenharmony_ci 90b1994897Sopenharmony_ci if self.space != 'all' and SPACES[space] != self.space: 91b1994897Sopenharmony_ci return True 92b1994897Sopenharmony_ci if self.strfilter is not None and self.strfilter not in stacktrace: 93b1994897Sopenharmony_ci return True 94b1994897Sopenharmony_ci return False 95b1994897Sopenharmony_ci 96b1994897Sopenharmony_ci 97b1994897Sopenharmony_cidef validate_space(value): 98b1994897Sopenharmony_ci """Validates space value""" 99b1994897Sopenharmony_ci 100b1994897Sopenharmony_ci if value not in SPACES.values(): 101b1994897Sopenharmony_ci print('Invalid value {} of --space option'.format(value)) 102b1994897Sopenharmony_ci sys.exit(1) 103b1994897Sopenharmony_ci 104b1994897Sopenharmony_ci 105b1994897Sopenharmony_cidef read_string(file): 106b1994897Sopenharmony_ci """Reads string from file""" 107b1994897Sopenharmony_ci 108b1994897Sopenharmony_ci num = struct.unpack('I', file.read(4))[0] 109b1994897Sopenharmony_ci return file.read(num).decode('utf-8') 110b1994897Sopenharmony_ci 111b1994897Sopenharmony_ci 112b1994897Sopenharmony_cidef sort(data): 113b1994897Sopenharmony_ci """Sorts data by allocated size and number of allocations""" 114b1994897Sopenharmony_ci 115b1994897Sopenharmony_ci return sorted( 116b1994897Sopenharmony_ci data, key=lambda info: (info.allocated_size, len(info.sizes)), 117b1994897Sopenharmony_ci reverse=True) 118b1994897Sopenharmony_ci 119b1994897Sopenharmony_ci 120b1994897Sopenharmony_cidef pretty_alloc_sizes(sizes): 121b1994897Sopenharmony_ci """Prettifies allocatation sizes""" 122b1994897Sopenharmony_ci 123b1994897Sopenharmony_ci min_size = sizes[0] 124b1994897Sopenharmony_ci max_size = min_size 125b1994897Sopenharmony_ci for size in sizes: 126b1994897Sopenharmony_ci if size < min_size: 127b1994897Sopenharmony_ci min_size = size 128b1994897Sopenharmony_ci elif size > max_size: 129b1994897Sopenharmony_ci max_size = size 130b1994897Sopenharmony_ci 131b1994897Sopenharmony_ci if min_size == max_size: 132b1994897Sopenharmony_ci return 'all {} bytes'.format(min_size) 133b1994897Sopenharmony_ci 134b1994897Sopenharmony_ci return 'from {} to {} bytes'.format(min_size, max_size) 135b1994897Sopenharmony_ci 136b1994897Sopenharmony_ci 137b1994897Sopenharmony_cidef pretty_stacktrace(stacktrace): 138b1994897Sopenharmony_ci """Prettifies stacktrace""" 139b1994897Sopenharmony_ci 140b1994897Sopenharmony_ci if not stacktrace: 141b1994897Sopenharmony_ci return "<No stacktrace>" 142b1994897Sopenharmony_ci return stacktrace 143b1994897Sopenharmony_ci 144b1994897Sopenharmony_ci 145b1994897Sopenharmony_cidef get_args(): 146b1994897Sopenharmony_ci """Gets cli arguments""" 147b1994897Sopenharmony_ci 148b1994897Sopenharmony_ci parser = argparse.ArgumentParser() 149b1994897Sopenharmony_ci parser.add_argument( 150b1994897Sopenharmony_ci '--live', action='store_true', default=False, 151b1994897Sopenharmony_ci help='Dump only live allocations (for which "free" is not called)') 152b1994897Sopenharmony_ci parser.add_argument( 153b1994897Sopenharmony_ci '--space', 154b1994897Sopenharmony_ci help='Report only allocations for the specific space. Possible values: {}'. 155b1994897Sopenharmony_ci format(', '.join(SPACES.values()))) 156b1994897Sopenharmony_ci parser.add_argument( 157b1994897Sopenharmony_ci '--filter', 158b1994897Sopenharmony_ci help='Filter allocations by a string in stacktrace (it may be a function of file and line)') 159b1994897Sopenharmony_ci parser.add_argument('input_file', default='memdump.bin') 160b1994897Sopenharmony_ci 161b1994897Sopenharmony_ci args = parser.parse_args() 162b1994897Sopenharmony_ci 163b1994897Sopenharmony_ci return args 164b1994897Sopenharmony_ci 165b1994897Sopenharmony_ci 166b1994897Sopenharmony_ci# pylint: disable=too-many-locals 167b1994897Sopenharmony_cidef get_allocs(args, space_filter): 168b1994897Sopenharmony_ci """Prints and returns statistic: stacktrace -> allocation info""" 169b1994897Sopenharmony_ci 170b1994897Sopenharmony_ci total_allocated = 0 171b1994897Sopenharmony_ci max_allocated = 0 172b1994897Sopenharmony_ci cur_allocated = 0 173b1994897Sopenharmony_ci with open(args.input_file, "rb") as file: 174b1994897Sopenharmony_ci num_items, num_stacktraces = struct.unpack('II', file.read(8)) 175b1994897Sopenharmony_ci stacktraces = {} 176b1994897Sopenharmony_ci stacktrace_id = 0 177b1994897Sopenharmony_ci while stacktrace_id < num_stacktraces: 178b1994897Sopenharmony_ci stacktraces[stacktrace_id] = read_string(file) 179b1994897Sopenharmony_ci stacktrace_id += 1 180b1994897Sopenharmony_ci 181b1994897Sopenharmony_ci allocs = {} 182b1994897Sopenharmony_ci id2alloc = {} 183b1994897Sopenharmony_ci while num_items > 0: 184b1994897Sopenharmony_ci tag = struct.unpack_from("I", file.read(4))[0] 185b1994897Sopenharmony_ci if tag == TAG_ALLOC: 186b1994897Sopenharmony_ci identifier, size, space, stacktrace_id = struct.unpack( 187b1994897Sopenharmony_ci "IIII", file.read(16)) 188b1994897Sopenharmony_ci stacktrace = stacktraces[stacktrace_id] 189b1994897Sopenharmony_ci if not space_filter.filter(space, stacktrace): 190b1994897Sopenharmony_ci info = allocs.setdefault( 191b1994897Sopenharmony_ci stacktrace_id, AllocInfo(stacktrace)) 192b1994897Sopenharmony_ci info.alloc(size) 193b1994897Sopenharmony_ci id2alloc[identifier] = (info, size) 194b1994897Sopenharmony_ci total_allocated += size 195b1994897Sopenharmony_ci cur_allocated += size 196b1994897Sopenharmony_ci max_allocated = max(cur_allocated, max_allocated) 197b1994897Sopenharmony_ci elif tag == TAG_FREE: 198b1994897Sopenharmony_ci alloc_id = struct.unpack("I", file.read(4))[0] 199b1994897Sopenharmony_ci res = id2alloc.pop(alloc_id, None) 200b1994897Sopenharmony_ci if res is not None: 201b1994897Sopenharmony_ci info = res[0] 202b1994897Sopenharmony_ci size = res[1] 203b1994897Sopenharmony_ci info.free(size) 204b1994897Sopenharmony_ci cur_allocated -= size 205b1994897Sopenharmony_ci else: 206b1994897Sopenharmony_ci raise Exception("Invalid file format") 207b1994897Sopenharmony_ci 208b1994897Sopenharmony_ci num_items -= 1 209b1994897Sopenharmony_ci 210b1994897Sopenharmony_ci print("Total allocated: {}, peak allocated: {}, current allocated {}".format( 211b1994897Sopenharmony_ci total_allocated, max_allocated, cur_allocated)) 212b1994897Sopenharmony_ci 213b1994897Sopenharmony_ci return allocs 214b1994897Sopenharmony_ci 215b1994897Sopenharmony_ci 216b1994897Sopenharmony_cidef main(): 217b1994897Sopenharmony_ci """Script's entrypoint""" 218b1994897Sopenharmony_ci 219b1994897Sopenharmony_ci args = get_args() 220b1994897Sopenharmony_ci live_allocs = args.live 221b1994897Sopenharmony_ci 222b1994897Sopenharmony_ci space_filter = 'all' 223b1994897Sopenharmony_ci if args.space is not None: 224b1994897Sopenharmony_ci validate_space(args.space) 225b1994897Sopenharmony_ci space_filter = args.space 226b1994897Sopenharmony_ci 227b1994897Sopenharmony_ci allocs = get_allocs(args, Filter(space_filter, args.filter)) 228b1994897Sopenharmony_ci allocs = allocs.values() 229b1994897Sopenharmony_ci allocs = sort(allocs) 230b1994897Sopenharmony_ci 231b1994897Sopenharmony_ci for info in allocs: 232b1994897Sopenharmony_ci if not live_allocs or (live_allocs and info.allocated_size > 0): 233b1994897Sopenharmony_ci print("Allocated: {} bytes. {} allocs {} from:\n{}".format( 234b1994897Sopenharmony_ci info.allocated_size, len(info.sizes), 235b1994897Sopenharmony_ci pretty_alloc_sizes(info.sizes), 236b1994897Sopenharmony_ci pretty_stacktrace(info.stacktrace))) 237b1994897Sopenharmony_ci 238b1994897Sopenharmony_ci 239b1994897Sopenharmony_ciif __name__ == "__main__": 240b1994897Sopenharmony_ci main() 241