1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.utility; 11 12 import core.time : Duration; 13 import logger = std.experimental.logger; 14 import std.algorithm : filter, map, splitter, sum, sort; 15 import std.array : appender, array; 16 import std.conv : to; 17 import std.typecons : Flag, No, Tuple; 18 import core.sync.mutex : Mutex; 19 20 import my.from_; 21 22 import my.hash : BuildChecksum128, toChecksum128; 23 import dextool.type : Path, AbsolutePath; 24 25 public import my.hash : toBytes; 26 27 public import dextool.plugin.mutate.backend.type; 28 29 /// Execution profile result gathered from analysers. 30 private shared ProfileResults gProfile; 31 private shared Mutex gProfileMtx; 32 33 alias BuildChecksum = BuildChecksum128; 34 alias toChecksum = toChecksum128; 35 36 /// Returns: the profiling results gathered for this module. 37 ProfileResults getProfileResult() @trusted { 38 gProfileMtx.lock_nothrow; 39 scope (exit) 40 gProfileMtx.unlock_nothrow(); 41 auto g = cast() gProfile; 42 return new ProfileResults(g.results.dup); 43 } 44 45 void putProfile(string name, Duration time) @trusted { 46 gProfileMtx.lock_nothrow; 47 scope (exit) 48 gProfileMtx.unlock_nothrow(); 49 auto g = cast() gProfile; 50 g.put(name, time); 51 } 52 53 shared static this() { 54 gProfileMtx = new shared Mutex(); 55 gProfile = cast(shared) new ProfileResults; 56 } 57 58 @safe: 59 60 Checksum checksum(const(ubyte)[] a) { 61 import my.hash : makeMurmur3; 62 63 return makeMurmur3(a); 64 } 65 66 Checksum checksum(AbsolutePath p) @trusted { 67 import std.mmfile : MmFile; 68 69 try { 70 scope content = new MmFile(p.toString); 71 return checksum(cast(const(ubyte)[]) content[]); 72 } catch (Exception e) { 73 } 74 75 return Checksum.init; 76 } 77 78 /// Package the values to a checksum. 79 Checksum checksum(T)(const(T[2]) a) if (T.sizeof == 8) { 80 return Checksum(cast(ulong) a[0], cast(ulong) a[1]); 81 } 82 83 /// Package the values to a checksum. 84 Checksum checksum(T)(const T a, const T b) if (T.sizeof == 8) { 85 return Checksum(cast(ulong) a, cast(ulong) b); 86 } 87 88 /// Sleep for a random time that is min_ + rnd(0, span msecs) 89 void rndSleep(Duration min_, int span) nothrow @trusted { 90 import core.thread : Thread; 91 import core.time : dur; 92 import std.random : uniform; 93 94 auto t_span = () { 95 try { 96 return uniform(0, span).dur!"msecs"; 97 } catch (Exception e) { 98 } 99 return span.dur!"msecs"; 100 }(); 101 102 Thread.sleep(min_ + t_span); 103 } 104 105 /** Returns: the file content as an array of tokens. 106 * 107 * This is a bit slow, I think. Optimize by reducing the created strings. 108 * trusted: none of the unsafe accessed data escape this function. 109 * 110 * Params: 111 * splitMultiLineTokens = a token, mostly comment tokens, can be over multiple 112 * lines. If true then this split it into multiple tokens where a token is at 113 * most one per line. 114 */ 115 auto tokenize(Flag!"splitMultiLineTokens" splitTokens = No.splitMultiLineTokens)( 116 ref from.libclang_ast.context.ClangContext ctx, Path file) @trusted { 117 import std.range : enumerate; 118 119 auto tu = ctx.makeTranslationUnit(file); 120 auto toks = appender!(Token[])(); 121 foreach (ref t; tu.cursor.tokens) { 122 const ext = t.extent; 123 const start = ext.start; 124 const end = ext.end; 125 const spell = t.spelling; 126 127 static if (splitTokens) { 128 // TODO: this do not correctly count the utf-8 graphems but rather 129 // the code points because `.length` is used. 130 131 auto offset = Offset(start.offset, start.offset); 132 auto startLoc = SourceLoc(start.line, start.column); 133 auto endLoc = startLoc; 134 foreach (ts; spell.splitter('\n').enumerate) { 135 offset = Offset(offset.end, cast(uint)(offset.end + ts.length)); 136 137 if (ts.index == 0) { 138 endLoc = SourceLoc(start.line, cast(uint)(start.column + ts.value.length)); 139 } else { 140 startLoc = SourceLoc(startLoc.line + 1, 1); 141 endLoc = SourceLoc(startLoc.line, cast(uint) ts.value.length); 142 } 143 144 toks.put(Token(t.kind, offset, startLoc, endLoc, ts.value)); 145 } 146 } else { 147 auto offset = Offset(start.offset, end.offset); 148 auto startLoc = SourceLoc(start.line, start.column); 149 auto endLoc = SourceLoc(end.line, end.column); 150 toks.put(Token(t.kind, offset, startLoc, endLoc, spell)); 151 } 152 } 153 154 return toks.data; 155 } 156 157 struct TokenRange { 158 private { 159 Token[] tokens; 160 } 161 162 Token front() @safe pure nothrow { 163 assert(!empty, "Can't get front of an empty range"); 164 return tokens[0]; 165 } 166 167 void popFront() @safe pure nothrow { 168 assert(!empty, "Can't pop front of an empty range"); 169 tokens = tokens[1 .. $]; 170 } 171 172 bool empty() @safe pure nothrow const @nogc { 173 return tokens.length == 0; 174 } 175 } 176 177 /** Collect profiling results 178 * 179 */ 180 class ProfileResults { 181 alias Result = Tuple!(string, "name", Duration, "time", double, "ratio"); 182 /// The profiling for the same name is accumulated. 183 Duration[string] results; 184 185 this() { 186 } 187 188 this(typeof(results) results) { 189 this.results = results; 190 } 191 192 void put(string name, Duration time) { 193 if (auto v = name in results) { 194 (*v) += time; 195 } else { 196 results[name] = time; 197 } 198 } 199 200 /// Returns: the total wall time. 201 Duration totalTime() const { 202 return results.byValue.sum(Duration.zero); 203 } 204 205 /// Returns; 206 Result[] toRows() const { 207 auto app = appender!(Result[])(); 208 const double total = totalTime.total!"nsecs"; 209 210 foreach (a; results.byKeyValue.array.sort!((a, b) => a.value < b.value)) { 211 Result row; 212 row.name = a.key; 213 row.time = a.value; 214 row.ratio = cast(double) a.value.total!"nsecs" / total; 215 app.put(row); 216 } 217 218 return app.data; 219 } 220 221 /** 222 * 223 * This is an example from clang-tidy for how it could be reported to the user. 224 * For now it is *just* reported as it is running. 225 * 226 * ===-------------------------------------------------------------------------=== 227 * clang-tidy checks profiling 228 * ===-------------------------------------------------------------------------=== 229 * Total Execution Time: 0.0021 seconds (0.0021 wall clock) 230 * 231 * ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- 232 * 0.0000 ( 0.1%) 0.0000 ( 0.0%) 0.0000 ( 0.0%) 0.0000 ( 0.1%) readability-misplaced-array-index 233 * 0.0000 ( 0.2%) 0.0000 ( 0.0%) 0.0000 ( 0.1%) 0.0000 ( 0.1%) abseil-duration-division 234 * 0.0012 (100.0%) 0.0009 (100.0%) 0.0021 (100.0%) 0.0021 (100.0%) Total 235 */ 236 override string toString() const { 237 import std.algorithm : maxElement; 238 import std.format : format, formattedWrite; 239 import std.math : log10; 240 241 const sec = 1000000000.0; 242 // 16 is the number of letters after the dot in "0.0000 (100.1%)" + 1 empty whitespace. 243 const wallMaxLen = cast(int) results.byValue.map!(a => a.total!"seconds") 244 .maxElement(1).log10 + 15; 245 246 auto app = appender!string; 247 formattedWrite(app, 248 "===-------------------------------------------------------------------------===\n"); 249 formattedWrite(app, " dextool profiling\n"); 250 formattedWrite(app, 251 "===-------------------------------------------------------------------------===\n"); 252 formattedWrite(app, "Total execution time: %.4f seconds\n\n", 253 cast(double) totalTime.total!"nsecs" / sec); 254 formattedWrite(app, "---Wall Time--- ---Name---\n"); 255 256 void print(string name, Duration time, double ratio) { 257 auto wt = format!"%.4f (%.1f%%)"(cast(double) time.total!"nsecs" / sec, ratio * 100.0); 258 formattedWrite(app, "%-*s %s\n", wallMaxLen, wt, name); 259 } 260 261 foreach (r; toRows) { 262 print(r.name, r.time, r.ratio); 263 } 264 265 return app.data; 266 } 267 } 268 269 /** Wall time profile of a task. 270 * 271 * If no results collector is specified the result is stored in the global 272 * collector. 273 */ 274 struct Profile { 275 import std.datetime.stopwatch : StopWatch; 276 277 string name; 278 StopWatch sw; 279 ProfileResults saveTo; 280 281 this(T)(T name, ProfileResults saveTo = null) @safe nothrow { 282 try { 283 this.name = name.to!string; 284 } catch (Exception e) { 285 this.name = T.stringof; 286 } 287 this.saveTo = saveTo; 288 sw.start; 289 } 290 291 ~this() @safe nothrow { 292 // uninitialized 293 if (name.length == 0) 294 return; 295 296 try { 297 sw.stop; 298 if (saveTo is null) { 299 putProfile(name, sw.peek); 300 } else { 301 saveTo.put(name, sw.peek); 302 } 303 } catch (Exception e) { 304 } 305 } 306 }