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