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 }