1 /*******************************************************************************
2 
3     Contains a collection of prometheus stat collectors that acts as an
4     interface between the response handler, and stat collectors from every
5     individual metric and label set.
6 
7     Copyright:
8         Copyright (c) 2019 dunnhumby Germany GmbH.
9         All rights reserved
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE.txt for details.
13 
14 *******************************************************************************/
15 
16 module ocean.util.prometheus.collector.CollectorRegistry;
17 
18 import ocean.meta.types.Qualifiers;
19 import ocean.util.prometheus.collector.Collector : Collector;
20 
21 /// ditto
22 public class CollectorRegistry
23 {
24     /// An alias for the type of delegates that are called for stat collection
25     public alias void delegate ( Collector ) CollectionDg;
26 
27     /// An array of delegates that are called for stat collection
28     private CollectionDg[] collector_callbacks;
29 
30     /// The collector to use for stat collection. This acts as the actual
31     /// parameter for the delegates in `collector_callbacks`.
32     private Collector collector;
33 
34     /***************************************************************************
35 
36         Constructor, that accepts an array of stat collection callbacks.
37 
38         Params:
39             callbacks = An array of delegates to be called for stat collection.
40 
41     ***************************************************************************/
42 
43     public this ( scope CollectionDg[] callbacks )
44     {
45         this.collector = new Collector();
46         this.collector_callbacks = callbacks;
47     }
48 
49     /***************************************************************************
50 
51         Appends a stat collector callback to the list of existing ones.
52         May result in a re-allocation of the entire buffer storing the
53         callbacks.
54 
55         Params:
56             collector = The stat collector to append.
57 
58     ***************************************************************************/
59 
60     public void addCollector ( scope CollectionDg collector )
61     {
62         this.collector_callbacks ~= collector;
63     }
64 
65     /***************************************************************************
66 
67         Collects stats by calling all delegates specified for stat collection,
68         formats them using `ocean.util.prometheus.collector.StatFormatter`
69         eventually, and finally returns the accumulated result. The
70         specifications of the format can be found at
71         `https://prometheus.io/docs/instrumenting/exposition_formats/`.
72 
73         Returns:
74             The stats accumulated since last time this method was called.
75 
76     ***************************************************************************/
77 
78     public cstring collect ( )
79     {
80         this.collector.reset();
81 
82         try
83         {
84             foreach (ref callback; this.collector_callbacks)
85             {
86                 callback(this.collector);
87             }
88         }
89         catch (Exception ex)
90         {
91             throw ex;
92         }
93 
94         return this.collector.getCollection();
95     }
96 }
97 
98 version (unittest)
99 {
100     import ocean.core.Test;
101 
102     import ocean.util.prometheus.collector.StatFormatter : Labels, Statistics;
103 
104     private class ExampleStats
105     {
106         Statistics test_stats;
107         Labels test_labels;
108 
109         ///
110         public void setTestStats ( Statistics stats )
111         {
112             test_stats = stats;
113         }
114 
115         ///
116         public void setTestLabels ( Labels labels )
117         {
118             test_labels = labels;
119         }
120 
121         ///
122         public void collectDg1 ( Collector collector )
123         {
124             collector.collect!("id")(test_stats, 123.034);
125         }
126 
127         ///
128         public void collectDg2 ( Collector collector )
129         {
130             collector.collect(test_stats, test_labels);
131         }
132     }
133 }
134 
135 /// Test collection from a single delegate
136 unittest
137 {
138     auto stats = new ExampleStats();
139     auto registry = new CollectorRegistry([&stats.collectDg1]);
140 
141     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
142 
143     test!("==")(registry.collect(),
144         "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~
145         "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~
146         "very_real {id=\"123.034\"} 0.43\n");
147 }
148 
149 /// Test collection from more than one delegates
150 unittest
151 {
152     auto stats = new ExampleStats();
153     auto registry = new CollectorRegistry([&stats.collectDg1]);
154     registry.addCollector(&stats.collectDg2);
155 
156     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
157     stats.setTestLabels(Labels(1_235_813, "ocean", 3.14159));
158 
159     test!("==")(registry.collect(),
160         "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~
161         "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~
162         "very_real {id=\"123.034\"} 0.43\n" ~
163 
164         "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3600\n" ~
165         "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 347\n" ~
166         "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3.14\n" ~
167         "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 6.023\n" ~
168         "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.43\n");
169 }
170 
171 /// Test that the collections are not accumulated upon more than one call to
172 /// the `collect` method.
173 unittest
174 {
175     auto stats = new ExampleStats();
176     auto registry = new CollectorRegistry([&stats.collectDg2]);
177 
178     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
179     stats.setTestLabels(Labels(1_235_813, "ocean", 3.14159));
180 
181     auto collected = registry.collect();
182 
183     // Update the stats
184     stats.setTestStats(Statistics(4200, 257, 2.345, 1.098, 0.56));
185 
186     // A 2nd call to `collect` should return the updated stats, without the
187     // previous values anywhere in it.
188     test!("==")(registry.collect(),
189         "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 4200\n" ~
190         "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 257\n" ~
191         "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 2.345\n" ~
192         "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 1.098\n" ~
193         "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.56\n");
194 }