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