1 module profdump;
2 
3 import std.stdio : File;
4 import std.string : indexOfAny, indexOfNeither;
5 import std.conv : to;
6 import std.regex : regex, matchFirst, replaceAll;
7 import std.json : JSONValue;
8 import std.exception : enforce;
9 
10 alias HASH = ubyte[4];
11 
12 private char[] demangle(const(char)[] buf, bool verbose = false) {
13 	static import core.demangle;
14 	if(buf == "_Dmain".dup) {
15 		return "void main()".dup;
16 	} else {
17 		if(verbose) {
18 			return core.demangle.demangle(buf);
19 		} else {
20 			return core.demangle.demangle(buf)
21 				.replaceAll(regex(r"(?:@\w+\s|pure\s|nothrow\s)", "g"), "")
22 				.replaceAll(regex(r"\([ ,*A-Za-z0-9\(\)!\[\]@]+\)", "g"), "(..)");
23 		}
24 	}
25 }
26 
27 struct Profile {
28 	Function[HASH] Functions;
29 	ulong TicksPerSecond;
30 	float TimeOfMain;
31 
32 	this(ref File f, bool verbose = false) {
33 		import std.digest.crc : crc32Of;
34 
35 		Function temp;
36 		bool newEntry = false;
37 		foreach(ref line; f.byLine) {
38 			if(line.length == 0) {
39 				continue;
40 			} else if(line[0] == '-') {
41 				newEntry = true;
42 				if(temp.Name.length != 0) {
43 					this.Functions[temp.Mangled.crc32Of] = temp;
44 					temp = Function();
45 				}
46 			} else if(line[0] == '=') {
47 				if(temp.Name.length != 0)
48 					this.Functions[temp.Mangled.crc32Of] = temp;
49 				auto i = line.indexOfAny("0123456789");
50 				enforce(i > 0,
51 					"Your trace.log file is invalid");
52 				auto s = line[i..$].indexOfNeither("0123456789");
53 				this.TicksPerSecond = line[i..i + s].to!ulong;
54 				break;
55 			} else if(line[0] == '\t') {
56 				auto cap = line.matchFirst(
57 					regex(r"\t\s*(\d+)\t\s*(\w+)"));
58 				enforce(!cap.empty,
59 					"Your trace.log file is invalid");
60 				if(newEntry) {
61 					temp.calledBy(FunctionElement(
62 						cap[2].demangle(verbose).dup,
63 						cap[2].dup,
64 						cap[1].to!ulong));
65 				} else {
66 					temp.callsTo(FunctionElement(
67 						cap[2].demangle(verbose).dup,
68 						cap[2].dup,
69 						cap[1].to!ulong));
70 				}
71 			} else {
72 				auto cap = line.matchFirst(
73 					regex(r"(\w+)\t\s*-?(\d+)\t\s*-?(\d+)\t\s*-?(\d+)"));
74 				assert(!cap.empty);
75 				newEntry = false;
76 				temp.Name = cap[1].demangle(verbose).dup;
77 				temp.Mangled = cap[1].dup;
78 				temp.Calls = cap[2].to!ulong;
79 				temp.FunctionTime = cap[4].to!ulong;
80 				temp.Time = cap[3].to!ulong;
81 			}
82 		}
83 
84 		const HASH main = "_Dmain".crc32Of;
85 		enforce(main in this.Functions,
86 			"Your trace.log file is invalid");
87 
88 		this.TimeOfMain = this.timeOf(main);
89 	}
90 
91 	const void writeString(ref File f, in float threshold = 0) {
92 		foreach(k, ref v; this.Functions) {
93 			if(threshold != 0 && this.percOf(k) < threshold)
94 				continue;
95 			f.writefln("Function '%s':\n"~
96 				"\tMangled name: '%s'",
97 					v.Name,
98 					v.Mangled);
99 
100 			if(v.CallsTo) {
101 				f.writeln("\tCalls:");
102 				foreach(ke, va; v.CallsTo)
103 					f.writefln("\t\t%s\t%d times", va.Name, va.Calls);
104 			}
105 			if(v.CalledBy) {
106 				f.writeln("\tCalled by:");
107 				foreach(ke, va; v.CalledBy)
108 					f.writefln("\t\t%s\t%d times", va.Name, va.Calls);
109 			}
110 			f.writefln("\tTook: %f seconds (%f%%)\n"~
111 				"\tFinished in: %f seconds (%f%%)",
112 					this.functionTimeOf(k),
113 					this.functionPercOf(k),
114 					this.timeOf(k),
115 					this.percOf(k));
116 		}
117 	}
118 
119 	deprecated const void toString(scope void delegate(const(char)[]) s, in float threshold = 0) {
120 		import std.format : format;
121 		foreach(k, ref v; this.Functions) {
122 			if(threshold != 0 && this.percOf(k) < threshold)
123 				continue;
124 			s("Function '%s':\n".format(v.Name));
125 			s("\tMangled name: '%s'\n".format(v.Mangled));
126 			if(v.CallsTo) {
127 				s("\tCalls:\n");
128 				foreach(ke, va; v.CallsTo)
129 					s("\t\t%s\t%d times\n".format(va.Name, va.Calls));
130 			}
131 			if(v.CalledBy) {
132 				s("\tCalled by:\n");
133 				foreach(ke, va; v.CalledBy)
134 					s("\t\t%s\t%d times\n".format(va.Name, va.Calls));
135 			}
136 			s("\tTook: %f seconds (%f%%)\n"
137 				.format(this.functionTimeOf(k), this.functionPercOf(k)));
138 			s("\tFinished in: %f seconds (%f%%)\n"
139 				.format(this.timeOf(k), this.percOf(k)));
140 		}
141 	}
142 
143 	deprecated const void toJSONString(scope void delegate(const(char)[]) s,
144 		in float threshold = 0,
145 		in bool pretty = false) {
146 		if(pretty) {
147 			s(this.toJSON(threshold).toPrettyString);
148 			s("\n");
149 		} else {
150 			s(this.toJSON(threshold).toString);
151 		}
152 	}
153 
154 	const void writeJSON(ref File f, in float threshold = 0, in bool pretty = false) {
155 		(pretty)
156 			? f.writeln(this.toJSON(threshold).toPrettyString)
157 			: f.write(this.toJSON(threshold).toString);
158 	}
159 
160 	const JSONValue toJSON(in float threshold = 0) {
161 		JSONValue[] ret;
162 		foreach(k, ref v; this.Functions) {
163 			if(threshold != 0 && this.percOf(k) < threshold)
164 				continue;
165 
166 			JSONValue func = JSONValue([
167 				"name": v.Name,
168 				"mangled": v.Mangled
169 			]);
170 			if(v.CallsTo) {
171 				JSONValue[] temp;
172 				foreach(kk, vv; v.CallsTo) {
173 					temp ~= JSONValue([
174 						"name": JSONValue(vv.Name),
175 						"mangled": JSONValue(vv.Mangled),
176 						"calls": JSONValue(vv.Calls)
177 					]);
178 				}
179 				func["callsTo"] = JSONValue(temp);
180 			}
181 			if(v.CalledBy) {
182 				JSONValue[] temp;
183 				foreach(k, vv; v.CalledBy) {
184 					temp ~= JSONValue([
185 						"name": JSONValue(vv.Name),
186 						"mangled": JSONValue(vv.Mangled),
187 						"calls": JSONValue(vv.Calls)
188 					]);
189 				}
190 				func["calledBy"] = JSONValue(temp);
191 			}
192 			func["functionTimeSec"] = JSONValue(this.functionTimeOf(k));
193 			func["timeSec"] = JSONValue(this.timeOf(k));
194 			func["functionTime"] = JSONValue(v.FunctionTime);
195 			func["time"] = JSONValue(v.Time);
196 			func["functionPerc"] = JSONValue(this.functionPercOf(k));
197 			func["perc"] = JSONValue(this.percOf(k));
198 
199 			ret ~= func;
200 		}
201 
202 		return JSONValue([
203 			"tps" : JSONValue(this.TicksPerSecond),
204 			"functions" : JSONValue(ret)]);
205 	}
206 
207 	@safe pure nothrow const float timeOf(HASH f)
208 	in {
209 		assert(f in this.Functions);
210 	} body {
211 		return cast(float) this.Functions[f].Time /
212 			cast(float) this.TicksPerSecond;
213 	}
214 
215 	@safe pure nothrow const float percOf(HASH f)
216 	in {
217 		assert(f in this.Functions);
218 	} body {
219 		return (cast(float) this.Functions[f].Time /
220 			cast(float) this.TicksPerSecond) / TimeOfMain * 100;
221 	}
222 
223 	@safe pure nothrow const float functionTimeOf(HASH f)
224 	in {
225 		assert(f in this.Functions);
226 	} body {
227 		return cast(float) this.Functions[f].FunctionTime /
228 			cast(float) this.TicksPerSecond;
229 	}
230 
231 	@safe pure nothrow const float functionPercOf(HASH f)
232 	in {
233 		assert(f in this.Functions);
234 	} body {
235 		return (cast(float) this.Functions[f].FunctionTime /
236 			cast(float) this.TicksPerSecond) / TimeOfMain * 100;
237 	}
238 
239 	const void writeDOT(ref File f,
240 			in float threshold = 0,
241 			in string[float] colours = [
242 				0: "limegreen",
243 				10: "slateblue",
244 				25: "steelblue",
245 				50: "royalblue",
246 				75: "navy",
247 				95: "red"
248 			]
249 		) {
250 		import std.string : tr, wrap;
251 		import std.digest.crc : crc32Of;
252 
253 		auto clr = (float f) {
254 			import std.algorithm : sort;
255 			foreach(k; sort!("a>b")(colours.keys))
256 				if(k <= f)
257 					return colours[k];
258 			return "gray";
259 		};
260 
261 		HASH[][HASH] func;
262 		enum fmt = "\"%s\" [label=\"%s\\n%.2f%%(%.2f%%)\", shape=\"box\"," ~
263 			" style=filled, fillcolor=\"%s\"];";
264 
265 		foreach(k, ref v; this.Functions) {
266 			if(threshold == 0 || this.percOf(k) > threshold) {
267 				func[k] = [];
268 				foreach(key, unused; v.CallsTo) {
269 					if(threshold != 0 && this.percOf(key) <= threshold)
270 						continue;
271 					if(key !in func)
272 						func[key] = [];
273 					func[k] ~= key;
274 				}
275 			} else {
276 				continue;
277 			}
278 		}
279 
280 		f.writeln("digraph {");
281 		foreach(k, ref v; func) {
282 			f.writefln(fmt,
283 				this.Functions[k].Mangled.tr("\"", "\\\""),
284 				this.Functions[k].Name.tr("\"", "\\\"").wrap(40),
285 				this.percOf(k),
286 				this.functionPercOf(k),
287 				clr(this.percOf(k)));
288 			foreach(i; v) {
289 				if(i !in func) {
290 					f.writefln(fmt,
291 						this.Functions[i].Mangled.tr("\"", "\\\""),
292 						this.Functions[i].Name.tr("\"", "\\\"").wrap(40),
293 						this.percOf(i),
294 						this.functionPercOf(i),
295 						clr(this.percOf(i)));
296 				}
297 				f.writefln("\"%s\" -> \"%s\" [label=\"%dx\"];",
298 					this.Functions[k].Mangled.tr("\"", "\\\""),
299 					this.Functions[i].Mangled.tr("\"", "\\\""),
300 					this.Functions[k].CallsTo[i].Calls);
301 			}
302 		}
303 		f.writeln("}");
304 	}
305 
306 	deprecated const void toDOT(scope void delegate(const(char)[]) s,
307 			float threshold = 0,
308 			string[float] colours = [
309 				0: "limegreen",
310 				10: "slateblue",
311 				25: "steelblue",
312 				50: "royalblue",
313 				75: "navy",
314 				95: "red"
315 			]
316 		) {
317 		import std.format : format;
318 		import std.string : tr, wrap;
319 		import std.digest.crc : crc32Of;
320 
321 		string clr(float f) {
322 			import std.algorithm : sort;
323 			foreach(k; sort!("a>b")(colours.keys)) {
324 				if(k <= f) {
325 					return colours[k];
326 				}
327 			}
328 			return "gray";
329 		}
330 
331 		HASH[][HASH] func;
332 		enum fmt = "\"%s\" [label=\"%s\\n%.2f%%(%.2f%%)\", shape=\"box\"," ~
333 			" style=filled, fillcolor=\"%s\"];\n";
334 
335 		foreach(k, ref v; this.Functions) {
336 			if(threshold == 0 || this.percOf(k) > threshold) {
337 				func[k] = [];
338 				foreach(key, unused; v.CallsTo) {
339 					if(threshold != 0 && this.percOf(key) <= threshold)
340 						continue;
341 					if(key !in func)
342 						func[key] = [];
343 					func[k] ~= key;
344 				}
345 			} else {
346 				continue;
347 			}
348 		}
349 
350 		s("digraph {\n");
351 		foreach(k, ref v; func) {
352 			s(fmt.format(
353 				this.Functions[k].Mangled.tr("\"", "\\\""),
354 				this.Functions[k].Name.tr("\"", "\\\"").wrap(40),
355 				this.percOf(k),
356 				this.functionPercOf(k),
357 				clr(this.percOf(k))));
358 			foreach(i; v) {
359 				if(i !in func) {
360 					s(fmt.format(
361 						this.Functions[i].Mangled.tr("\"", "\\\""),
362 						this.Functions[i].Name.tr("\"", "\\\"").wrap(40),
363 						this.percOf(i),
364 						this.functionPercOf(i),
365 						clr(this.percOf(i))));
366 				}
367 				s("\"%s\" -> \"%s\" [label=\"%dx\"];\n".format(
368 					this.Functions[k].Mangled.tr("\"", "\\\""),
369 					this.Functions[i].Mangled.tr("\"", "\\\""),
370 					this.Functions[k].CallsTo[i].Calls));
371 			}
372 		}
373 		s("}\n");
374 	}
375 }
376 
377 private struct FunctionElement {
378 	char[] Name;
379 	char[] Mangled;
380 	ulong Calls;
381 }
382 
383 private struct Function {
384 	char[] Name;
385 	char[] Mangled;
386 	ulong Calls;
387 	FunctionElement[HASH] CallsTo;
388 	FunctionElement[HASH] CalledBy;
389 	ulong FunctionTime;
390 	ulong Time;
391 
392 	void callsTo(FunctionElement func) {
393 		import std.digest.crc : crc32Of;
394 		HASH h = func.Mangled.crc32Of;
395 		this.CallsTo[h] = func;
396 	}
397 
398 	void calledBy(FunctionElement func) {
399 		import std.digest.crc : crc32Of;
400 		HASH h = func.Mangled.crc32Of;
401 		this.CalledBy[h] = func;
402 	}
403 }