1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  * Radosław Korzeniewski, MMXVIII
21  * radoslaw@korzeniewski.net, radekk@inteos.pl
22  * Inteos Sp. z o.o. http://www.inteos.pl/
23  *
24  * This is a Bacula statistics support utilities.
25  * Author: Radosław Korzeniewski, radekk@inteos.pl, Inteos Sp. z o.o.
26  */
27 
28 #include "bacula.h"
29 
30 /*
31  * Scans a display format parameter and return appropriate enum value.
32  *
33  * in:
34  *    buf - the command argument string to check
35  * out:
36  *    appropriate display_format_t value, defaults to COLLECT_SIMPLE
37  */
scandisplayformat(POOLMEM * buf)38 display_format_t scandisplayformat(POOLMEM *buf)
39 {
40    if (bstrcmp(buf, "json")){
41       return COLLECT_JSON;
42    }
43    if (bstrcmp(buf, "full")){
44       return COLLECT_FULL;
45    }
46    return COLLECT_SIMPLE;
47 };
48 
49 /*
50  * Render a simple representation of the metric into a buffer.
51  *
52  * in:
53  *    out - a memory buffer where render to
54  *    m - a bstatmetric object to render its value
55  * out:
56  *    rendered metric name and value in simple format at buf
57  */
rendermetricsimple(POOL_MEM & out,bstatmetric * m)58 void rendermetricsimple(POOL_MEM &out, bstatmetric *m)
59 {
60    POOL_MEM buf(PM_MESSAGE);
61 
62    m->render_metric_value(buf);
63    Mmsg(out, "%s=%s\n", m->name, buf.c_str());
64 };
65 
66 /*
67  * Render a JSON representation of the metric into a buffer.
68  *
69  * in:
70  *    out - a memory buffer where render to
71  *    m - a bstatmetric object to render its value
72  * out:
73  *    rendered metric in JSON format at buf
74  *
75  * JSON example output implemented:
76  * [
77  *    {
78  *       name: "bacula.jobs.all",
79  *       value: 228,
80  *       type: "Integer",
81  *       unit: "Jobs",
82  *       description: "Number of all jobs."
83  *    },
84  * ]
85  * - the array brackets are delivered outside this function
86  */
rendermetricjson(POOL_MEM & out,bstatmetric * m,int nr)87 void rendermetricjson(POOL_MEM &out, bstatmetric *m, int nr)
88 {
89    POOL_MEM buf(PM_MESSAGE);
90 
91    m->render_metric_value(buf, true);
92    Mmsg(out, "%s  {\n    \"name\": \"%s\",\n    \"value\": %s,\n    \"type\": \"%s\",\n    \"unit\": \"%s\",\n    \"description\": \"%s\"\n  }",
93          nr > 0 ? ",\n":"\n", m->name, buf.c_str(), m->metric_type_str(), m->metric_unit_str(), m->description);
94 };
95 
96 /*
97  * Render a full representation of the metric into a buffer.
98  *
99  * in:
100  *    out - a memory buffer where render to
101  *    m - a bstatmetric object to render its value
102  * out:
103  *    rendered metric at buf
104  */
rendermetricfull(POOL_MEM & out,bstatmetric * m)105 void rendermetricfull(POOL_MEM &out, bstatmetric *m)
106 {
107    POOL_MEM buf(PM_MESSAGE);
108 
109    m->render_metric_value(buf);
110    Mmsg(out, "name=\"%s\" value=%s type=%s unit=%s descr=\"%s\"\n", m->name, buf.c_str(), m->metric_type_str(),
111             m->metric_unit_str(), m->description);
112 };
113 
114 /*
115  * Render metric into a buffer based on display format provided.
116  *
117  * in:
118  *    out - a memory buffer where render to
119  *    m - a bstatmetric object to render its value
120  *    format - display format enum
121  * out:
122  *    rendered metric at buf
123  */
rendermetric(POOL_MEM & out,bstatmetric * m,display_format_t format,int nr)124 void rendermetric(POOL_MEM &out, bstatmetric *m, display_format_t format, int nr)
125 {
126    switch (format){
127       case COLLECT_SIMPLE:
128          rendermetricsimple(out, m);
129          break;
130       case COLLECT_JSON:
131          rendermetricjson(out, m, nr);
132          break;
133       case COLLECT_FULL:
134          rendermetricfull(out, m);
135          break;
136    }
137 };
138 
139 /*
140  * Return a string representation of the display format enum.
141  */
displayformat2str(display_format_t format)142 const char *displayformat2str(display_format_t format)
143 {
144    switch (format){
145       case COLLECT_SIMPLE:
146          return "simple";
147       case COLLECT_JSON:
148          return "json";
149       case COLLECT_FULL:
150          return "full";
151       default:
152          return "simple";
153    }
154 };
155 
156 /*
157  * Return a string representation of the collector status.
158  *
159  * in:
160  *    res_collector - a COLLECTOR resource for collector backend
161  * out:
162  *    string representation of the collector status
163  */
str_collector_status(COLLECTOR & res_collector)164 const char *str_collector_status(COLLECTOR &res_collector)
165 {
166    const char *status;
167 
168    if (res_collector.valid){
169       status = res_collector.running?"running":"stopped";
170    } else {
171       status = res_collector.running?"waiting to exit":"stopped";
172    }
173    return status;
174 };
175 
176 /*
177  * Return a string representation of the collector spooling status.
178  *
179  * in:
180  *    res_collector - a COLLECTOR resource for collector backend
181  * out:
182  *    string representation of the collector spooling status
183  */
str_collector_spooling(COLLECTOR & res_collector)184 const char *str_collector_spooling(COLLECTOR &res_collector)
185 {
186    const char *spool;
187 
188    if (res_collector.spool_directory){
189       /* spooling defined */
190       switch (res_collector.spooled){
191          case BCOLLECT_SPOOL_YES:
192             spool = "in progress";
193             break;
194          case BCOLLECT_SPOOL_DESPOOL:
195             spool = "despooling now";
196             break;
197          case BCOLLECT_SPOOL_NO:
198             spool = "enabled";
199             break;
200          default:
201             spool = "unknown (enabled)";
202       }
203    } else {
204       spool = "disabled";
205    }
206    return spool;
207 };
208 
209 /*
210  * A support function renders a Statistics resource status into a buffer.
211  *
212  * in:
213  *    res_collector - a COLLECTOR resource class to display
214  *    buf - a POLL_MEM buffer to render into
215  * out:
216  *    the length of rendered text
217  */
render_collector_status(COLLECTOR & res_collector,POOL_MEM & buf)218 int render_collector_status(COLLECTOR &res_collector, POOL_MEM &buf)
219 {
220    const char *status, *spool;
221    char dt[MAX_TIME_LENGTH];
222    time_t t;
223    utime_t i;
224    int len;
225    POOL_MEM errmsg(PM_MESSAGE);
226 
227    res_collector.lock();
228    status = str_collector_status(res_collector);
229    t = res_collector.timestamp;
230    i = res_collector.interval;
231    spool = str_collector_spooling(res_collector);
232    if (res_collector.errmsg && strlen(res_collector.errmsg)){
233       Mmsg(errmsg, " lasterror=%s\n", res_collector.errmsg);
234    } else {
235       pm_strcpy(errmsg, "");
236    }
237    res_collector.unlock();
238 
239    bstrftime_nc(dt, sizeof(dt), t);
240    len = Mmsg(buf, "Statistics backend: %s is %s\n type=%i lasttimestamp=%s\n interval=%d secs\n spooling=%s\n%s\n",
241          res_collector.hdr.name, status,
242          res_collector.type, dt,
243          i, spool,
244          errmsg.c_str());
245    return len;
246 };
247 
248 /*
249  * A support function renders a Statistics resource status into an OutputWriter for APIv2.
250  *
251  * in:
252  *    res_collector - a COLLECTOR resource class to display
253  *    ow - OutputWriter for apiv2
254  * out:
255  *    rendered status in OutputWritter buffer
256  */
api_render_collector_status(COLLECTOR & res_collector,OutputWriter & ow)257 void api_render_collector_status(COLLECTOR &res_collector, OutputWriter &ow)
258 {
259    const char *status, *spool;
260    time_t t;
261    utime_t i;
262 
263    res_collector.lock();
264    status = str_collector_status(res_collector);
265    t = res_collector.timestamp;
266    i = res_collector.interval;
267    spool = str_collector_spooling(res_collector);
268    res_collector.unlock();
269    ow.get_output(
270          OT_START_OBJ,
271          OT_STRING,  "name",           res_collector.hdr.name,
272          OT_STRING,  "status",         status,
273          OT_INT,     "interval",       i,
274          OT_UTIME,   "lasttimestamp",  t,
275          OT_STRING,  "spooling",       spool,
276          OT_STRING,  "lasterror",      NPRTB(res_collector.errmsg),
277          OT_END_OBJ,
278          OT_END
279    );
280    return;
281 };
282 
283 /*
284  * Replace dot '.' character for "%32" to avoid metric level split
285  */
replace_dot_metric_name(POOL_MEM & out,const char * name)286 char *replace_dot_metric_name(POOL_MEM &out, const char *name)
287 {
288    char *p, *q;
289    POOL_MEM tmp(PM_NAME);
290 
291    pm_strcpy(out, NULL);
292    pm_strcpy(tmp, name);
293    p = tmp.c_str();
294    while((q = strchr(p, '.')) != NULL){
295       /* q is the dot char and p is a start of the substring to copy */
296       *q = 0;  // temporary terminate substring
297       pm_strcat(out, p);
298       pm_strcat(out, "%32");
299       // printf(">%s<", out.c_str());
300       p = q + 1;  // next substring
301    }
302    pm_strcat(out, p);
303    return out.c_str();
304 };
305 
306 /*
307  * Return the metric name updated with Statistics Prefix parameter if defined.
308  */
render_metric_prefix(COLLECTOR * collector,POOL_MEM & buf,bstatmetric * item)309 void render_metric_prefix(COLLECTOR *collector, POOL_MEM &buf, bstatmetric *item)
310 {
311    POOL_MEM name(PM_NAME);
312 
313    if (collector && item){
314       if (collector->mangle_name){
315          replace_dot_metric_name(name, item->name);
316       } else {
317          Mmsg(name, "%s", item->name);
318       }
319       if (collector->prefix){
320          /* $prefix.$metric */
321          Mmsg(buf, "%s.%s", collector->prefix, name.c_str());
322       } else {
323          /* $metric */
324          Mmsg(buf, "%s", name.c_str());
325       }
326       Dmsg2(1500, "Statistics: %s met&prefix: %s\n", collector->hdr.name, buf.c_str());
327    }
328 };
329 
330 
331 #ifndef TEST_PROGRAM
332 #define TEST_PROGRAM_A
333 #endif
334 
335 #ifdef TEST_PROGRAM
336 #include "unittests.h"
337 
338 struct test {
339    const int nr;
340    const char *in;
341    const char *out;
342 };
343 
344 static struct test tests[] = {
345    {1, "abc.def", "abc%32def"},
346    {2, "", ""},
347    {3, ".abc", "%32abc"},
348    {4, "abc.", "abc%32"},
349    {5, "abc..def", "abc%32%32def"},
350    {6, "abc..", "abc%32%32"},
351    {7, "..def", "%32%32def"},
352    {8, ".......", "%32%32%32%32%32%32%32"},
353    {0, NULL, NULL},
354 };
355 
356 #define ntests ((int)(sizeof(tests)/sizeof(struct test)))
357 
main()358 int main()
359 {
360    Unittests collect_test("collect_test");
361    POOL_MEM name(PM_NAME);
362    char buf[64];
363 
364    for (int i = 0; i < ntests; i++) {
365       if (tests[i].nr > 0){
366          snprintf(buf, 64, "Checking mangle test: %d - '%s'", tests[i].nr, tests[i].in);
367          ok(strcmp(replace_dot_metric_name(name, tests[i].in), tests[i].out) == 0, buf);
368       }
369    }
370 
371    return report();
372 }
373 #endif /* TEST_PROGRAM */
374