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 }