1 /*
2 A tiny hack to install a GC proxy and track all allocations, gathering some
3 stats and logging to given File (or stdout, by default).
4
5 Use it like this:
6 void myCode() {
7 auto tracker = allocsTracker();
8 ... // do some work
9 // while `tracker` is alive all allocations will be shown
10 }
11
12 Alternatively use pair of functions
13 startTrackingAllocs() and stopTrackingAllocs().
14
15 */
16
17 module trackallocs;
18 import std.stdio, core.memory, core.stdc.string, std.traits, std.range, std.algorithm;
19
20 //stats
21 __gshared ulong allocs_mc = 0; // numbers of calls for malloc, qalloc and calloc
22 __gshared ulong allocs_qc = 0;
23 __gshared ulong allocs_cc = 0;
24 __gshared ulong allocs_msz = 0; // total numbers of bytes requested
25 __gshared ulong allocs_qsz = 0;
26 __gshared ulong allocs_csz = 0;
27
28 void clearAllocStats() {
29 allocs_mc = 0; allocs_qc = 0; allocs_cc = 0;
30 allocs_msz = 0; allocs_qsz = 0; allocs_csz = 0;
31 }
32
33 void startTrackingAllocs(File logFile = stdout) {
34 allocs_file = logFile;
35 if (myProxy.gc_calloc is null) {
36 initGcProxy();
37 myProxy.gc_malloc = &genAlloc!("m");
38 myProxy.gc_qalloc = &genAlloc!("q");
39 myProxy.gc_calloc = &genAlloc!("c");
40 }
41 *pproxy = &myProxy;
42 logFile.writeln("allocs tracking started");
43 }
44
45 void stopTrackingAllocs() {
46 *pproxy = null;
47 allocs_file.writeln("allocs tracking ended");
48 }
49
50 auto allocsTracker(File logFile = stdout) // RAII version: starts tracking now, ends it when returned value dies
51 {
52 struct S {
53 ~this() { stopTrackingAllocs(); }
54 @disable this(this);
55 }
56 startTrackingAllocs(logFile);
57 return S();
58 }
59
60 private: //////////////////////////////////////////////////////////////////////
61 alias BlkInfo = GC.BlkInfo;
62
63 struct Proxy // taken from proxy.d (d runtime)
64 {
65 extern (C)
66 {
67 void function() gc_enable;
68 void function() gc_disable;
69 void function() gc_collect;
70 void function() gc_minimize;
71
72 uint function(void*) gc_getAttr;
73 uint function(void*, uint) gc_setAttr;
74 uint function(void*, uint) gc_clrAttr;
75
76 void* function(size_t, uint, const TypeInfo) gc_malloc;
77 BlkInfo function(size_t, uint, const TypeInfo) gc_qalloc;
78 void* function(size_t, uint, const TypeInfo) gc_calloc;
79 void* function(void*, size_t, uint ba, const TypeInfo) gc_realloc;
80 size_t function(void*, size_t, size_t, const TypeInfo) gc_extend;
81 size_t function(size_t) gc_reserve;
82 void function(void*) gc_free;
83
84 void* function(void*) gc_addrOf;
85 size_t function(void*) gc_sizeOf;
86
87 BlkInfo function(void*) gc_query;
88
89 void function(void*) gc_addRoot;
90 void function(void*, size_t, const TypeInfo) gc_addRange;
91
92 void function(void*) gc_removeRoot;
93 void function(void*) gc_removeRange;
94 void function(in void[]) gc_runFinalizers;
95 }
96 }
97
98 __gshared Proxy myProxy;
99 __gshared Proxy *pOrg; // pointer to original Proxy of runtime
100 __gshared Proxy** pproxy; // should point to proxy var in runtime (proxy.d)
101 __gshared File allocs_file; // where to write messages
102
103 template FunArgsTypes(string funname) {
104 alias FunType = typeof(*__traits(getMember, myProxy, funname));
105 alias FunArgsTypes = ParameterTypeTuple!FunType;
106 }
107
108 extern(C) {
109 Proxy* gc_getProxy();
110
111 auto genCall(string funname)(FunArgsTypes!funname args) {
112 *pproxy = null;
113 scope(exit) *pproxy = &myProxy;
114 return __traits(getMember, pOrg, funname)(args);
115 }
116
117 auto genAlloc(string letter)(size_t sz, uint ba, const TypeInfo ti) {
118 *pproxy = null;
119 scope(exit) *pproxy = &myProxy;
120
121 mixin("alias cc = allocs_" ~ letter ~ "c;");
122 mixin("alias asz = allocs_" ~ letter ~ "sz;");
123 allocs_file.writefln("gc_"~letter~"alloc(%d, %d) :%d %s", sz, ba, cc, ti is null ? "" : ti.toString());
124 asz += sz; cc++;
125 return __traits(getMember, pOrg, "gc_"~letter~"alloc")(sz, ba, ti);
126 }
127
128 auto genNop(string funname)(FunArgsTypes!funname args) { }
129 }
130
131 void initGcProxy() {
132 pOrg = gc_getProxy();
133 pproxy = cast(Proxy**) (cast(byte*)pOrg + Proxy.sizeof);
134 foreach(funname; __traits(allMembers, Proxy))
135 __traits(getMember, myProxy, funname) = &genCall!funname;
136 }
137
138
139 version(unittest) {
140 extern(C) {
141 void gc_setProxy( Proxy* p );
142 void gc_clrProxy();
143 }
144 }
145
146 unittest {
147 //make sure our way to get pproxy works
148 initGcProxy();
149 myProxy.gc_addRoot = &genNop!"gc_addRoot";
150 myProxy.gc_addRange = &genNop!"gc_addRange";
151 myProxy.gc_removeRoot = &genNop!"gc_removeRoot";
152 myProxy.gc_removeRange = &genNop!"gc_removeRange";
153 assert(*pproxy == null);
154 gc_setProxy(&myProxy);
155 assert(*pproxy == &myProxy);
156 gc_clrProxy();
157 assert(*pproxy == null);
158 writeln("pproxy test OK");
159 memset(&myProxy, 0, Proxy.sizeof);
160 }
161
162 unittest {
163 clearAllocStats();
164 scope atr = allocsTracker();
165 assert(allocs_qsz == 0 && allocs_qc == 0);
166 scope arr = new int[32];
167 assert(allocs_qsz == 129 && allocs_qc == 1);
168 writeln("allocsTracker() and new int[] test OK");
169 }
170
171 unittest {
172 clearAllocStats();
173 startTrackingAllocs(stderr);
174 assert(allocs_qsz == 0 && allocs_qc == 0);
175 scope arr = new int[32];
176 assert(allocs_qsz == 129 && allocs_qc == 1);
177 stopTrackingAllocs();
178
179 scope arr2 = new int[42];
180 assert(allocs_qsz == 129 && allocs_qc == 1);
181
182 startTrackingAllocs(stderr);
183 scope arr3 = new byte[10];
184 assert(allocs_qsz != 129 && allocs_qc != 1);
185 stopTrackingAllocs();
186
187 writeln("startTrackingAllocs() and new int[] test OK");
188 }