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 /// ditto
19 public class CollectorRegistry
20 {
21     import ocean.transition;
22     import ocean.util.prometheus.collector.Collector : Collector;
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     import Traits = ocean.core.Traits;
102     import ocean.transition;
103     import ocean.util.prometheus.collector.Collector;
104 
105     ///
106     public struct Statistics
107     {
108         ulong up_time_s;
109         size_t count;
110         float ratio;
111         double fraction;
112         real very_real;
113     }
114 
115     ///
116     public struct Labels
117     {
118         hash_t id;
119         cstring job;
120         float perf;
121     }
122 
123     private class ExampleStats
124     {
125         Statistics test_stats;
126         Labels test_labels;
127 
128         ///
129         public void setTestStats ( Statistics stats )
130         {
131             test_stats = stats;
132         }
133 
134         ///
135         public void setTestLabels ( Labels labels )
136         {
137             test_labels = labels;
138         }
139 
140         ///
141         public void collectDg1 ( Collector collector )
142         {
143             collector.collect!("id")(test_stats, 123.034);
144         }
145 
146         ///
147         public void collectDg2 ( Collector collector )
148         {
149             collector.collect(test_stats, test_labels);
150         }
151     }
152 }
153 
154 /// Test collection from a single delegate
155 unittest
156 {
157     auto stats = new ExampleStats();
158     auto registry = new CollectorRegistry([&stats.collectDg1]);
159 
160     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
161 
162     test!("==")(registry.collect(),
163         "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~
164         "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~
165         "very_real {id=\"123.034\"} 0.43\n");
166 }
167 
168 /// Test collection from more than one delegates
169 unittest
170 {
171     auto stats = new ExampleStats();
172     auto registry = new CollectorRegistry([&stats.collectDg1]);
173     registry.addCollector(&stats.collectDg2);
174 
175     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
176     stats.setTestLabels(Labels(1_235_813, "ocean", 3.14159));
177 
178     test!("==")(registry.collect(),
179         "up_time_s {id=\"123.034\"} 3600\ncount {id=\"123.034\"} 347\n" ~
180         "ratio {id=\"123.034\"} 3.14\nfraction {id=\"123.034\"} 6.023\n" ~
181         "very_real {id=\"123.034\"} 0.43\n" ~
182 
183         "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3600\n" ~
184         "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 347\n" ~
185         "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 3.14\n" ~
186         "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 6.023\n" ~
187         "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.43\n");
188 }
189 
190 /// Test that the collections are not accumulated upon more than one call to
191 /// the `collect` method.
192 unittest
193 {
194     auto stats = new ExampleStats();
195     auto registry = new CollectorRegistry([&stats.collectDg2]);
196 
197     stats.setTestStats(Statistics(3600, 347, 3.14, 6.023, 0.43));
198     stats.setTestLabels(Labels(1_235_813, "ocean", 3.14159));
199 
200     auto collected = registry.collect();
201 
202     // Update the stats
203     stats.setTestStats(Statistics(4200, 257, 2.345, 1.098, 0.56));
204 
205     // A 2nd call to `collect` should return the updated stats, without the
206     // previous values anywhere in it.
207     test!("==")(registry.collect(),
208         "up_time_s {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 4200\n" ~
209         "count {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 257\n" ~
210         "ratio {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 2.345\n" ~
211         "fraction {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 1.098\n" ~
212         "very_real {id=\"1235813\",job=\"ocean\",perf=\"3.14159\"} 0.56\n");
213 }