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 }