1 /*
2 * Data collection and report generation for
3 * -profile=gc
4 * switch
5 *
6 * Copyright: Copyright Digital Mars 2015 - 2015.
7 * License: Distributed under the
8 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
9 * (See accompanying file LICENSE)
10 * Authors: Andrei Alexandrescu and Walter Bright
11 * Source: $(DRUNTIMESRC rt/_profilegc.d)
12 */
13
14 module rt.profilegc;
15
16 private:
17
18 import core.stdc.stdio;
19 import core.stdc.stdlib;
20 import core.stdc.string;
21
22 import core.exception : onOutOfMemoryError;
23 import core.internal.container.hashtab;
24
25 struct Entry { ulong count, size; }
26
27 char[] buffer;
28 HashTab!(const(char)[], Entry) newCounts;
29
30 __gshared
31 {
32 HashTab!(const(char)[], Entry) globalNewCounts;
33 string logfilename = "profilegc.log";
34 }
35
36 /****
37 * Set file name for output.
38 * A file name of "" means write results to stdout.
39 * Params:
40 * name = file name
41 */
42
profilegc_setlogfilename(string name)43 extern (C) void profilegc_setlogfilename(string name)
44 {
45 logfilename = name ~ "\0";
46 }
47
accumulate(string file,uint line,string funcname,string type,ulong sz)48 public void accumulate(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow
49 {
50 if (sz == 0)
51 return;
52
53 char[3 * line.sizeof + 1] buf = void;
54 auto buflen = snprintf(buf.ptr, buf.length, "%u", line);
55
56 auto length = type.length + 1 + funcname.length + 1 + file.length + 1 + buflen;
57 if (length > buffer.length)
58 {
59 // Enlarge buffer[] so it is big enough
60 assert(buffer.length > 0 || buffer.ptr is null);
61 auto p = cast(char*)realloc(buffer.ptr, length);
62 if (!p)
63 onOutOfMemoryError();
64 buffer = p[0 .. length];
65 }
66
67 // "type funcname file:line"
68 buffer[0 .. type.length] = type[];
69 buffer[type.length] = ' ';
70 buffer[type.length + 1 ..
71 type.length + 1 + funcname.length] = funcname[];
72 buffer[type.length + 1 + funcname.length] = ' ';
73 buffer[type.length + 1 + funcname.length + 1 ..
74 type.length + 1 + funcname.length + 1 + file.length] = file[];
75 buffer[type.length + 1 + funcname.length + 1 + file.length] = ':';
76 buffer[type.length + 1 + funcname.length + 1 + file.length + 1 ..
77 type.length + 1 + funcname.length + 1 + file.length + 1 + buflen] = buf[0 .. buflen];
78
79 if (auto pcount = cast(string)buffer[0 .. length] in newCounts)
80 { // existing entry
81 pcount.count++;
82 pcount.size += sz;
83 }
84 else
85 {
86 auto key = (cast(char*) malloc(char.sizeof * length))[0 .. length];
87 key[] = buffer[0..length];
88 newCounts[key] = Entry(1, sz); // new entry
89 }
90 }
91
92 // Merge thread local newCounts into globalNewCounts
~this()93 static ~this()
94 {
95 if (newCounts.length)
96 {
97 synchronized
98 {
99 foreach (name, entry; newCounts)
100 {
101 if (!(name in globalNewCounts))
102 globalNewCounts[name] = Entry.init;
103
104 globalNewCounts[name].count += entry.count;
105 globalNewCounts[name].size += entry.size;
106 }
107 }
108 newCounts.reset();
109 }
110 free(buffer.ptr);
111 buffer = null;
112 }
113
114 // Write report to stderr
~this()115 shared static ~this()
116 {
117 static struct Result
118 {
119 const(char)[] name;
120 Entry entry;
121
122 // qsort() comparator to sort by count field
123 extern (C) static int qsort_cmp(scope const void *r1, scope const void *r2) @nogc nothrow
124 {
125 auto result1 = cast(Result*)r1;
126 auto result2 = cast(Result*)r2;
127 long cmp = result2.entry.size - result1.entry.size;
128 if (cmp) return cmp < 0 ? -1 : 1;
129 cmp = result2.entry.count - result1.entry.count;
130 if (cmp) return cmp < 0 ? -1 : 1;
131 if (result2.name == result1.name) return 0;
132 // ascending order for names reads better
133 return result2.name > result1.name ? -1 : 1;
134 }
135 }
136
137 size_t size = globalNewCounts.length;
138 Result[] counts = (cast(Result*) malloc(size * Result.sizeof))[0 .. size];
139 scope(exit)
140 free(counts.ptr);
141
142 size_t i;
143 foreach (name, entry; globalNewCounts)
144 {
145 counts[i].name = name;
146 counts[i].entry = entry;
147 ++i;
148 }
149
150 if (counts.length)
151 {
152 qsort(counts.ptr, counts.length, Result.sizeof, &Result.qsort_cmp);
153
154 FILE* fp = logfilename.length == 0 ? stdout : fopen((logfilename).ptr, "w");
155 if (fp)
156 {
157 fprintf(fp, "bytes allocated, allocations, type, function, file:line\n");
158 foreach (ref c; counts)
159 {
160 fprintf(fp, "%15llu\t%15llu\t%8.*s\n",
161 cast(ulong)c.entry.size, cast(ulong)c.entry.count,
162 cast(int) c.name.length, c.name.ptr);
163 }
164 if (logfilename.length)
165 fclose(fp);
166 }
167 else
168 fprintf(stderr, "cannot write profilegc log file '%.*s'", cast(int) logfilename.length, logfilename.ptr);
169 }
170 }
171