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() nothrow { stopTrackingAllocs();	}
54 		this(this) @disable;
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 }