1 /** 2 Copyright: Copyright (c) 2021, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 Utility for manually profiling of parts of a program. 7 */ 8 module my.profile; 9 10 import core.sync.mutex : Mutex; 11 import std.datetime : Duration; 12 13 /// Execution profile result gathered from analysers. 14 private shared ProfileResults gProfile; 15 private shared Mutex gProfileMtx; 16 17 /// Returns: the profiling results gathered for this module. 18 ProfileResults getProfileResult() @trusted { 19 gProfileMtx.lock_nothrow; 20 scope (exit) 21 gProfileMtx.unlock_nothrow(); 22 auto g = cast() gProfile; 23 return new ProfileResults(g.results.dup); 24 } 25 26 void putProfile(string name, Duration time) @trusted { 27 gProfileMtx.lock_nothrow; 28 scope (exit) 29 gProfileMtx.unlock_nothrow(); 30 auto g = cast() gProfile; 31 g.add(name, time); 32 } 33 34 shared static this() { 35 gProfileMtx = new shared Mutex(); 36 gProfile = cast(shared) new ProfileResults; 37 } 38 39 @safe: 40 41 /** Wall time profile of a task. 42 * 43 * If no results collector is specified the result is stored in the global 44 * collector. 45 */ 46 struct Profile { 47 import std.datetime.stopwatch : StopWatch; 48 49 string name; 50 StopWatch sw; 51 ProfileResults saveTo; 52 53 this(T)(T name, ProfileResults saveTo = null) @safe nothrow { 54 static if (is(T == string)) { 55 this.name = name; 56 } else { 57 try { 58 this.name = name.to!string; 59 } catch (Exception e) { 60 this.name = T.stringof; 61 } 62 } 63 this.saveTo = saveTo; 64 sw.start; 65 } 66 67 ~this() @safe nothrow { 68 // uninitialized 69 if (name.length == 0) 70 return; 71 72 try { 73 sw.stop; 74 if (saveTo is null) { 75 putProfile(name, sw.peek); 76 } else { 77 saveTo.add(name, sw.peek); 78 } 79 } catch (Exception e) { 80 } 81 } 82 } 83 84 /** Collect profiling results. 85 */ 86 class ProfileResults { 87 import std.algorithm : sum, map, sort; 88 import std.array : array, appender; 89 90 struct Result { 91 string name; 92 Duration time; 93 double ratio; 94 } 95 96 /// The profiling for the same name is accumulated. 97 Duration[string] results; 98 99 this() { 100 } 101 102 this(typeof(results) results) { 103 this.results = results; 104 } 105 106 /// Add `time` to `name`. 107 void add(string name, Duration time) { 108 results.update(name, () => time, (ref Duration a) { a += time; }); 109 } 110 111 /// Set `name` to `time`. 112 void set(string name, Duration time) { 113 results.update(name, () => time, (ref Duration a) { a = time; }); 114 } 115 116 /// Returns: the total wall time. 117 Duration totalTime() const { 118 return results.byValue.sum(Duration.zero); 119 } 120 121 /// Returns; 122 Result[] toRows() const { 123 auto app = appender!(Result[])(); 124 const double total = totalTime.total!"nsecs"; 125 126 foreach (a; results.byKeyValue.array.sort!((a, b) => a.value < b.value)) { 127 Result row; 128 row.name = a.key; 129 row.time = a.value; 130 row.ratio = cast(double) a.value.total!"nsecs" / total; 131 app.put(row); 132 } 133 134 return app.data; 135 } 136 137 /** 138 * 139 * This is an example from clang-tidy for how it could be reported to the user. 140 * For now it is *just* reported as it is running. 141 * 142 * ===-------------------------------------------------------------------------=== 143 * clang-tidy checks profiling 144 * ===-------------------------------------------------------------------------=== 145 * Total Execution Time: 0.0021 seconds (0.0021 wall clock) 146 * 147 * ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- 148 * 0.0000 ( 0.1%) 0.0000 ( 0.0%) 0.0000 ( 0.0%) 0.0000 ( 0.1%) readability-misplaced-array-index 149 * 0.0000 ( 0.2%) 0.0000 ( 0.0%) 0.0000 ( 0.1%) 0.0000 ( 0.1%) abseil-duration-division 150 * 0.0012 (100.0%) 0.0009 (100.0%) 0.0021 (100.0%) 0.0021 (100.0%) Total 151 */ 152 override string toString() const { 153 import std.algorithm : maxElement; 154 import std.format : format, formattedWrite; 155 import std.math : log10; 156 157 const sec = 1000000000.0; 158 // 16 is the number of letters after the dot in "0.0000 (100.1%)" + 1 empty whitespace. 159 const wallMaxLen = cast(int) results.byValue.map!(a => a.total!"seconds") 160 .maxElement(1).log10 + 15; 161 162 auto app = appender!string; 163 formattedWrite(app, 164 "===-------------------------------------------------------------------------===\n"); 165 formattedWrite(app, " dextool profiling\n"); 166 formattedWrite(app, 167 "===-------------------------------------------------------------------------===\n"); 168 formattedWrite(app, "Total execution time: %.4f seconds\n\n", 169 cast(double) totalTime.total!"nsecs" / sec); 170 formattedWrite(app, "---Wall Time--- ---Name---\n"); 171 172 void print(string name, Duration time, double ratio) { 173 auto wt = format!"%.4f (%.1f%%)"(cast(double) time.total!"nsecs" / sec, ratio * 100.0); 174 formattedWrite(app, "%-*s %s\n", wallMaxLen, wt, name); 175 } 176 177 foreach (r; toRows) { 178 print(r.name, r.time, r.ratio); 179 } 180 181 return app.data; 182 } 183 } 184 185 @("shall accumulate profiling results") 186 unittest { 187 import std.conv : to; 188 import std.stdio : writeln; 189 190 auto pc = new ProfileResults; 191 192 foreach (_; 0 .. 10) 193 auto p = Profile("a", pc); 194 assert(pc.toRows.length == 1); 195 196 foreach (i; 0 .. 10) 197 auto p = Profile("a" ~ i.to!string, pc); 198 assert(pc.toRows.length == 11); 199 200 writeln(pc); 201 }