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 }