1 /******************************************************************************* 2 3 Contains methods that collect stats from primitive data members of structs 4 or classes to respond to Prometheus queries with. 5 6 The metric designs specified by Prometheus 7 (`https://prometheus.io/docs/concepts/metric_types/`) have not been 8 implemented yet. So, as of now, this collector can process only primitive 9 data-type members from a struct or a class. However, the current Prometheus 10 stat collection framework could be enhanced to support metrics with a very 11 little effort. 12 13 In Prometheus' data model, the stats that are measured are called Metrics, 14 and the dimensions along which stats are measured are called Labels. 15 16 Metrics can be any measurable value, e.g., CPU or memory consumption. 17 18 Labels resemble key-value pairs, where the key is referred to as a label's 19 name, and the value as a label's value. A label name would refer to the name 20 of a dimension across which we want to measure stats. Correspondingly, a 21 label value would refer to a point along the said dimension. A stat can have 22 more than one label, if it is intended to be measured across multiple 23 dimensions. 24 25 Stats with labels look like the following example 26 ` 27 promhttp_metric_handler_requests_total{code="200"} 3 28 promhttp_metric_handler_requests_total{code="500"} 0 29 promhttp_metric_handler_requests_total{code="503"} 0 30 ` 31 Here `promhttp_metric_handler_requests_total` is the metric, `code` is 32 the label name, `"200"`, `"500"` and `"503"` are the label values, and `3`, 33 `0` and `0` are the respective metric values. 34 35 On the data visualization side of Prometheus, fetching stats using queries 36 is analogous to calling functions. A metric name is analogous to a function 37 name, and a label is analogous to a function parameter. Continuing with the 38 above example, the following query will return `3`: 39 ` 40 promhttp_metric_handler_requests_total {code="200"} 41 ` 42 43 (For detailed information about Prometheus-specific terminology, e.g., 44 Metrics and Labels, please refer to 45 `https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels`.) 46 47 This module contains a class which can be used to collect metrics as well as 48 labels from composite-type values, and format them in a way acceptable for 49 Prometheus. 50 51 Copyright: 52 Copyright (c) 2019 dunnhumby Germany GmbH. 53 All rights reserved 54 55 License: 56 Boost Software License Version 1.0. See LICENSE.txt for details. 57 58 *******************************************************************************/ 59 60 module ocean.util.prometheus.collector.Collector; 61 62 version (unittest) import ocean.core.Test; 63 import ocean.meta.types.Qualifiers; 64 import ocean.util.prometheus.collector.StatFormatter; 65 66 /******************************************************************************* 67 68 This class provides methods to collect metrics with no label, one label, or 69 multiple labels, using overloaded definitions of the `collect` method. 70 71 Once the desired stats have been collected, the accumulated response 72 string can be collected using the `getCollection` method. 73 74 Additionally, the `reset` method can be used to reset stat 75 collection, when the desired stats have been collected from a Collector 76 instance. 77 78 *******************************************************************************/ 79 80 public class Collector 81 { 82 /// A buffer used for storing collected stats. Is cleared when the `reset` 83 /// method is called. 84 private mstring collect_buf; 85 86 /*************************************************************************** 87 88 Returns: 89 The stats collected since the last call to the `reset` method, in a 90 textual respresentation that can be readily added to a response 91 message body. 92 The specifications of the format of the collected stats can be found 93 at `https://prometheus.io/docs/instrumenting/exposition_formats/`. 94 95 ***************************************************************************/ 96 97 public cstring getCollection ( ) 98 { 99 return this.collect_buf; 100 } 101 102 /// Reset the length of the stat collection buffer to 0. 103 public void reset ( ) 104 { 105 this.collect_buf.length = 0; 106 assumeSafeAppend(this.collect_buf); 107 } 108 109 /*************************************************************************** 110 111 Collect stats from the data members of a struct or a class and prepare 112 them to be fetched upon the next call to `getCollection`. The 113 specifications of the format of the collected stats can be found at 114 `https://prometheus.io/docs/instrumenting/exposition_formats/`. 115 116 Params: 117 ValuesT = The struct or class type to fetch the stat names from. 118 values = The struct or class to fetch stat values from. 119 120 ***************************************************************************/ 121 122 public void collect ( ValuesT ) ( ValuesT values ) 123 { 124 static assert (is(ValuesT == struct) || is(ValuesT == class), 125 "'values' parameter must be a struct or a class."); 126 127 formatStats(values, this.collect_buf); 128 } 129 130 /*************************************************************************** 131 132 Collect stats from the data members of a struct or a class, annotate 133 them with a given label name and value, and prepare them to be fetched 134 upon the next call to `getCollection`. 135 The specifications of the format of the collected stats can be found 136 at `https://prometheus.io/docs/instrumenting/exposition_formats/`. 137 138 Params: 139 LabelName = The name of the label to annotate the stats with. 140 ValuesT = The struct or class type to fetch the stat names from. 141 LabelT = The type of the label's value. 142 values = The struct or class to fetch stat values from. 143 label_val = The label value to annotate the stats with. 144 145 ***************************************************************************/ 146 147 public void collect ( string LabelName, ValuesT, LabelT ) ( 148 ValuesT values, LabelT label_val ) 149 { 150 static assert (is(ValuesT == struct) || is(ValuesT == class), 151 "'values' parameter must be a struct or a class."); 152 153 formatStats!(LabelName)(values, label_val, this.collect_buf); 154 } 155 156 /*************************************************************************** 157 158 Collect stats from the data members of a struct or a class, annotate 159 them with labels from the data members of another struct or class, and 160 prepare them to be fetched upon the next call to `getCollection`. 161 The specifications of the format of the collected stats can be found 162 at `https://prometheus.io/docs/instrumenting/exposition_formats/`. 163 164 Params: 165 ValuesT = The struct or class type to fetch the stat names from. 166 LabelsT = The struct or class type to fetch the label names from. 167 values = The struct or class to fetch stat values from. 168 labels = The struct or class holding the label values to annotate 169 the stats with. 170 171 ***************************************************************************/ 172 173 public void collect ( ValuesT, LabelsT ) ( ValuesT values, LabelsT labels ) 174 { 175 static assert (is(ValuesT == struct) || is(ValuesT == class), 176 "'values' parameter must be a struct or a class."); 177 static assert (is(LabelsT == struct) || is(LabelsT == class), 178 "'labels' parameter must be a struct or a class."); 179 180 formatStats(values, labels, this.collect_buf); 181 } 182 } 183 184 /// Test collecting populated stats, but without any label. 185 unittest 186 { 187 auto test_collector = new Collector(); 188 test_collector.collect(Statistics(3600, 347, 3.14, 6.023, 0.43)); 189 190 test!("==")(test_collector.getCollection(), 191 "up_time_s 3600\ncount 347\nratio 3.14\nfraction 6.023\n" ~ 192 "very_real 0.43\n"); 193 } 194 195 /// Test collecting populated stats with one label 196 unittest 197 { 198 auto test_collector = new Collector(); 199 test_collector.collect!("id")( 200 Statistics(3600, 347, 3.14, 6.023, 0.43), 123.034); 201 202 test!("==")(test_collector.getCollection(), 203 "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~ 204 "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~ 205 "very_real {id=\"123.034\"} 0.43\n"); 206 } 207 208 /// Test collecting stats having initial values with multiple labels 209 unittest 210 { 211 auto test_collector = new Collector(); 212 test_collector.collect(Statistics(3600, 347, 3.14, 6.023, 0.43), 213 Labels(1_235_813, "ocean", 3.14159)); 214 215 test!("==")(test_collector.getCollection(), 216 "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3600\n" ~ 217 "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 347\n" ~ 218 "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3.14\n" ~ 219 "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 6.023\n" ~ 220 "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.43\n"); 221 } 222 223 /// Test resetting collected stats 224 unittest 225 { 226 auto test_collector = new Collector(); 227 test_collector.collect!("id")(Statistics.init, 123); 228 229 test!("==")(test_collector.getCollection(), 230 "up_time_s {id=\"123\"} 0\ncount {id=\"123\"} 0\n" ~ 231 "ratio {id=\"123\"} 0\nfraction {id=\"123\"} 0\n" ~ 232 "very_real {id=\"123\"} 0\n"); 233 234 test_collector.reset(); 235 236 test!("==")(test_collector.getCollection(), ""); 237 } 238 239 // Test collecting stats having initial values, but without any label. 240 unittest 241 { 242 auto test_collector = new Collector(); 243 test_collector.collect(Statistics.init); 244 245 test!("==")(test_collector.getCollection(), 246 "up_time_s 0\ncount 0\nratio 0\nfraction 0\nvery_real 0\n"); 247 } 248 249 // Test collecting stats having initial values with one label 250 unittest 251 { 252 auto test_collector = new Collector(); 253 test_collector.collect!("id")(Statistics.init, 123); 254 255 test!("==")(test_collector.getCollection(), 256 "up_time_s {id=\"123\"} 0\ncount {id=\"123\"} 0\n" ~ 257 "ratio {id=\"123\"} 0\nfraction {id=\"123\"} 0\n" ~ 258 "very_real {id=\"123\"} 0\n"); 259 } 260 261 // Test collecting stats having initial values with multiple labels 262 unittest 263 { 264 auto test_collector = new Collector(); 265 test_collector.collect(Statistics.init, Labels.init); 266 267 test!("==")(test_collector.getCollection(), 268 "up_time_s {id=\"0\",job=\"\",perf=\"0\"} 0\n" ~ 269 "count {id=\"0\",job=\"\",perf=\"0\"} 0\n" ~ 270 "ratio {id=\"0\",job=\"\",perf=\"0\"} 0\n" ~ 271 "fraction {id=\"0\",job=\"\",perf=\"0\"} 0\n" ~ 272 "very_real {id=\"0\",job=\"\",perf=\"0\"} 0\n"); 273 } 274 275 // Test accumulation of collected stats 276 unittest 277 { 278 auto test_collector = new Collector(); 279 test_collector.collect!("id")(Statistics.init, 123); 280 281 test!("==")(test_collector.getCollection(), 282 "up_time_s {id=\"123\"} 0\ncount {id=\"123\"} 0\n" ~ 283 "ratio {id=\"123\"} 0\nfraction {id=\"123\"} 0\n" ~ 284 "very_real {id=\"123\"} 0\n"); 285 286 test_collector.collect!("id")( 287 Statistics(3600, 347, 3.14, 6.023, 0.43), 123.034); 288 289 test!("==")(test_collector.getCollection(), 290 "up_time_s {id=\"123\"} 0\ncount {id=\"123\"} 0\n" ~ 291 "ratio {id=\"123\"} 0\nfraction {id=\"123\"} 0\n" ~ 292 "very_real {id=\"123\"} 0\n" ~ 293 294 "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~ 295 "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~ 296 "very_real {id=\"123.034\"} 0.43\n"); 297 }