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