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