171 lines
4.8 KiB
D
171 lines
4.8 KiB
D
|
/*
|
||
|
* 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);
|
||
|
}
|
||
|
}
|