1 /*******************************************************************************
2 3 Contains methods that format primitive data members from structs or classes
4 into strings that can be exported as responses to prometheus queries.
5 6 Copyright:
7 Copyright (c) 2019 dunnhumby Germany GmbH.
8 All rights reserved
9 10 License:
11 Boost Software License Version 1.0. See LICENSE.txt for details.
12 13 *******************************************************************************/14 15 moduleocean.util.prometheus.collector.StatFormatter;
16 17 importocean.net.http.TaskHttpConnectionHandler;
18 19 importcore.stdc.time;
20 importocean.meta.traits.Basic;
21 importocean.meta.codegen.Identifier;
22 importocean.math.IEEE;
23 importocean.text.convert.Formatter;
24 importocean.transition;
25 26 /*******************************************************************************
27 28 Format data members from a struct or a class into a string that can be
29 added to a stat collection buffer for exporting to prometheus.
30 31 The names and values of the members of the given struct are formatted as
32 stat names and values, respectively.
33 34 Params:
35 ValuesT = The struct or class type to fetch the stat names from.
36 values = The struct or class to fetch stat values from.
37 buffer = The buffer to add the formatted stats to.
38 39 *******************************************************************************/40 41 publicstaticvoidformatStats ( ValuesT ) (
42 ValuesTvalues, refmstringbuffer )
43 {
44 staticassert (is(ValuesT == struct) || is(ValuesT == class),
45 "'values' parameter must be a struct or a class.");
46 47 foreach (i, ValMemberT; typeof(ValuesT.tupleof))
48 {
49 staticif (isPrimitiveType!(ValMemberT))
50 {
51 sformat(buffer, "{}", identifier!(ValuesT.tupleof[i]));
52 appendValue(values.tupleof[i], buffer);
53 }
54 }
55 }
56 57 /*******************************************************************************
58 59 Format data members from a struct or a class, with a additional label name
60 and value, into a string that can be added to a stat collection buffer for
61 exporting to prometheus.
62 63 Params:
64 LabelName = The name of the label to annotate the stats with.
65 ValuesT = The struct or class type to fetch the stat names from.
66 LabelT = The type of the label's value.
67 values = The struct or class to fetch stat values from.
68 label_val = The label value to annotate the stats with.
69 buffer = The buffer to add the formatted stats to.
70 71 *******************************************************************************/72 73 publicstaticvoidformatStats ( istringLabelName, ValuesT, LabelT ) (
74 ValuesTvalues, LabelTlabel_val, refmstringbuffer )
75 {
76 staticassert (is(ValuesT == struct) || is(ValuesT == class),
77 "'values' parameter must be a struct or a class.");
78 79 foreach (i, ValMemberT; typeof(ValuesT.tupleof))
80 {
81 staticif (isPrimitiveType!(ValMemberT))
82 {
83 sformat(buffer, "{} {{", identifier!(ValuesT.tupleof[i]));
84 85 appendLabel!(LabelName)(label_val, buffer);
86 87 sformat(buffer, "}");
88 appendValue(values.tupleof[i], buffer);
89 }
90 }
91 }
92 93 94 /*******************************************************************************
95 96 Format data members from a struct or a class, with an additional struct or
97 class to fetch label names and values from, into a string that can be added
98 to a stat collection buffer for exporting to prometheus.
99 100 Params:
101 ValuesT = The struct or class type to fetch the stat names from.
102 LabelsT = The struct or class type to fetch the label names from.
103 values = The struct or class to fetch stat values from.
104 labels = The struct or class holding the label values to annotate
105 the stats with.
106 buffer = The buffer to add the formatted stats to.
107 108 *******************************************************************************/109 110 publicstaticvoidformatStats ( ValuesT, LabelsT ) ( ValuesTvalues,
111 LabelsTlabels, refmstringbuffer )
112 {
113 staticassert (is(ValuesT == struct) || is(ValuesT == class),
114 "'values' parameter must be a struct or a class.");
115 staticassert (is(LabelsT == struct) || is(LabelsT == class),
116 "'labels' parameter must be a struct or a class.");
117 118 foreach (i, ValMemberT; typeof(ValuesT.tupleof))
119 {
120 staticif (isPrimitiveType!(ValMemberT))
121 {
122 sformat(buffer, "{} {{", identifier!(ValuesT.tupleof[i]));
123 124 boolfirst_label = true;
125 126 foreach (j, LabelMemberT; typeof(LabelsT.tupleof))
127 {
128 if (first_label)
129 {
130 first_label = false;
131 }
132 else133 {
134 sformat(buffer, ",");
135 }
136 137 appendLabel!(identifier!(LabelsT.tupleof[j]))(
138 labels.tupleof[j], buffer);
139 }
140 141 sformat(buffer, "}");
142 appendValue(values.tupleof[i], buffer);
143 }
144 }
145 }
146 147 /*******************************************************************************
148 149 Appends a label name and value to a given buffer. If the value is of a
150 floating-point type, then appends it with a precision upto 6 decimal places.
151 152 Params:
153 LabelName = The name of the label to annotate the stats with.
154 LabelT = The type of the label's value.
155 label_val = The label value to annotate the stats with.
156 buffer = The buffer to append the label to.
157 158 *******************************************************************************/159 160 privatestaticvoidappendLabel ( istringLabelName, LabelT ) (
161 LabelTlabel_val, refmstringbuffer )
162 {
163 staticif (isFloatingPointType!(LabelT))
164 {
165 sformat(buffer, "{}=\"{:6.}\"", LabelName,
166 getSanitized(label_val));
167 }
168 else169 {
170 sformat(buffer, "{}=\"{}\"", LabelName, label_val);
171 }
172 }
173 174 // Test appending a label with a value of type string175 unittest176 {
177 mstringbuffer;
178 appendLabel!("labelname")("labelval", buffer);
179 test!("==")(buffer, "labelname=\"labelval\"");
180 }
181 182 // Test appending a label with an integer type value183 unittest184 {
185 mstringbuffer;
186 appendLabel!("labelname")(2345678UL, buffer);
187 test!("==")(buffer, "labelname=\"2345678\"");
188 }
189 190 // Test appending a label with a floting point type value191 unittest192 {
193 mstringbuffer;
194 appendLabel!("labelname")(3.1415926, buffer);
195 test!("==")(buffer, "labelname=\"3.141593\"");
196 }
197 198 /*******************************************************************************
199 200 Appends a stat value to a given buffer. If the value is of a floating-point
201 type, then appends it with a precision of upto 6 decimal places.
202 203 Params:
204 T = The datatype of the value.
205 value = The value to append to the buffer.
206 buffer = The buffer to which the stat value will be appended.
207 208 *******************************************************************************/209 210 privatestaticvoidappendValue ( T ) ( Tvalue, refmstringbuffer )
211 {
212 staticif (isFloatingPointType!(T))
213 {
214 sformat(buffer, " {:6.}\n", getSanitized(value));
215 }
216 else217 {
218 sformat(buffer, " {}\n", value);
219 }
220 }
221 222 // Test appending a non-floating-point values223 unittest224 {
225 mstringbuffer;
226 appendValue(32768UL, buffer);
227 test!("==")(buffer, " 32768\n");
228 }
229 230 // Test appending a floating-point value having less than 6 decimal places231 unittest232 {
233 mstringbuffer;
234 appendValue(3.14159, buffer);
235 test!("==")(buffer, " 3.14159\n");
236 }
237 238 // Test appending a floating-point value having more than 6 decimal places239 unittest240 {
241 mstringbuffer;
242 appendValue(3.1415926, buffer);
243 test!("==")(buffer, " 3.141593\n");
244 }
245 246 /*******************************************************************************
247 248 Sanitizes floating-point type values.
249 250 If the datatype of the value is a floating-point type, then returns 0.0
251 for NaN and the datatype's maximum value for +/-Inf.
252 If the datatype of the value is not a floating-point type, then returns
253 the given value without any modification.
254 255 Params:
256 T = The datatype of the value.
257 value = The value to append to sanitize.
258 259 Returns:
260 The sanitized value, if the input is of a floating-point type,
261 otherwise the given value itself.
262 263 *******************************************************************************/264 265 privatestaticTgetSanitized ( T ) ( Tval )
266 {
267 staticif (isFloatingPointType!(T))
268 {
269 if (isNaN(val))
270 {
271 return0.0;
272 }
273 elseif (isInfinity(val))
274 {
275 returnT.max;
276 }
277 }
278 279 returnval;
280 }
281 282 // Test sanitization of a floating type value as NaN283 unittest284 {
285 test!("==")(getSanitized(double.init), 0.0);
286 }
287 288 // Test sanitization of a floating point value as infinity289 unittest290 {
291 test!("==")(getSanitized(double.infinity), double.max);
292 }
293 294 // Test that sanitization does not alter any floating point value that is295 // not NaN or Inf.296 unittest297 {
298 doublepi = 3.141592;
299 test!("==")(getSanitized(pi), 3.141592);
300 }
301 302 303 version (UnitTest)
304 {
305 importocean.core.Test;
306 importocean.transition;
307 308 structStatistics309 {
310 ulongup_time_s;
311 size_tcount;
312 floatratio;
313 doublefraction;
314 realvery_real;
315 }
316 317 structLabels318 {
319 hash_tid;
320 cstringjob;
321 floatperf;
322 }
323 }
324 325 /// Test collecting populated stats, but without any label.326 unittest327 {
328 autoexpected = "up_time_s 3600\ncount 347\nratio 3.14\nfraction 6.023\n" ~
329 "very_real 0.43\n";
330 331 mstringactual;
332 formatStats(Statistics(3600, 347, 3.14, 6.023, 0.43), actual);
333 334 test!("==")(actual, expected);
335 }
336 337 /// Test collecting populated stats with one label338 unittest339 {
340 autoexpected =
341 "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~
342 "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~
343 "very_real {id=\"123.034\"} 0.43\n";
344 345 mstringactual;
346 formatStats!("id")(
347 Statistics(3600, 347, 3.14, 6.023, 0.43), 123.034, actual);
348 349 test!("==")(actual, expected);
350 }
351 352 /// Test collecting stats having initial values with multiple labels353 unittest354 {
355 autoexpected =
356 "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3600\n" ~
357 "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 347\n" ~
358 "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3.14\n" ~
359 "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 6.023\n" ~
360 "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.43\n";
361 362 mstringactual;
363 formatStats(Statistics(3600, 347, 3.14, 6.023, 0.43),
364 Labels(1_235_813, "ocean", 3.14159), actual);
365 366 test!("==")(actual, expected);
367 }