| /* | |
| * 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.errno : errno; | |
| import core.stdc.stdio : fclose, FILE, fopen, fprintf, snprintf, stderr, stdout; | |
| import core.stdc.stdlib : free, malloc, qsort, realloc; | |
| 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 == "\0" ? cast()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 | |
| { | |
| const err = errno; | |
| fprintf(cast()stderr, "cannot write profilegc log file '%.*s' (errno=%d)", | |
| cast(int) logfilename.length, | |
| logfilename.ptr, | |
| cast(int) err); | |
| } | |
| } | |
| } |