1 module served.utils.trace;
2 
3 import core.time;
4 import std.stdio : File;
5 
6 struct TraceDataStat(T)
7 {
8 	T min;
9 	T max;
10 	T total;
11 
12 	void resetTo(T value)
13 	{
14 		min = value;
15 		max = value;
16 		total = value;
17 	}
18 
19 	void put(T value)
20 	{
21 		if (value < min)
22 			min = value;
23 		if (value > max)
24 			max = value;
25 		total += value;
26 	}
27 
28 	auto map(alias fn)()
29 	{
30 		import std.functional : unaryFun;
31 
32 		alias mapFun = unaryFun!fn;
33 
34 		auto minV = mapFun(min);
35 		auto maxV = mapFun(max);
36 		auto totalV = mapFun(total);
37 
38 		return TraceDataStat!(typeof(minV))(minV, maxV, totalV);
39 	}
40 }
41 
42 struct TraceState
43 {
44 	MonoTime time;
45 	long gc;
46 
47 	static TraceState current()
48 	{
49 		import core.memory : GC;
50 
51 		TraceState ret;
52 		ret.time = MonoTime.currTime;
53 		static if (__traits(hasMember, typeof(GC.stats()), "allocatedInCurrentThread"))
54 			ret.gc = GC.stats().allocatedInCurrentThread;
55 		else
56 			ret.gc = GC.stats().usedSize;
57 		return ret;
58 	}
59 }
60 
61 struct TraceData
62 {
63 	TraceDataStat!long gcUsage;
64 	TraceDataStat!Duration runtime;
65 	int timesCalled;
66 
67 	TraceState start()
68 	{
69 		return TraceState.current();
70 	}
71 
72 	void end(TraceState state)
73 	{
74 		auto now = TraceState.current();
75 		if (timesCalled == 0)
76 		{
77 			gcUsage.resetTo(now.gc - state.gc);
78 			runtime.resetTo(now.time - state.time);
79 		}
80 		else
81 		{
82 			gcUsage.put(now.gc - state.gc);
83 			runtime.put(now.time - state.time);
84 		}
85 		timesCalled++;
86 	}
87 }
88 
89 TraceData*[string] _traceInfos;
90 
91 /// CTFE function to generate mixin code for function tracing
92 /// The trace
93 const(char)[] traceStatistics(const(char)[] name)
94 {
95 	import std.ascii : isAlphaNum;
96 
97 	char[] id;
98 	foreach (char c; name)
99 		if (c.isAlphaNum)
100 			id ~= c;
101 	//dfmt off
102 	return `
103 		static bool _trace_init_` ~ id ~ ` = false;
104 		static TraceData _trace_info_` ~ id ~ `;
105 		if (!_trace_init_` ~ id ~ `)
106 		{
107 			_traceInfos["` ~ name ~ `"] = &_trace_info_` ~ id ~ `;
108 			_trace_init_` ~ id ~ ` = true;
109 		}
110 		auto _trace_state_` ~ id ~ ` = _trace_info_` ~ id ~ `.start();
111 
112 		scope (exit)
113 		{
114 			_trace_info_` ~ id ~ `.end(_trace_state_` ~ id ~ `);
115 		}
116 	`;
117 	//dfmt on
118 }
119 
120 void dumpTraceInfos(File output)
121 {
122 	import std.array : array;
123 	import std.algorithm : sort;
124 
125 	output.write("trace name\tnum calls");
126 	foreach (stat; ["time", "gc"])
127 		output.writef("\tmin %s\tmax %s\ttotal %s\tavg %s",
128 				stat, stat, stat, stat);
129 	output.writeln();
130 
131 	auto kv = _traceInfos.byKeyValue.array
132 		.sort!"a.value.timesCalled > b.value.timesCalled";
133 
134 	foreach (entry; kv)
135 	{
136 		int total = entry.value.timesCalled;
137 		double dTotal = cast(double)total;
138 
139 		void dumpStat(T)(TraceDataStat!T stat)
140 		{
141 			output.write('\t', stat.min);
142 			output.write('\t', stat.max);
143 			output.write('\t', stat.total);
144 			output.write('\t', stat.total / dTotal);
145 		}
146 
147 		output.write(entry.key, '\t', total);
148 		dumpStat(entry.value.runtime.map!"a.total!`msecs`");
149 		dumpStat(entry.value.gcUsage);
150 		output.writeln();
151 	}
152 }