/* * Data collection and report generation for * -profile=gc * switch * * Copyright: Copyright Digital Mars 2015 - 2015. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Andrei Alexandrescu and Walter Bright * Source: $(DRUNTIMESRC rt/_profilegc.d) */ module rt.profilegc; private: import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import core.exception : onOutOfMemoryError; import core.internal.container.hashtab; struct Entry { ulong count, size; } char[] buffer; HashTab!(const(char)[], Entry) newCounts; __gshared { HashTab!(const(char)[], Entry) globalNewCounts; string logfilename = "profilegc.log"; } /**** * Set file name for output. * A file name of "" means write results to stdout. * Params: * name = file name */ extern (C) void profilegc_setlogfilename(string name) { logfilename = name ~ "\0"; } public void accumulate(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow { if (sz == 0) return; char[3 * line.sizeof + 1] buf = void; auto buflen = snprintf(buf.ptr, buf.length, "%u", line); auto length = type.length + 1 + funcname.length + 1 + file.length + 1 + buflen; if (length > buffer.length) { // Enlarge buffer[] so it is big enough assert(buffer.length > 0 || buffer.ptr is null); auto p = cast(char*)realloc(buffer.ptr, length); if (!p) onOutOfMemoryError(); buffer = p[0 .. length]; } // "type funcname file:line" buffer[0 .. type.length] = type[]; buffer[type.length] = ' '; buffer[type.length + 1 .. type.length + 1 + funcname.length] = funcname[]; buffer[type.length + 1 + funcname.length] = ' '; buffer[type.length + 1 + funcname.length + 1 .. type.length + 1 + funcname.length + 1 + file.length] = file[]; buffer[type.length + 1 + funcname.length + 1 + file.length] = ':'; buffer[type.length + 1 + funcname.length + 1 + file.length + 1 .. type.length + 1 + funcname.length + 1 + file.length + 1 + buflen] = buf[0 .. buflen]; if (auto pcount = cast(string)buffer[0 .. length] in newCounts) { // existing entry pcount.count++; pcount.size += sz; } else { auto key = (cast(char*) malloc(char.sizeof * length))[0 .. length]; key[] = buffer[0..length]; newCounts[key] = Entry(1, sz); // new entry } } // Merge thread local newCounts into globalNewCounts static ~this() { if (newCounts.length) { synchronized { foreach (name, entry; newCounts) { if (!(name in globalNewCounts)) globalNewCounts[name] = Entry.init; globalNewCounts[name].count += entry.count; globalNewCounts[name].size += entry.size; } } newCounts.reset(); } free(buffer.ptr); buffer = null; } // Write report to stderr shared static ~this() { static struct Result { const(char)[] name; Entry entry; // qsort() comparator to sort by count field extern (C) static int qsort_cmp(scope const void *r1, scope const void *r2) @nogc nothrow { auto result1 = cast(Result*)r1; auto result2 = cast(Result*)r2; long cmp = result2.entry.size - result1.entry.size; if (cmp) return cmp < 0 ? -1 : 1; cmp = result2.entry.count - result1.entry.count; if (cmp) return cmp < 0 ? -1 : 1; if (result2.name == result1.name) return 0; // ascending order for names reads better return result2.name > result1.name ? -1 : 1; } } size_t size = globalNewCounts.length; Result[] counts = (cast(Result*) malloc(size * Result.sizeof))[0 .. size]; scope(exit) free(counts.ptr); size_t i; foreach (name, entry; globalNewCounts) { counts[i].name = name; counts[i].entry = entry; ++i; } if (counts.length) { qsort(counts.ptr, counts.length, Result.sizeof, &Result.qsort_cmp); FILE* fp = logfilename.length == 0 ? stdout : fopen((logfilename).ptr, "w"); if (fp) { fprintf(fp, "bytes allocated, allocations, type, function, file:line\n"); foreach (ref c; counts) { fprintf(fp, "%15llu\t%15llu\t%8.*s\n", cast(ulong)c.entry.size, cast(ulong)c.entry.count, cast(int) c.name.length, c.name.ptr); } if (logfilename.length) fclose(fp); } else fprintf(stderr, "cannot write profilegc log file '%.*s'", cast(int) logfilename.length, logfilename.ptr); } }