From 92f9e30dc210f45880b61d5c6472e631166fa883 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Fri, 14 Sep 2018 18:01:55 +0200 Subject: add usefull chattymalloc support and --nolibmemusage flag improve bench.py improve process_stdout hook -> process_output rename perf_cmd -> measure_cmd --- bench.py | 7 +-- benchmark.py | 99 +++++++++++++++++++------------------- chattymalloc.c | 80 +++++++++++++++++++++---------- chattyparser.py | 144 ++++++++++++++++++++++++++++++++++++++------------------ dj_trace.py | 33 +++++++------ falsesharing.py | 2 +- larson.py | 2 +- mysql.py | 4 +- 8 files changed, 229 insertions(+), 142 deletions(-) diff --git a/bench.py b/bench.py index b661a56..82b640d 100755 --- a/bench.py +++ b/bench.py @@ -22,6 +22,7 @@ parser.add_argument("-b", "--benchmarks", help="benchmarks to run", nargs='+') parser.add_argument("-ns", "--nosum", help="don't produce plots", action='store_true') parser.add_argument("-sd", "--summarydir", help="directory where all plots and the summary go", type=str) parser.add_argument("-a", "--analyse", help="collect allocation sizes", action='store_true') +parser.add_argument("--nolibmemusage", help="don't use libmemusage to analyse", action='store_true') def main(): args = parser.parse_args() @@ -44,16 +45,16 @@ def main(): if args.analyse and hasattr(bench, "analyse") and callable(bench.analyse): print("Analysing", bench.name, "...") - bench.analyse(verbose=args.verbose) + analyse_args = {"nolibmemusage": args.nolibmemusage, "verbose": args.verbose} + bench.analyse(**analyse_args) - print("Running", bench.name, "...") if not bench.run(runs=args.runs, verbose=args.verbose): continue if args.save: bench.save() - if not args.nosum: + if not args.nosum and not (args.runs < 1 and not args.load): print("Summarizing", bench.name, "...") bench.summary(args.summarydir) diff --git a/benchmark.py b/benchmark.py index caa25fd..2eb2436 100644 --- a/benchmark.py +++ b/benchmark.py @@ -4,6 +4,7 @@ import csv import itertools import os import pickle +import shutil import subprocess from common_targets import common_targets @@ -14,7 +15,7 @@ class Benchmark (object): "name" : "default_benchmark", "description" : "This is the default benchmark description please add your own useful one.", - "perf_cmd" : "perf stat -x, -dd ", + "measure_cmd" : "perf stat -x, -dd ", "analyse_cmd" : "memusage -p {} -t ", "cmd" : "true", "targets" : common_targets, @@ -114,64 +115,63 @@ class Benchmark (object): yield p - def analyse(self, verbose=False): - for perm in self.iterate_args(): + def analyse(self, verbose=False, nolibmemusage=True): + if not nolibmemusage and not shutil.which("memusage"): + print("memusage not found. Using chattymalloc.") + libmemusage = False + + if nolibmemusage: + import chattyparser + actual_cmd = "" + old_preload = os.environ.get("LD_PRELOAD", None) + os.environ["LD_PRELOAD"] = "build/chattymalloc.so" + + n = len(list(self.iterate_args())) + for i, perm in enumerate(self.iterate_args()): + print(i + 1, "of", n, "\r", end='') perm = perm._asdict() file_name = self.name + "." file_name += ".".join([str(x) for x in perm.values()]) file_name += ".memusage" - actual_cmd = self.analyse_cmd.format(file_name + ".png") + if not nolibmemusage: + actual_cmd = self.analyse_cmd.format(file_name + ".png") + if "binary_suffix" in self.cmd: perm["binary_suffix"] = "" actual_cmd += self.cmd.format(**perm) - with open(file_name + ".hist", "w") as f: - res = subprocess.run(actual_cmd.split(), + res = subprocess.run(actual_cmd.split(), stdout=subprocess.PIPE, - stderr=f, + stderr=subprocess.PIPE, universal_newlines=True) - if res.returncode != 0: - print(actual_cmd, "failed.") - print("Aborting analysing.") - print("You may look at", file_name + ".hist", "to fix this.") - return - - def parse_chattymalloc_data(self, path="chattymalloc.data"): - hist = {} - total = 0 - with open(path, "r") as f: - for l in f.readlines(): - total += 1 + if res.returncode != 0: + print(actual_cmd, "failed.") + print("Stdout:", res.stdout) + print("Stderr:", res.stderr) + print("Aborting analysing.") + return + + if nolibmemusage: try: - n = int(l) - except ValueError: - pass - hist[n] = hist.get(n, 0) + 1 - hist["total"] = total - return hist - - def plot_hist_ascii(self, hist, path): - total = hist["total"] - del(hist["total"]) - bins = {} - bin = 1 - for size in sorted(hist): - if int(size) > bin * 16: - bin += 1 - bins[bin] = bins.get(bin, 0) + hist[size] - hist["total"] = total - - with open(path, "w") as f: - print("Total malloc calls:", total, file=f) - print("Histogram of sizes:", file=f) - for b in sorted(bins): - perc = bins[b]/total*100 - print((b-1)*16, '-', b*16-1, '\t', bins[b], - perc, '%', '*'*int(perc/2), file=f) + hist, calls, reqsize, top5reqsize = chattyparser.parse() + top5 = [s[1] for s in sorted([(n, s) for s, n in hist.items()])] + hist, calls, reqsize, top5reqsize = chattyparser.parse(track_top5=top5) + + chattyparser.plot_hist_ascii(hist, calls, file_name + ".hist") + chattyparser.plot_profile(reqsize, top5reqsize, file_name + ".profile.png") + except MemoryError as memerr: + print("Can't Analyse", actual_cmd, "with chattymalloc because", + "to much memory would be needed.") + continue + + os.environ["LD_PRELOAD"] = old_preload or "" + print() def run(self, verbose=False, runs=5): + if runs > 0: + print("Running", self.name, "...") n = len(list(self.iterate_args())) * len(self.targets) for run in range(1, runs + 1): print(str(run) + ". run") @@ -190,9 +190,9 @@ class Benchmark (object): for perm in self.iterate_args(): i += 1 - print(i, "of", n, "\r", end='') + print(i, "of", n,"\r", end='') - actual_cmd = self.perf_cmd + actual_cmd = self.measure_cmd perm_dict = perm._asdict() perm_dict.update(t) @@ -227,11 +227,12 @@ class Benchmark (object): break os.remove("status") - if hasattr(self, "process_stdout"): - self.process_stdout(result, res.stdout, verbose) + if hasattr(self, "process_output"): + self.process_output(result, res.stdout, res.stderr, + tname, perm, verbose) # Parse perf output if available - if self.perf_cmd != "": + if self.measure_cmd != self.defaults["measure_cmd"]: csvreader = csv.reader(res.stderr.splitlines(), delimiter=',') for row in csvreader: # Split of the user/kernel space info to be better portable diff --git a/chattymalloc.c b/chattymalloc.c index 77732a1..54708d6 100644 --- a/chattymalloc.c +++ b/chattymalloc.c @@ -1,16 +1,20 @@ #define _GNU_SOURCE #include +#include +#include +#include #include #include #include #include +#include static char tmpbuff[1024]; static unsigned long tmppos = 0; static unsigned long tmpallocs = 0; -static FILE* out = NULL; -static int in_fprintf = 0; +static int out = -1; +static int prevent_recursion = 0; /*========================================================= * * interception points @@ -20,22 +24,42 @@ static void * (*myfn_malloc)(size_t size); static void (*myfn_free)(void* ptr); static void * (*myfn_calloc)(size_t nmemb, size_t size); static void * (*myfn_realloc)(void* ptr, size_t size); +static void * (*myfn_memalign)(size_t alignment, size_t size); + +static void write_output(const char* fmt, ...) +{ + if (!prevent_recursion) + { + prevent_recursion = 1; + + /* lockf(out, F_LOCK, 0); */ + + va_list args; + va_start(args, fmt); + vdprintf(out, fmt, args); + va_end(args); + + /* lockf(out, F_ULOCK, 0); */ + prevent_recursion = 0; + } +} static void init() { - out = fopen("chattymalloc.data", "w"); - if (out == NULL) + out = open("chattymalloc.data", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (out == -1) { - fprintf(stderr, "failed to open output file\n"); + fprintf(stderr, "failed to open output file with %d\n", errno); exit(1); } myfn_malloc = dlsym(RTLD_NEXT, "malloc"); myfn_free = dlsym(RTLD_NEXT, "free"); - myfn_calloc = dlsym(RTLD_NEXT, "calloc"); - myfn_realloc = dlsym(RTLD_NEXT, "realloc"); + myfn_calloc = dlsym(RTLD_NEXT, "calloc"); + myfn_realloc = dlsym(RTLD_NEXT, "realloc"); + myfn_memalign = dlsym(RTLD_NEXT, "memalign"); - if (!myfn_malloc || !myfn_free || !myfn_calloc || !myfn_realloc) + if (!myfn_malloc || !myfn_free || !myfn_calloc || !myfn_realloc || !myfn_memalign) { fprintf(stderr, "Error in `dlsym`: %s\n", dlerror()); exit(1); @@ -66,19 +90,15 @@ void *malloc(size_t size) } 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); } } } - if (!in_fprintf) - { - in_fprintf = 1; - fprintf(out, "%d\n", size); - in_fprintf = 0; - } void *ptr = myfn_malloc(size); + write_output("m %zu %p\n", size, ptr); return ptr; } @@ -88,7 +108,10 @@ void free(void *ptr) if (myfn_malloc == NULL) init(); if (!(ptr >= (void*) tmpbuff && ptr <= (void*)(tmpbuff + tmppos))) + { + write_output("f %p\n", ptr); myfn_free(ptr); + } } void* realloc(void *ptr, size_t size) @@ -104,13 +127,9 @@ void* realloc(void *ptr, size_t size) return nptr; } - if (!in_fprintf) - { - in_fprintf = 1; - fprintf(out, "%d\n", size); - in_fprintf = 0; - } - return myfn_realloc(ptr, size); + void* nptr = myfn_realloc(ptr, size); + write_output("r %p %zu %p\n", ptr, size, nptr); + return nptr; } void* calloc(size_t nmemb, size_t size) @@ -123,11 +142,20 @@ void* calloc(size_t nmemb, size_t size) return ptr; } - if (!in_fprintf) + void* ptr = myfn_calloc(nmemb, size); + write_output("c %zu %zu %p\n", nmemb, size, ptr); + return ptr; +} + +void* memalign(size_t alignment, size_t size) +{ + if (myfn_memalign == NULL) { - in_fprintf = 1; - fprintf(out, "%d\n", size*nmemb); - in_fprintf = 0; + fprintf(stderr, "called memalign before or during init"); + exit(1); } - return myfn_calloc(nmemb, size); + + void* ptr = myfn_memalign(alignment, size); + write_output("mm %zu %zu %p\n", alignment, size, ptr); + return ptr; } diff --git a/chattyparser.py b/chattyparser.py index 02ac6f6..fc6975a 100644 --- a/chattyparser.py +++ b/chattyparser.py @@ -1,88 +1,140 @@ import re - -rss_re = re.compile("^VmRSS:\s+(\d+) kB$") +import matplotlib.pyplot as plt +import numpy as np ptr = "(?:0x)?(?P(?:\w+)|(?:\(nil\)))" size = "(?P\d+)" -time = "(?P