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