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 
9 alias HASH = ubyte[4];
10 
11 private char[] demangle(const(char)[] buf, bool verbose = false) {
12 	static import core.demangle;
13 	if(buf == "_Dmain".dup) {
14 		return "void main()".dup;
15 	} else {
16 		if(verbose) {
17 			return core.demangle.demangle(buf);
18 		} else {
19 			return core.demangle.demangle(buf)
20 				.replaceAll(regex(r"(?:@\w+\s|pure\s|nothrow\s)", "g"), "")
21 				.replaceAll(regex(r"\([ ,*A-Za-z0-9\(\)!\[\]@]+\)", "g"), "(..)");
22 		}
23 	}
24 }
25 
26 struct Profile {
27 	Function[HASH] Functions;
28 	ulong TicksPerSecond;
29 	float TimeOfMain;
30 
31 	this(ref File f, bool verbose = false) {
32 		import std.digest.crc : crc32Of;
33 
34 		Function temp;
35 		bool newEntry = false;
36 		foreach(ref line; f.byLine) {
37 			if(line.length == 0) {
38 				continue;
39 			} else if(line[0] == '-') {
40 				newEntry = true;
41 				if(temp.Name.length != 0) {
42 					this.Functions[temp.Mangled.crc32Of] = temp;
43 					temp = Function();
44 				}
45 			} else if(line[0] == '=') {
46 				if(temp.Name.length != 0)
47 					this.Functions[temp.Mangled.crc32Of] = temp;
48 				auto i = line.indexOfAny("0123456789");
49 				assert(i > 0);
50 				auto s = line[i..$].indexOfNeither("0123456789");
51 				this.TicksPerSecond = line[i..i + s].to!ulong;
52 				break;
53 			} else if(line[0] == '\t') {
54 				auto cap = line.matchFirst(
55 					regex(r"\t\s*(\d+)\t\s*(\w+)"));
56 				assert(!cap.empty);
57 				if(newEntry) {
58 					temp.calledBy(FunctionElement(
59 						cap[2].demangle(verbose).dup,
60 						cap[2].dup,
61 						cap[1].to!ulong));
62 				} else {
63 					temp.callsTo(FunctionElement(
64 						cap[2].demangle(verbose).dup,
65 						cap[2].dup,
66 						cap[1].to!ulong));
67 				}
68 			} else {
69 				auto cap = line.matchFirst(
70 					regex(r"(\w+)\t\s*-?(\d+)\t\s*-?(\d+)\t\s*-?(\d+)"));
71 				assert(!cap.empty);
72 				newEntry = false;
73 				temp.Name = cap[1].demangle(verbose).dup;
74 				temp.Mangled = cap[1].dup;
75 				temp.Calls = cap[2].to!ulong;
76 				temp.FunctionTime = cap[4].to!ulong;
77 				temp.Time = cap[3].to!ulong;
78 			}
79 		}
80 
81 		const HASH main = "_Dmain".crc32Of;
82 		if(main !in this.Functions)
83 			throw new Exception("Your trace.log is invalid");
84 
85 		this.TimeOfMain = this.timeOf(main);
86 	}
87 
88 	const void toString(scope void delegate(const(char)[]) s, in float threshold = 0) {
89 		import std.format : format;
90 		foreach(k, ref v; this.Functions) {
91 			if(threshold != 0 && this.percOf(k) < threshold)
92 				continue;
93 			s("Function '%s':\n".format(v.Name));
94 			s("\tMangled name: '%s'\n".format(v.Mangled));
95 			if(v.CallsTo) {
96 				s("\tCalls:\n");
97 				foreach(ke, va; v.CallsTo)
98 					s("\t\t%s\t%d times\n".format(va.Name, va.Calls));
99 			}
100 			if(v.CalledBy) {
101 				s("\tCalled by:\n");
102 				foreach(ke, va; v.CalledBy)
103 					s("\t\t%s\t%d times\n".format(va.Name, va.Calls));
104 			}
105 			s("\tTook: %f seconds (%f%%)\n"
106 				.format(this.functionTimeOf(k), this.functionPercOf(k)));
107 			s("\tFinished in: %f seconds (%f%%)\n"
108 				.format(this.timeOf(k), this.percOf(k)));
109 		}
110 	}
111 
112 	const void toJSONString(scope void delegate(const(char)[]) s,
113 		in float threshold = 0,
114 		in bool pretty = false) {
115 		if(pretty) {
116 			s(this.toJSON(threshold).toPrettyString);
117 			s("\n");
118 		} else {
119 			s(this.toJSON(threshold).toString);
120 		}
121 	}
122 
123 	const JSONValue toJSON(in float threshold = 0) {
124 		JSONValue[] ret;
125 		foreach(k, ref v; this.Functions) {
126 			if(threshold != 0 && this.percOf(k) < threshold)
127 				continue;
128 
129 			JSONValue func = JSONValue([
130 				"name": v.Name,
131 				"mangled": v.Mangled
132 			]);
133 			if(v.CallsTo) {
134 				JSONValue[] temp;
135 				foreach(kk, vv; v.CallsTo) {
136 					temp ~= JSONValue([
137 						"name": JSONValue(vv.Name),
138 						"mangled": JSONValue(vv.Mangled),
139 						"calls": JSONValue(vv.Calls)
140 					]);
141 				}
142 				func["callsTo"] = JSONValue(temp);
143 			}
144 			if(v.CalledBy) {
145 				JSONValue[] temp;
146 				foreach(k, vv; v.CalledBy) {
147 					temp ~= JSONValue([
148 						"name": JSONValue(vv.Name),
149 						"mangled": JSONValue(vv.Mangled),
150 						"calls": JSONValue(vv.Calls)
151 					]);
152 				}
153 				func["calledBy"] = JSONValue(temp);
154 			}
155 			func["functionTimeSec"] = JSONValue(this.functionTimeOf(k));
156 			func["timeSec"] = JSONValue(this.timeOf(k));
157 			func["functionTime"] = JSONValue(v.FunctionTime);
158 			func["time"] = JSONValue(v.Time);
159 			func["functionPerc"] = JSONValue(this.functionPercOf(k));
160 			func["perc"] = JSONValue(this.percOf(k));
161 
162 			ret ~= func;
163 		}
164 
165 		return JSONValue([
166 			"tps" : JSONValue(this.TicksPerSecond),
167 			"functions" : JSONValue(ret)]);
168 	}
169 
170 	@safe pure nothrow const float timeOf(HASH f)
171 	in {
172 		assert(f in this.Functions);
173 	} body {
174 		return cast(float) this.Functions[f].Time /
175 			cast(float) this.TicksPerSecond;
176 	}
177 
178 	@safe pure nothrow const float percOf(HASH f)
179 	in {
180 		assert(f in this.Functions);
181 	} body {
182 		return (cast(float) this.Functions[f].Time /
183 			cast(float) this.TicksPerSecond) / TimeOfMain * 100;
184 	}
185 
186 	@safe pure nothrow const float functionTimeOf(HASH f)
187 	in {
188 		assert(f in this.Functions);
189 	} body {
190 		return cast(float) this.Functions[f].FunctionTime /
191 			cast(float) this.TicksPerSecond;
192 	}
193 
194 	@safe pure nothrow const float functionPercOf(HASH f)
195 	in {
196 		assert(f in this.Functions);
197 	} body {
198 		return (cast(float) this.Functions[f].FunctionTime /
199 			cast(float) this.TicksPerSecond) / TimeOfMain * 100;
200 	}
201 
202 	const void toDOT(scope void delegate(const(char)[]) s,
203 			float threshold = 0,
204 			string[float] colours = [
205 				0: "limegreen",
206 				10: "slateblue",
207 				25: "steelblue",
208 				50: "royalblue",
209 				75: "navy",
210 				95: "red"
211 			]
212 		) {
213 		import std.format : format;
214 		import std.string : tr, wrap;
215 		import std.digest.crc : crc32Of;
216 
217 		string clr(float f) {
218 			import std.algorithm : sort;
219 			foreach(k; sort!("a>b")(colours.keys)) {
220 				if(k <= f) {
221 					return colours[k];
222 				}
223 			}
224 			return "gray";
225 		}
226 
227 		HASH[][HASH] func;
228 		enum fmt = "\"%s\" [label=\"%s\\n%.2f%%(%.2f%%)\", shape=\"box\"," ~
229 			" style=filled, fillcolor=\"%s\"];\n";
230 
231 		foreach(k, ref v; this.Functions) {
232 			if(threshold == 0 || this.percOf(k) > threshold) {
233 				func[k] = [];
234 				foreach(key, unused; v.CallsTo) {
235 					if(threshold != 0 && this.percOf(key) <= threshold)
236 						continue;
237 					if(key !in func)
238 						func[key] = [];
239 					func[k] ~= key;
240 				}
241 			} else {
242 				continue;
243 			}
244 		}
245 
246 		s("digraph {\n");
247 		foreach(k, ref v; func) {
248 			s(fmt.format(
249 				this.Functions[k].Mangled.tr("\"", "\\\""),
250 				this.Functions[k].Name.tr("\"", "\\\"").wrap(40),
251 				this.percOf(k),
252 				this.functionPercOf(k),
253 				clr(this.percOf(k))));
254 			foreach(i; v) {
255 				if(i !in func) {
256 					s(fmt.format(
257 						this.Functions[i].Mangled.tr("\"", "\\\""),
258 						this.Functions[i].Name.tr("\"", "\\\"").wrap(40),
259 						this.percOf(i),
260 						this.functionPercOf(i),
261 						clr(this.percOf(i))));
262 				}
263 				s("\"%s\" -> \"%s\" [label=\"%dx\"];\n".format(
264 					this.Functions[k].Mangled.tr("\"", "\\\""),
265 					this.Functions[i].Mangled.tr("\"", "\\\""),
266 					this.Functions[k].CallsTo[i].Calls));
267 			}
268 		}
269 		s("}\n");
270 	}
271 }
272 
273 private struct FunctionElement {
274 	char[] Name;
275 	char[] Mangled;
276 	ulong Calls;
277 }
278 
279 private struct Function {
280 	char[] Name;
281 	char[] Mangled;
282 	ulong Calls;
283 	FunctionElement[HASH] CallsTo;
284 	FunctionElement[HASH] CalledBy;
285 	ulong FunctionTime;
286 	ulong Time;
287 
288 	void callsTo(FunctionElement func) {
289 		import std.digest.crc : crc32Of;
290 		HASH h = func.Mangled.crc32Of;
291 		this.CallsTo[h] = func;
292 	}
293 
294 	void calledBy(FunctionElement func) {
295 		import std.digest.crc : crc32Of;
296 		HASH h = func.Mangled.crc32Of;
297 		this.CalledBy[h] = func;
298 	}
299 }