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 }