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, std.c.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         static if (__VERSION__ >= 2066) {
77             void*   function(size_t, uint, const TypeInfo) gc_malloc;
78             BlkInfo function(size_t, uint, const TypeInfo) gc_qalloc;
79             void*   function(size_t, uint, const TypeInfo) gc_calloc;
80             void*   function(void*, size_t, uint ba, const TypeInfo) gc_realloc;
81             size_t  function(void*, size_t, size_t, const TypeInfo) gc_extend;
82         } else {
83             void*   function(size_t, uint) gc_malloc;
84             BlkInfo function(size_t, uint) gc_qalloc;
85             void*   function(size_t, uint) gc_calloc;
86             void*   function(void*, size_t, uint ba) gc_realloc;
87             size_t  function(void*, size_t, size_t) gc_extend;
88         }
89         size_t  function(size_t) gc_reserve;
90         void    function(void*) gc_free;
91 
92         void*   function(void*) gc_addrOf;
93         size_t  function(void*) gc_sizeOf;
94 
95         BlkInfo function(void*) gc_query;
96 
97         void function(void*) gc_addRoot;
98         static if (__VERSION__ >= 2066) {
99             void function(void*, size_t, const TypeInfo) gc_addRange;
100         } else {
101             void function(void*, size_t) gc_addRange;
102         }
103 
104         void function(void*) gc_removeRoot;
105         void function(void*) gc_removeRange;
106         static if (__VERSION__ >= 2066) {
107             void function(in void[]) gc_runFinalizers;
108         }
109     }
110 }
111 
112 __gshared Proxy myProxy;
113 __gshared Proxy *pOrg; // pointer to original Proxy of runtime
114 __gshared Proxy** pproxy; // should point to proxy var in runtime (proxy.d)
115 __gshared File allocs_file; // where to write messages
116 
117 template FunArgsTypes(string funname) {
118     alias FunType = typeof(*__traits(getMember, myProxy, funname));
119     alias FunArgsTypes = ParameterTypeTuple!FunType;
120 }
121 
122 extern(C) {
123     Proxy* gc_getProxy();
124 
125     auto genCall(string funname)(FunArgsTypes!funname args) {
126         *pproxy = null;
127         scope(exit) *pproxy = &myProxy;
128         return __traits(getMember, pOrg, funname)(args);
129     }
130 
131     static if (__VERSION__ >= 2066) {
132         auto genAlloc(string letter)(size_t sz, uint ba, const TypeInfo ti) {
133             *pproxy = null;
134             scope(exit) *pproxy = &myProxy;
135 
136             mixin("alias cc = allocs_" ~ letter ~ "c;");
137             mixin("alias asz = allocs_" ~ letter ~ "sz;");
138             allocs_file.writefln("gc_"~letter~"alloc(%d, %d) :%d %s", sz, ba, cc, ti is null ? "" : ti.toString());
139             asz += sz; cc++;
140             return __traits(getMember, pOrg, "gc_"~letter~"alloc")(sz, ba, ti);
141         }
142     } else {
143         auto genAlloc(string letter)(size_t sz, uint ba) {
144             *pproxy = null;
145             scope(exit) *pproxy = &myProxy;
146 
147             mixin("alias cc = allocs_" ~ letter ~ "c;");
148             mixin("alias asz = allocs_" ~ letter ~ "sz;");
149             allocs_file.writefln("gc_"~letter~"alloc(%d, %d) :%d", sz, ba, cc);
150             asz += sz; cc++;
151             return __traits(getMember, pOrg, "gc_"~letter~"alloc")(sz, ba);
152         }
153     }
154 
155     auto genNop(string funname)(FunArgsTypes!funname args) { }
156 }
157 
158 void initGcProxy() {
159     pOrg = gc_getProxy();
160     pproxy = cast(Proxy**) (cast(byte*)pOrg + Proxy.sizeof);
161     foreach(funname; __traits(allMembers, Proxy))
162         __traits(getMember, myProxy, funname) = &genCall!funname;
163 }
164 
165 
166 version(unittest) {
167     extern(C) {
168         void gc_setProxy( Proxy* p );
169         void gc_clrProxy();
170     }
171 }
172 
173 unittest {
174     //make sure our way to get pproxy works
175     initGcProxy();
176     myProxy.gc_addRoot = &genNop!"gc_addRoot";
177     myProxy.gc_addRange = &genNop!"gc_addRange";
178     myProxy.gc_removeRoot = &genNop!"gc_removeRoot";
179     myProxy.gc_removeRange = &genNop!"gc_removeRange";
180     assert(*pproxy == null);
181     gc_setProxy(&myProxy);
182     assert(*pproxy == &myProxy);
183     gc_clrProxy();
184     assert(*pproxy == null);
185     writeln("pproxy test OK");
186     memset(&myProxy, 0, Proxy.sizeof);
187 }
188 
189 unittest {
190     clearAllocStats();
191     scope atr = allocsTracker();
192     assert(allocs_qsz == 0 && allocs_qc == 0);
193     scope arr = new int[32];
194     assert(allocs_qsz == 129 && allocs_qc == 1);
195     writeln("allocsTracker() and new int[] test OK");
196 }
197 
198 unittest {
199     clearAllocStats();
200     startTrackingAllocs(stderr);
201     assert(allocs_qsz == 0 && allocs_qc == 0);
202     scope arr = new int[32];
203     assert(allocs_qsz == 129 && allocs_qc == 1);
204     stopTrackingAllocs();
205 
206     scope arr2 = new int[42];
207     assert(allocs_qsz == 129 && allocs_qc == 1);
208 
209     startTrackingAllocs(stderr);
210     scope arr3 = new byte[10];
211     assert(allocs_qsz != 129 && allocs_qc != 1);
212     stopTrackingAllocs();
213 
214     writeln("startTrackingAllocs() and new int[] test OK");
215 }