xref: /spdk/scripts/dpdk_mem_info.py (revision 0ed85362c8132a2d1927757fbcade66b6660d26a)
1#!/usr/bin/env python3
2
3import argparse
4import os
5from enum import Enum
6
7
8class memory:
9    def __init__(self, size):
10        self.size = size
11        self.heaps = []
12        self.mempools = []
13        self.memzones = []
14
15    def get_size(self):
16        return self.size
17
18    def add_mempool(self, pool):
19        self.mempools.append(pool)
20
21    def add_memzone(self, zone):
22        self.memzones.append(zone)
23
24    def add_heap(self, heap):
25        self.heaps.append(heap)
26
27    def get_total_heap_size(self):
28        size = 0
29        for heap in self.heaps:
30            size = size + heap.size
31        return size
32
33    def get_total_mempool_size(self):
34        size = 0
35        for pool in self.mempools:
36            size = size + pool.get_memzone_size_sum()
37        return size
38
39    def get_total_memzone_size(self):
40        size = 0
41        for zone in self.memzones:
42            size = size + zone.size
43        return size
44
45    def print_summary(self):
46        print("DPDK memory size {} in {} heap(s)"
47              .format(B_to_MiB(self.size), len(self.heaps)))
48        print("{} heaps totaling size {}".format(len(self.heaps), B_to_MiB(self.get_total_heap_size())))
49        for x in sorted(self.heaps, key=lambda x: x.size, reverse=True):
50            x.print_summary('  ')
51        print("end heaps----------")
52        print("{} mempools totaling size {}".format(len(self.mempools), B_to_MiB(self.get_total_mempool_size())))
53        for x in sorted(self.mempools, key=lambda x: x.get_memzone_size_sum(), reverse=True):
54            x.print_summary('  ')
55        print("end mempools-------")
56        print("{} memzones totaling size {}".format(len(self.memzones), B_to_MiB(self.get_total_memzone_size())))
57        for x in sorted(self.memzones, key=lambda x: x.size, reverse=True):
58            x.print_summary('  ')
59        print("end memzones-------")
60
61    def print_heap_summary(self, heap_id):
62        for heap in self.heaps:
63            if heap_id == heap.id:
64                heap.print_detailed_stats()
65                break
66        else:
67            print("heap id {} is invalid. please see the summary for valid heaps.\n".format(heap_id))
68
69    def print_mempool_summary(self, name):
70        for pool in self.mempools:
71            if name == pool.name:
72                pool.print_detailed_stats()
73                break
74        else:
75            print("mempool name {} is invalid. please see the summary for valid mempools.\n".format(name))
76
77    def print_memzone_summary(self, name):
78        for zone in self.memzones:
79            if name == zone.name:
80                zone.print_detailed_stats("")
81                break
82        else:
83            print("memzone name {} is invalid. please see the summary for valid memzone.\n".format(name))
84
85    def associate_heap_elements_and_memzones(self):
86        for zone in self.memzones:
87            for heap_obj in self.heaps:
88                for element in heap_obj.busy_malloc_elements:
89                    if element.check_memzone_compatibility(zone):
90                        heap_obj.busy_memzone_elements.append(element)
91                        heap_obj.busy_malloc_elements.remove(element)
92
93    def associate_memzones_and_mempools(self):
94        for pool in self.mempools:
95            for zone in self.memzones:
96                if pool.name in zone.name:
97                    pool.add_memzone(zone)
98
99        for pool in self.mempools:
100            for zone in pool.memzones:
101                if zone in self.memzones:
102                    self.memzones.remove(zone)
103
104
105class heap_elem_status(Enum):
106    FREE = 0
107    BUSY = 1
108
109
110class heap_element:
111    def __init__(self, size, status, addr):
112        self.status = status
113        self.size = size
114        self.addr = addr
115        self.memzone = None
116
117    def print_summary(self, header):
118        print("{}element at address: {} with size: {:>15}".format(header, hex(self.addr), B_to_MiB(self.size)))
119
120    def check_memzone_compatibility(self, memzone):
121        ele_fini_addr = self.addr + self.size
122        memzone_fini_addr = memzone.address + memzone.size
123        if (self.addr <= memzone.address and ele_fini_addr >= memzone_fini_addr):
124            self.memzone = memzone
125            return True
126        return False
127
128
129class heap:
130    def __init__(self, id, size, num_allocations):
131        self.id = id
132        self.size = size
133        self.num_allocations = num_allocations
134        self.free_elements = []
135        self.busy_malloc_elements = []
136        self.busy_memzone_elements = []
137
138    def add_element(self, element):
139        if element.status == heap_elem_status.FREE:
140            self.free_elements.append(element)
141        else:
142            self.busy_malloc_elements.append(element)
143
144    def print_element_stats(self, list_to_print, list_type, header):
145        print("{}list of {} elements. size: {}".format(header, list_type, B_to_MiB(self.get_element_size(list_to_print))))
146        for x in sorted(list_to_print, key=lambda x: x.size, reverse=True):
147            x.print_summary("{}  ".format(header))
148            if x.memzone is not None:
149                x.memzone.print_summary("    {}associated memzone info: ".format(header))
150
151    def get_element_size(self, list_to_check):
152        size = 0
153        for element in list_to_check:
154            size = size + element.size
155        return size
156
157    def print_summary(self, header):
158        print("{}size: {:>15} heap id: {}".format(header, B_to_MiB(self.size), self.id))
159
160    def print_detailed_stats(self):
161        print("heap id: {} total size: {} number of busy elements: {} number of free elements: {}"
162              .format(self.id, B_to_MiB(self.size), len(self.busy_malloc_elements), len(self.free_elements)))
163        self.print_element_stats(self.free_elements, "free", "  ")
164        self.print_element_stats(self.busy_malloc_elements, "standard malloc", "  ")
165        self.print_element_stats(self.busy_memzone_elements, "memzone associated", "  ")
166
167
168class mempool:
169    def __init__(self, name, num_objs, num_populated_objs, obj_size):
170        self.name = name
171        self.num_objs = num_objs
172        self.num_populated_objs = num_populated_objs
173        self.obj_size = obj_size
174        self.memzones = []
175
176    def add_memzone(self, memzone):
177        self.memzones.append(memzone)
178
179    def get_memzone_size_sum(self):
180        size = 0
181        for zone in self.memzones:
182            size = size + zone.size
183        return size
184
185    def print_summary(self, header):
186        print("{}size: {:>15} name: {}"
187              .format(header, B_to_MiB(self.get_memzone_size_sum()), self.name))
188
189    def print_detailed_stats(self):
190        print("size: {:>15} name: {} comprised of {} memzone(s):"
191              .format(B_to_MiB(self.get_memzone_size_sum()), self.name, len(self.memzones)))
192        for x in sorted(self.memzones, key=lambda x: x.size, reverse=True):
193            x.print_detailed_stats("  ")
194
195
196class memzone:
197    def __init__(self, name, size, address):
198        self.name = name
199        self.size = size
200        self.address = address
201        self.segments = []
202
203    def add_segment(self, segment):
204        self.segments.append(segment)
205
206    def print_summary(self, header):
207        print("{}size: {:>15} name: {}".format(header,  B_to_MiB(self.size), self.name))
208
209    def print_detailed_stats(self, header):
210        self.print_summary(header)
211        print("{}located at address {}".format(header, hex(self.address)))
212        print("{}spanning {} segment(s):".format(header, len(self.segments)))
213        for x in sorted(self.segments, key=lambda x: x.size, reverse=True):
214            x.print_summary('  ')
215
216
217class segment:
218    def __init__(self, size, address):
219        self.size = size
220        self.address = address
221
222    def print_summary(self, header):
223        print("{}address: {} length: {:>15}".format(header, hex(self.address), B_to_MiB(self.size)))
224
225
226class parse_state(Enum):
227    PARSE_MEMORY_SIZE = 0
228    PARSE_MEMZONES = 1
229    PARSE_MEMZONE_SEGMENTS = 2
230    PARSE_MEMPOOLS = 3
231    PARSE_MEMPOOL_INFO = 4
232    PARSE_HEAPS = 5
233    PARSE_HEAP_ELEMENTS = 6
234
235
236def B_to_MiB(raw_value):
237    raw_value = raw_value / (1024.0 * 1024.0)
238
239    return "%6f %s" % (raw_value, "MiB")
240
241
242def parse_zone(line):
243    zone, info = line.split(':', 1)
244    name, length, addr, trash = info.split(',', 3)
245
246    trash, name = name.split(':', 1)
247    name = name.replace("<", "")
248    name = name.replace(">", "")
249    trash, length = length.split(':', 1)
250    trash, addr = addr.split(':', 1)
251
252    return memzone(name, int(length, 0), int(addr, 0))
253
254
255def parse_segment(line):
256    trash, addr, iova, length, pagesz = line.split(':')
257    addr, trash = addr.strip().split(' ')
258    length, trash = length.strip().split(' ')
259
260    return segment(int(length, 0), int(addr, 0))
261
262
263def parse_mempool_name(line):
264    trash, info = line.split()
265    name, addr = line.split('@')
266    name = name.replace("<", "")
267    name = name.replace(">", "")
268    trash, name = name.split()
269
270    return name
271
272
273def parse_mem_stats(stat_path):
274    state = parse_state.PARSE_MEMORY_SIZE
275    with open(stat_path, "r") as stats:
276
277        line = stats.readline()
278        while line != '':
279            if state == parse_state.PARSE_MEMORY_SIZE:
280                if "DPDK memory size" in line:
281                    mem_size = int(line.replace("DPDK memory size ", ""))
282                    memory_struct = memory(mem_size)
283                    state = parse_state.PARSE_MEMZONES
284                line = stats.readline()
285
286            if state == parse_state.PARSE_MEMZONES:
287                if line.find("Zone") == 0:
288                    zone = parse_zone(line)
289                    state = parse_state.PARSE_MEMZONE_SEGMENTS
290                line = stats.readline()
291
292            if state == parse_state.PARSE_MEMZONE_SEGMENTS:
293                if line.find("Zone") == 0:
294                    memory_struct.add_memzone(zone)
295                    state = parse_state.PARSE_MEMZONES
296                    continue
297                elif line.lstrip().find("addr:") == 0:
298                    segment = parse_segment(line)
299                    zone.add_segment(segment)
300                elif "DPDK mempools." in line:
301                    state = parse_state.PARSE_MEMPOOLS
302                    continue
303                line = stats.readline()
304
305            if state == parse_state.PARSE_MEMPOOLS:
306                mempool_info = {}
307                if line.find("mempool") == 0:
308                    mempool_info['name'] = parse_mempool_name(line)
309                    state = parse_state.PARSE_MEMPOOL_INFO
310                line = stats.readline()
311
312            if state == parse_state.PARSE_MEMPOOL_INFO:
313                if line.find("mempool") == 0:
314                    try:
315                        new_mempool = mempool(mempool_info['name'], int(mempool_info['size'], 0),
316                                              int(mempool_info['populated_size'], 0), int(mempool_info['total_obj_size'], 0))
317                        memory_struct.add_mempool(new_mempool)
318                    except KeyError:
319                        print("proper key values not provided for mempool.")
320                    state = parse_state.PARSE_MEMPOOLS
321                    continue
322                elif "cache" in line:
323                    pass
324                elif "DPDK malloc stats." in line:
325                    try:
326                        new_mempool = mempool(mempool_info['name'], int(mempool_info['size'], 0),
327                                              int(mempool_info['populated_size'], 0), int(mempool_info['total_obj_size'], 0))
328                        memory_struct.add_mempool(new_mempool)
329                    except KeyError:
330                        print("proper key values not provided for mempool.")
331                    while "DPDK malloc heaps." not in line:
332                        line = stats.readline()
333                    state = parse_state.PARSE_HEAPS
334                else:
335                    try:
336                        field, value = line.strip().split('=')
337                        mempool_info[field] = value
338                    except Exception as e:
339                        pass
340                line = stats.readline()
341
342            if state == parse_state.PARSE_HEAPS:
343                trash, heap_id = line.strip().split(':')
344                line = stats.readline()
345                trash, heap_size = line.split(':')
346                line = stats.readline()
347                trash, num_allocations = line.split(':')
348                if int(heap_size, 0) == 0:
349                    pass
350                else:
351                    new_heap = heap(heap_id.lstrip(), int(heap_size, 0), int(num_allocations, 0))
352                    memory_struct.add_heap(new_heap)
353                    state = parse_state.PARSE_HEAP_ELEMENTS
354
355                line = stats.readline()
356
357            if state == parse_state.PARSE_HEAP_ELEMENTS:
358                if line.find("Heap id") == 0:
359                    state = parse_state.PARSE_HEAPS
360                    continue
361                elif line.find("Malloc element at") == 0:
362                    trash, address, status = line.rsplit(maxsplit=2)
363                    line = stats.readline()
364                    trash, length, trash = line.split(maxsplit=2)
365                    line = stats.readline()
366                    if "FREE" in status:
367                        element = heap_element(int(length, 0), heap_elem_status.FREE, int(address, 0))
368                    else:
369                        element = heap_element(int(length, 0), heap_elem_status.BUSY, int(address, 0))
370                    new_heap.add_element(element)
371                line = stats.readline()
372
373    memory_struct.associate_heap_elements_and_memzones()
374    memory_struct.associate_memzones_and_mempools()
375    return memory_struct
376
377
378if __name__ == "__main__":
379    parser = argparse.ArgumentParser(description='Dumps memory stats for DPDK. If no arguments are provided, it dumps a general summary.')
380    parser.add_argument('-f', dest="stats_file", help='path to a dpdk memory stats file.', default='/tmp/spdk_mem_dump.txt')
381    parser.add_argument('-m', '--heap', dest="heap", help='Print detailed information about the given heap.', default=None)
382    parser.add_argument('-p', '--mempool', dest="mempool", help='Print detailed information about the given mempool.', default=None)
383    parser.add_argument('-z', '--memzone', dest="memzone", help='Print detailed information about the given memzone.', default=None)
384
385    args = parser.parse_args()
386
387    if not os.path.exists(args.stats_file):
388        print("Error, specified stats file does not exist. Please make sure you have run the"
389              "env_dpdk_get_mem_stats rpc on the spdk app you want to analyze.")
390        exit(1)
391
392    mem_info = parse_mem_stats(args.stats_file)
393
394    summary = True
395    if args.heap is not None:
396        mem_info.print_heap_summary(args.heap)
397        summary = False
398    if args.mempool is not None:
399        mem_info.print_mempool_summary(args.mempool)
400        summary = False
401    if args.memzone is not None:
402        mem_info.print_memzone_summary(args.memzone)
403        summary = False
404
405    if summary:
406        mem_info.print_summary()
407