From 879e8cd0130754b0f0bf50eeeca18859095bd581 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Tue, 5 May 2020 14:34:39 +0200 Subject: chattymalloc: use external chattymalloc repository --- src/Makefile | 6 +- src/allocators/chattymalloc.py | 41 +++- src/chattymalloc.c | 400 --------------------------------------- src/chattymalloc.h | 43 ----- src/chattyparser.py | 420 ----------------------------------------- 5 files changed, 32 insertions(+), 878 deletions(-) delete mode 100644 src/chattymalloc.c delete mode 100644 src/chattymalloc.h delete mode 100755 src/chattyparser.py diff --git a/src/Makefile b/src/Makefile index 84179e2..caceb19 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,7 +14,7 @@ MEMSIZE_KB=$(shell free -t | tail -1 | tr -s ' ' | cut -d ' ' -f 2) MEMSIZE="$(MEMSIZE_KB) * 1024l" TOOLS = print_status_on_exit.so exec sig_handlers.so -ALLOCS = chattymalloc.so bumpptr_alloc.so align_to_cl.so +ALLOCS = bumpptr_alloc.so align_to_cl.so TARGETS = $(addprefix $(OBJDIR)/allocators/,$(ALLOCS)) $(addprefix $(OBJDIR)/,$(TOOLS)) .PHONY: all clean @@ -29,10 +29,6 @@ $(OBJDIR)/allocators/align_to_cl.so: align_to_cl.c Makefile @if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi $(CC) $(LDFLAGS) -shared $(CFLAGS) -o $@ $< -ldl -$(OBJDIR)/allocators/chattymalloc.so: chattymalloc.h chattymalloc.c Makefile - @if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi - $(CC) $(LDFLAGS) -shared $(CFLAGS) -o $@ chattymalloc.c -ldl - $(OBJDIR)/print_status_on_exit.so: print_status_on_exit.c Makefile @if test \( ! \( -d $(@D) \) \) ;then mkdir -p $(@D);fi $(CC) $(LDFLAGS) -shared $(CFLAGS) -o $@ $< diff --git a/src/allocators/chattymalloc.py b/src/allocators/chattymalloc.py index 0f893bb..ff4a3ba 100644 --- a/src/allocators/chattymalloc.py +++ b/src/allocators/chattymalloc.py @@ -1,4 +1,4 @@ -# Copyright 2018-2019 Florian Fischer +# Copyright 2018-2020 Florian Fischer # # This file is part of allocbench. # @@ -16,16 +16,37 @@ # along with allocbench. If not, see . """chattymalloc allocator -This shared library is no functional allocator. It is used to retrieve a trace -of the allocator usage of the executed programm. It overrides the malloc API -and writes each call and its result to an output file. -See src/chattymalloc.c and chattyparser.py for its implementation and usage. +This shared library is not a functional allocator. It is used to trace +the allocator usage and the executed program. It overrides the malloc API +and saves each call and its result to a memory mapped output file. """ import os -from src.allocator import Allocator, BUILDDIR -chattymalloc = Allocator( - "chattymalloc", - LD_PRELOAD=os.path.join(BUILDDIR, "chattymalloc.so"), - cmd_prefix="env CHATTYMALLOC_FILE={{result_dir}}/{{perm}}.trace") +from src.artifact import GitArtifact +from src.allocator import Allocator + +VERSION = "1a09b144eb18919014ecf86da3442344b0eaa5b2" + +class Chattymalloc(Allocator): + """Chattymalloc definition for allocbench""" + + sources = GitArtifact("chattymalloc", + "https://github.com/fischerling/chattymalloc") + + def __init__(self, name, **kwargs): + + configuration = "--buildtype=release " + for option, value in kwargs.get("options", {}).items(): + configuration += f"-D{option}={value} " + + self.build_cmds = [ + f"meson {{srcdir}} {{dir}} {configuration}", "ninja -C {dir}" + ] + + self.LD_PRELOAD = "{dir}/libchattymalloc.so" + self.cmd_prefix="env CHATTYMALLOC_FILE={{result_dir}}/{{perm}}.trace" + super().__init__(name, **kwargs) + + +chattymalloc = Chattymalloc("chattymalloc", version=VERSION) diff --git a/src/chattymalloc.c b/src/chattymalloc.c deleted file mode 100644 index 14bde06..0000000 --- a/src/chattymalloc.c +++ /dev/null @@ -1,400 +0,0 @@ -/* -Copyright 2018-2020 Florian Fischer - -This file is part of allocbench. - -allocbench is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -allocbench is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with allocbench. If not, see . -*/ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chattymalloc.h" - -#define unlikely(x) __builtin_expect((x),0) - -#define GROWTH_THRESHOLD 4096 -#define GROWTH_ENTRIES 100000 - -// flag to stop recursion during bootstrap -static int initializing = 0; - -// memory to bootstrap malloc -static char tmpbuff[4096]; -static unsigned long tmppos = 0; -static unsigned long tmpallocs = 0; - -// global log file descriptor -static int out_fd = -1; -// memory mapping of our output file -static volatile trace_t* out[2] = {NULL, NULL}; -// next free index into the mapped buffer -static volatile uint64_t next_entry = 0; -// current size of our log file / mapping -static volatile uint64_t total_entries = 0; - -// pthread mutex and cond to protect growth of the buffer -static pthread_cond_t growing; -static pthread_mutex_t growth_mutex; - -static __thread pid_t tid = 0; - -// thread specific key to register a destructor -static pthread_key_t tls_key; -static pthread_once_t tls_key_once = PTHREAD_ONCE_INIT; - -// log_thread_termination forward declaration because it uses trace_write -static void log_thread_termination(void* key __attribute__ ((unused))); - -static void make_tls_key() -{ - int err = pthread_key_create(&tls_key, log_thread_termination); - if (err) { - abort(); - } -} - -static void init_thread() -{ - tid = syscall(SYS_gettid); - - // init our thread destructor - int err = pthread_once(&tls_key_once, make_tls_key); - if (err) { - abort(); - } - - // set the key to something != NULL to execute the destructor on thread exit - // NOLINTNEXTLINE(readability-magic-numbers) - err = pthread_setspecific(tls_key, (void*)42); - if (err) { - abort(); - } -} - -/*========================================================= - * intercepted functions - */ - -static void* (*next_malloc)(size_t size); -static void (*next_free)(void* ptr); -static void* (*next_calloc)(size_t nmemb, size_t size); -static void* (*next_realloc)(void* ptr, size_t size); -static void* (*next_memalign)(size_t alignment, size_t size); -static int (*next_posix_memalign)(void** memptr, size_t alignment, size_t size); -static void* (*next_valloc)(size_t size); -static void* (*next_pvalloc)(size_t size); -static void* (*next_aligned_alloc)(size_t alignment, size_t size); -static int (*next_malloc_stats)(); - -static void -grow_trace() -{ - pthread_mutex_lock(&growth_mutex); - - size_t old_buf_idx; - if (unlikely(total_entries == 0)) - old_buf_idx = 0; - else - old_buf_idx = ((total_entries) / GROWTH_ENTRIES) % 2; - size_t new_buf_size = (total_entries + GROWTH_ENTRIES) * sizeof(trace_t); - - /* remap old buffer - * hopefully no thread uses the old buffer anymore! - */ - if (out[old_buf_idx] == NULL) { - out[old_buf_idx] = (trace_t*) mmap(NULL, new_buf_size, PROT_WRITE, - MAP_FILE | MAP_SHARED, out_fd, 0); - if (out[old_buf_idx] == MAP_FAILED) { - perror("mapping new buf failed"); - exit(1); - } - } else { - size_t old_buf_size = (total_entries - GROWTH_ENTRIES) * sizeof(trace_t); - out[old_buf_idx] = (trace_t*) mremap((void*) out[old_buf_idx], old_buf_size, - new_buf_size, MREMAP_MAYMOVE); - if (out[old_buf_idx] == MAP_FAILED) { - perror("remapping old buf failed"); - exit(1); - } - } - - - if(ftruncate(out_fd, new_buf_size) != 0) { - perror("extending file failed"); - exit(1); - } - - total_entries += GROWTH_ENTRIES; - pthread_cond_broadcast(&growing); - pthread_mutex_unlock(&growth_mutex); -} - -static void -write_trace(char func, void* ptr, size_t size, size_t var_arg) -{ - - if (unlikely(tid == 0)) { - init_thread(); - } - - uint64_t idx = __atomic_fetch_add (&next_entry, 1, __ATOMIC_SEQ_CST); - if (idx == total_entries - GROWTH_THRESHOLD) { - grow_trace(); - // wait for growth completion - } else if (idx >= total_entries) { - pthread_mutex_lock(&growth_mutex); - while(idx >= total_entries) - pthread_cond_wait(&growing, &growth_mutex); - pthread_mutex_unlock(&growth_mutex); - } - - volatile trace_t* trace = &out[(idx / GROWTH_ENTRIES) % 2][idx]; - - trace->tid = tid; - trace->func = func; - trace->ptr = ptr; - trace->size = size; - trace->var_arg = var_arg; -} - -static void log_thread_termination(void* key __attribute__ ((unused))) -{ - write_trace(THREAD_TERMINATION, NULL, 0, 0); -} - -static void -trim_trace() -{ - uint64_t cur_size = next_entry * sizeof(trace_t); - if(ftruncate(out_fd, cur_size) != 0) { - perror("trimming file failed"); - } - close(out_fd); -} - -static void __attribute__((constructor)) -init() -{ - initializing = 1; - char* fname = getenv("CHATTYMALLOC_FILE"); - if (fname == NULL) - fname = "chattymalloc.trace"; - - out_fd = open(fname, - O_RDWR | O_TRUNC | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (out_fd == -1) { - perror("opening output file"); - exit(1); - } - - pthread_cond_init(&growing, NULL); - pthread_mutex_init(&growth_mutex, NULL); - - // init trace buffer - grow_trace(); - - next_malloc = dlsym(RTLD_NEXT, "malloc"); - next_free = dlsym(RTLD_NEXT, "free"); - next_calloc = dlsym(RTLD_NEXT, "calloc"); - next_realloc = dlsym(RTLD_NEXT, "realloc"); - next_memalign = dlsym(RTLD_NEXT, "memalign"); - next_posix_memalign = dlsym(RTLD_NEXT, "posix_memalign"); - next_valloc = dlsym(RTLD_NEXT, "valloc"); - next_pvalloc = dlsym(RTLD_NEXT, "pvalloc"); - next_aligned_alloc = dlsym(RTLD_NEXT, "aligned_alloc"); - next_malloc_stats = dlsym(RTLD_NEXT, "malloc_stats"); - - if (!next_malloc || !next_free || !next_calloc || !next_realloc || - !next_memalign) { - fprintf(stderr, "Can't load core functions with `dlsym`: %s\n", dlerror()); - exit(1); - } - if (!next_posix_memalign) - fprintf(stderr, "Can't load posix_memalign with `dlsym`: %s\n", dlerror()); - if (!next_valloc) - fprintf(stderr, "Can't load valloc with `dlsym`: %s\n", dlerror()); - if (!next_pvalloc) - fprintf(stderr, "Can't load pvalloc with `dlsym`: %s\n", dlerror()); - if (!next_aligned_alloc) - fprintf(stderr, "Can't load aligned_alloc with `dlsym`: %s\n", dlerror()); - if (!next_malloc_stats) - fprintf(stderr, "Can't load malloc_stats with `dlsym`: %s\n", dlerror()); - - atexit(trim_trace); - initializing = 0; -} - -void* -malloc(size_t size) -{ - if (next_malloc == NULL) { - if (!initializing) { - init(); - - } else { - void* retptr = tmpbuff + tmppos; - tmppos += size; - ++tmpallocs; - - if (tmppos < sizeof(tmpbuff)) { - return retptr; - } else { - fprintf(stderr, "%d in %d allocs\n", tmppos, tmpallocs); - fprintf(stderr, - "jcheck: too much memory requested during initialisation - " - "increase tmpbuff size\n"); - exit(1); - } - } - } - - void* ptr = next_malloc(size); - write_trace(MALLOC, ptr, size, 0); - return ptr; -} - -void -free(void* ptr) -{ - // something wrong if we call free before one of the allocators! - if (next_malloc == NULL) - init(); - if (!(ptr >= (void*)tmpbuff && ptr <= (void*)(tmpbuff + tmppos))) { - write_trace(FREE, ptr, 0, 0); - next_free(ptr); - } -} - -void* -realloc(void* ptr, size_t size) -{ - if (next_realloc == NULL) { - void* nptr = malloc(size); - if (nptr && ptr) { - memmove(nptr, ptr, size); - free(ptr); - } - return nptr; - } - - void* nptr = next_realloc(ptr, size); - write_trace(REALLOC, nptr, size, (size_t)ptr); - return nptr; -} - -void* -calloc(size_t nmemb, size_t size) -{ - if (next_calloc == NULL) { - void* ptr = malloc(nmemb * size); - if (ptr) - memset(ptr, 0, nmemb * size); - return ptr; - } - - void* ptr = next_calloc(nmemb, size); - write_trace(CALLOC, ptr, size, nmemb); - return ptr; -} - -void* -memalign(size_t alignment, size_t size) -{ - if (next_memalign == NULL) { - fprintf(stderr, "called memalign before or during init\n"); - exit(1); - } - - void* ptr = next_memalign(alignment, size); - write_trace(MEMALIGN, ptr, size, alignment); - return ptr; -} - -int -posix_memalign(void** memptr, size_t alignment, size_t size) -{ - if (next_posix_memalign == NULL) { - fprintf(stderr, "called posix_memalign before or during init\n"); - exit(1); - } - - int ret = next_posix_memalign(memptr, alignment, size); - write_trace(POSIX_MEMALIGN, *memptr, size, alignment); - return ret; -} - -void* -valloc(size_t size) -{ - if (next_valloc == NULL) { - fprintf(stderr, "called valloc before or during init"); - exit(1); - } - - void* ptr = next_valloc(size); - write_trace(VALLOC, ptr, size, 0); - return ptr; -} - -void* -pvalloc(size_t size) -{ - if (next_pvalloc == NULL) { - fprintf(stderr, "called pvalloc before or during init\n"); - exit(1); - } - - void* ptr = next_pvalloc(size); - write_trace(PVALLOC, ptr, size, 0); - return ptr; -} - -void* -aligned_alloc(size_t alignment, size_t size) -{ - if (next_aligned_alloc == NULL) { - fprintf(stderr, "called aligned_alloc before or during init\n"); - exit(1); - } - - void* ptr = next_aligned_alloc(alignment, size); - write_trace(ALIGNED_ALLOC, ptr, size, alignment); - return ptr; -} - -int -malloc_stats() -{ - if (next_malloc_stats == NULL) { - fprintf(stderr, "called malloc_stats before or during init\n"); - exit(1); - } - - fprintf(stderr, "chattymalloc by muhq\n"); - return next_malloc_stats(); -} diff --git a/src/chattymalloc.h b/src/chattymalloc.h deleted file mode 100644 index 5120193..0000000 --- a/src/chattymalloc.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2018-2020 Florian Fischer - -This file is part of allocbench. - -allocbench is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -allocbench is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with allocbench. If not, see . -*/ - -#include // uint8_t -#include // pid_t - -enum functions { - UNINITIALIZED, - MALLOC, - FREE, - REALLOC, - CALLOC, - MEMALIGN, - POSIX_MEMALIGN, - VALLOC, - PVALLOC, - ALIGNED_ALLOC, - THREAD_TERMINATION}; - -typedef struct trace { - void* ptr; - size_t size; - size_t var_arg; - pid_t tid; - char func; -} __attribute__((packed)) trace_t; - diff --git a/src/chattyparser.py b/src/chattyparser.py deleted file mode 100755 index 169463f..0000000 --- a/src/chattyparser.py +++ /dev/null @@ -1,420 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2018-2020 Florian Fischer -# -# This file is part of allocbench. -# -# allocbench is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# allocbench is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with allocbench. If not, see . -"""Parser and Plotter for the traces produced by chattymalloc""" - -import argparse -from enum import Enum -import os -import struct -import sys - -import matplotlib.pyplot as plt -import numpy as np - -CHECK_ALIGNMENT = None -EXPORT_TXT = False - - -class Function(Enum): - """Enum holding all trace events of chattymalloc""" - uninitialized = 0 - malloc = 1 - free = 2 - realloc = 3 - calloc = 4 - memalign = 5 - posix_memalign = 6 - valloc = 7 - pvalloc = 8 - aligned_alloc = 9 - thread_termination = 10 - - -class Trace: - """Class representing the chattymalloc trace_t struct""" - - fmt = 'Pnnib' - size = struct.calcsize(fmt) - - def __init__(self, ptr, size, var_arg, tid, func): - self.ptr = ptr - self.size = size - self.var_arg = var_arg - self.tid = tid - self.func = Function(func) - - @classmethod - def unpack(cls, buf): - """Create a new Trace object from bytes""" - return Trace(*struct.unpack(Trace.fmt, buf)) - - @classmethod - def iter_unpack(cls, buf): - """Create a iterator returning Trace object from bytes""" - for values in struct.iter_unpack(Trace.fmt, buf): - yield Trace(*values) - - def __str__(self): - if self.func == Function.realloc: - var_arg = hex(self.var_arg) - else: - var_arg = self.var_arg - return f"{self.tid}: {self.depth} {self.func.name} {hex(self.ptr)} {self.size} {var_arg}" - -def update_cache_lines(cache_lines, trace, size): - """mark or unmark all cache lines spanned by this allocation""" - if cache_lines is None: - return "" - - start = trace.ptr - end = start + abs(size) - msg = "" - - cache_line = start & ~(64 - 1) - assert cache_line % 64 == 0 - while cache_line < end: - if trace.func != Function.free: - if cache_line not in cache_lines or cache_lines[cache_line] == []: - cache_lines[cache_line] = [trace.tid] - # false sharing - else: - if trace.tid not in cache_lines[cache_line]: - msg += (f"WARNING: cache line {hex(cache_line)} is shared " - f"between {set(cache_lines[cache_line] + [trace.tid])}\n") - cache_lines[cache_line].append(trace.tid) - else: - if trace.tid in cache_lines[cache_line]: - cache_lines[cache_line].remove(trace.tid) - else: - #If cache line is only owned by one thread it should be save to remove it - if len(cache_lines[cache_line]) == 1: - del cache_lines[cache_line] - elif len(cache_lines[cache_line]) == 0: - msg += f"INTERNAL ERROR: freeing not owned cache line\n" - #TODO fix passed allocations - else: - pass - - # print(hex(cache_line), cache_lines[cache_line], end=" ") - cache_line += 64 - - # print() - return msg - - -def record_allocation(trace, context): - """add allocation to histogram or total requested memory - - trace - Trace object ro record - context - dict holding all data structures used for parsing - allocations - dict of life allocations mapping their pointer to their size - hist - dict mapping allocation sizes to their occurrence - realloc_hist - dict mapping the two realloc sizes to their occurence - total_size - list of total requested memory till last recorded function call - cache_lines - dict of cache lines mapped to the owning tids - req_size - dict mapping sizes to their individual total requested memory - """ - - # mandatory - allocations = context.setdefault("allocations", []) - - # optional - hist = context.get("hist", None) - realloc_hist = context.get("realloc_hist", None) - total_size = context.get("total_size", None) - cache_lines = context.get("cache_lines", None) - req_sizes = context.get("req_sizes", {}) - - size = 0 - msg = "" - - if trace.func == Function.thread_termination: - return "" - - if trace.func == Function.uninitialized: - return "WARNING: empty entry\n" - - # (potential) free of a pointer - if trace.func in (Function.free, Function.realloc): - if trace.func == Function.realloc: - freed_ptr = trace.var_arg - else: - freed_ptr = trace.ptr - - # get size and delete old pointer - if freed_ptr != 0: - if freed_ptr not in allocations: - msg = f"WARNING: free of invalid pointer {freed_ptr:x}\n" - else: - size = allocations.pop(freed_ptr) * -1 - msg = update_cache_lines(cache_lines, trace, size) - - # allocations - if trace.func != Function.free and trace.ptr != 0: - # check for alignment - if CHECK_ALIGNMENT: - if (trace.ptr - CHECK_ALIGNMENT[1]) % CHECK_ALIGNMENT[0] != 0: - msg += (f"WARNING: ptr: {trace.ptr:x} is not aligned to" - f" {CHECK_ALIGNMENT[0]} with offset {CHECK_ALIGNMENT[1]}\n") - - if trace.func == Function.calloc: - allocation_size = trace.var_arg * trace.size - else: - allocation_size = trace.size - - # realloc returning the same pointer will not be reported because it has been freed already - if trace.ptr in allocations: - msg += f"WARNING: returned ptr {trace.ptr:x} is already a live allocation\n" - - allocations[trace.ptr] = allocation_size - - msg += update_cache_lines(cache_lines, trace, allocation_size) - - # update hist - if hist is not None and trace.func != Function.free: - hist[allocation_size] = hist.get(allocation_size, 0) + 1 - - # special case realloc - if trace.func == Function.realloc: - if realloc_hist is not None: - realloc_hist[(size, allocation_size)] = realloc_hist.get( - (size, allocation_size), 0) - - size += allocation_size - - # update total size - if total_size is not None: - total_size.append(total_size[-1] + size) - - for req_size in req_sizes: - if size == req_size: - req_sizes[req_size].append(req_sizes[req_size][-1] + size) - else: - req_sizes[req_size].append(req_sizes[req_size][-1]) - - return msg - - -def parse(path="chattymalloc.txt", - hist=True, - track_total=True, - track_calls=True, - realloc_hist=True, - cache_lines=True, - req_sizes=None): - """parse a chattymalloc trace - - :returns: a context dict containing the histogram, a realloc histogram, - a function call histogram, total live memory per function call, - a dict mapping cache_lines to their owning TIDs - """ - # context dictionary holding our parsed information - context = {} - - # Dictionary to track all live allocations - context["allocations"] = {} - - if track_calls: - # function call histogram - context["calls"] = {f: 0 for f in Function} - - if track_total: - # List of total live memory per operation - context["total_size"] = [0] - - if req_sizes: - # allocation sizes to track - context["req_sizes"] = req_sizes - - if hist: - # Dictionary mapping allocation sizes to the count - context["hist"] = {} - - if realloc_hist: - # Dictionary mapping realloc sizes to their count - context["realloc_hist"] = {} - - if cache_lines: - # Dictionary mapping cache lines to their owning TIDs - context["cache_lines"] = {} - - if EXPORT_TXT: - plain_file = open(path+".txt", "w") - - with open(path, "rb") as trace_file: - total_entries = os.stat(trace_file.fileno()).st_size // Trace.size - update_interval = int(total_entries * 0.0005) - if update_interval == 0: - update_interval = 1 - - i = 0 - entry = trace_file.read(Trace.size) - while entry != b'': - # print process - if i % update_interval == 0: - print(f"\r[{i} / {total_entries}] {(i/total_entries)*100:.2f}% parsed ...", end="") - - try: - trace = Trace.unpack(entry) - - if track_calls: - context["calls"][trace.func] += 1 - msg = record_allocation(trace, context) - if msg: - print(f"entry {i}: {msg}", file=sys.stderr, end="") - - if EXPORT_TXT: - print(trace, file=plain_file) - - except ValueError as err: - print(f"ERROR: {err} in entry {i}: {entry}", file=sys.stderr) - - - i += 1 - entry = trace_file.read(Trace.size) - - print(f"\r[{i} / {total_entries}] {(i / total_entries) * 100:.2f}% parsed ...") - if EXPORT_TXT: - plain_file.close() - return context - - -def plot(path): - """Plot a histogram and a memory profile of the given chattymalloc trace""" - result = parse(path=path) - hist = result["hist"] - - plot_hist_ascii(f"{path}.hist", hist, result["calls"]) - - top5 = [t[1] for t in sorted([(n, s) for s, n in hist.items()])[-5:]] - - plot_profile(path, path + ".profile.png", top5) - - -def plot_profile(trace_path, plot_path, sizes): - """Plot a memory profile of the total memory and the top 5 sizes""" - - res = parse(path=trace_path, - hist=False, - realloc_hist=False, - cache_lines=False, - req_sizes={s: [0] for s in sizes}) - - total_size = np.array(res["total_size"]) - del res["total_size"] - - x_vals = range(0, len(total_size)) - - plt.plot(x_vals, - total_size / 1000, - marker='', - linestyle='-', - label="Total requested") - - for size in sizes: - req_size = np.array(res["req_sizes"][size]) - del res["req_sizes"][size] - plt.plot(x_vals, req_size / 1000, label=size) - - plt.legend(loc="lower center") - plt.xlabel("Allocations") - plt.ylabel("mem in kb") - plt.title("Memusage profile") - plt.savefig(plot_path) - plt.clf() - - -def plot_hist_ascii(path, hist, calls): - """Plot an ascii histogram""" - bins = {} - for size in sorted(hist): - size_class = size // 16 - bins[size_class] = bins.get(size_class, 0) + hist[size] - - with open(path, "w") as hist_file: - print("Total function calls:", sum(calls.values()), file=hist_file) - for func, func_calls in calls.items(): - print(func.name, func_calls, file=hist_file) - - print(file=hist_file) - - total = sum(hist.values()) - top10 = [t[1] for t in sorted([(n, s) for s, n in hist.items()])[-10:]] - top10_total = sum([hist[size] for size in top10]) - - print( - f"Top 10 allocation sizes {(top10_total/total)*100:.2f}% of all allocations", - file=hist_file) - for i, size in enumerate(reversed(top10)): - print(f"{i+1}. {size} B occurred {hist[size]} times", - file=hist_file) - print(file=hist_file) - - for i in [64, 1024, 4096]: - allocations = sum([n for s, n in hist.items() if s <= i]) - print( - f"allocations <= {i}: {allocations} {(allocations/total)*100:.2f}%", - file=hist_file) - print(file=hist_file) - - print("Histogram of sizes:", file=hist_file) - sbins = sorted(bins) - binmaxlength = len(str(sbins[-1])) + 1 - amountmaxlength = str(len(str(sorted(bins.values())[-1]))) - for current_bin in sbins: - perc = bins[current_bin] / total * 100 - binsize = f"{{:<{binmaxlength}}} - {{:>{binmaxlength}}}" - print(binsize.format(current_bin * 16, (current_bin + 1) * 16 - 1), - end=" ", - file=hist_file) - amount = "{:<" + amountmaxlength + "} {:.2f}% {}" - print(amount.format(bins[current_bin], perc, '*' * int(perc / 2)), - file=hist_file) - - -if __name__ == "__main__": - # Code duplication with src.util.print_license_and_exit - # to keep chattyparser independent from allocbench - if "--license" in sys.argv: - print("Copyright (C) 2018-2019 Florian Fischer") - print( - "License GPLv3: GNU GPL version 3 " - ) - sys.exit(0) - - parser = argparse.ArgumentParser(description="parse and analyse chattymalloc traces") - parser.add_argument("trace", - help="binary trace file created by chattymalloc") - parser.add_argument("--alignment", - nargs=2, - help="export to plain text format") - parser.add_argument("--txt", - help="export to plain text format", - action="store_true") - parser.add_argument("-v", "--verbose", help="more output", action='count') - parser.add_argument("--license", - help="print license info and exit", - action='store_true') - - args = parser.parse_args() - - if args.alignment: - CHECK_ALIGNMENT = [int(x) for x in args.alignment] - EXPORT_TXT = args.txt - plot(args.trace) -- cgit v1.2.3