src/utils_format_stackdriver.c: Address code review comments.
[collectd.git] / src / utils_format_stackdriver.c
1 /**
2  * collectd - src/utils_format_stackdriver.c
3  * ISC license
4  *
5  * Copyright (C) 2017  Florian Forster
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  *
19  * Authors:
20  *   Florian Forster <octo at collectd.org>
21  **/
22
23 #include "collectd.h"
24
25 #include "utils_format_stackdriver.h"
26
27 #include "common.h"
28 #include "plugin.h"
29 #include "utils_avltree.h"
30 #include "utils_cache.h"
31 #include "utils_time.h"
32
33 #include <yajl/yajl_gen.h>
34 #include <yajl/yajl_parse.h>
35 #if HAVE_YAJL_YAJL_VERSION_H
36 #include <yajl/yajl_version.h>
37 #endif
38
39 struct sd_output_s {
40   sd_resource_t *res;
41   yajl_gen gen;
42   c_avl_tree_t *staged;
43   c_avl_tree_t *metric_descriptors;
44 };
45
46 struct sd_label_s {
47   char *key;
48   char *value;
49 };
50 typedef struct sd_label_s sd_label_t;
51
52 struct sd_resource_s {
53   char *type;
54
55   sd_label_t *labels;
56   size_t labels_num;
57 };
58
59 static int json_string(yajl_gen gen, char const *s) /* {{{ */
60 {
61   yajl_gen_status status =
62       yajl_gen_string(gen, (unsigned char const *)s, strlen(s));
63   if (status != yajl_gen_status_ok)
64     return (int)status;
65
66   return 0;
67 } /* }}} int json_string */
68
69 static int json_time(yajl_gen gen, cdtime_t t) {
70   char buffer[64];
71
72   size_t status = rfc3339(buffer, sizeof(buffer), t);
73   if (status != 0) {
74     return status;
75   }
76
77   return json_string(gen, buffer);
78 } /* }}} int json_time */
79
80 /* MonitoredResource
81  *
82  * {
83  *   "type": "library.googleapis.com/book",
84  *   "labels": {
85  *     "/genre": "fiction",
86  *     "/media": "paper"
87  *     "/title": "The Old Man and the Sea"
88  *   }
89  * }
90  */
91 static int format_gcm_resource(yajl_gen gen, sd_resource_t *res) /* {{{ */
92 {
93   yajl_gen_map_open(gen);
94
95   int status = json_string(gen, "type") || json_string(gen, res->type);
96   if (status != 0)
97     return status;
98
99   if (res->labels_num != 0) {
100     status = json_string(gen, "labels");
101     if (status != 0)
102       return status;
103
104     yajl_gen_map_open(gen);
105     for (size_t i = 0; i < res->labels_num; i++) {
106       status = json_string(gen, res->labels[i].key) ||
107                json_string(gen, res->labels[i].value);
108       if (status != 0)
109         return status;
110     }
111     yajl_gen_map_close(gen);
112   }
113
114   yajl_gen_map_close(gen);
115   return 0;
116 } /* }}} int format_gcm_resource */
117
118 /* TypedValue
119  *
120  * {
121  *   // Union field, only one of the following:
122  *   "int64Value": string,
123  *   "doubleValue": number,
124  * }
125  */
126 static int format_gcm_typed_value(yajl_gen gen, int ds_type,
127                                   value_t v) /* {{{ */
128 {
129   char integer[21];
130
131   yajl_gen_map_open(gen);
132   if (ds_type == DS_TYPE_GAUGE) {
133     int status = json_string(gen, "doubleValue");
134     if (status != 0)
135       return status;
136
137     status = (int)yajl_gen_double(gen, (double)v.gauge);
138     if (status != yajl_gen_status_ok)
139       return status;
140   } else {
141     switch (ds_type) {
142     case DS_TYPE_COUNTER:
143       snprintf(integer, sizeof(integer), "%llu", v.counter);
144       break;
145     case DS_TYPE_DERIVE:
146       snprintf(integer, sizeof(integer), "%" PRIi64, v.derive);
147       break;
148     case DS_TYPE_ABSOLUTE:
149       snprintf(integer, sizeof(integer), "%" PRIu64, v.derive);
150       break;
151     default:
152       ERROR("format_gcm_typed_value: unknown value type %d.", ds_type);
153       return EINVAL;
154     }
155
156     int status = json_string(gen, "int64Value") || json_string(gen, integer);
157     if (status != 0) {
158       return status;
159     }
160   }
161   yajl_gen_map_close(gen);
162
163   return 0;
164 } /* }}} int format_gcm_typed_value */
165
166 /* MetricKind
167  *
168  * enum(
169  *   "CUMULATIVE",
170  *   "GAUGE"
171  * )
172 */
173 static int format_metric_kind(yajl_gen gen, int ds_type) {
174   return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "GAUGE" : "CUMULATIVE");
175 }
176
177 /* ValueType
178  *
179  * enum(
180  *   "DOUBLE",
181  *   "INT64"
182  * )
183 */
184 static int format_value_type(yajl_gen gen, int ds_type) {
185   return json_string(gen, (ds_type == DS_TYPE_GAUGE) ? "DOUBLE" : "INT64");
186 }
187
188 static int metric_type(char *buffer, size_t buffer_size, data_set_t const *ds,
189                        value_list_t const *vl, int ds_index) {
190   /* {{{ */
191   char const *ds_name = ds->ds[ds_index].name;
192
193 #define GCM_PREFIX "custom.googleapis.com/collectd/"
194   if ((ds_index != 0) || strcmp("value", ds_name) != 0) {
195     snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s_%s", vl->plugin, vl->type,
196              ds_name);
197   } else {
198     snprintf(buffer, buffer_size, GCM_PREFIX "%s/%s", vl->plugin, vl->type);
199   }
200
201   char const *whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
202                           "abcdefghijklmnopqrstuvwxyz"
203                           "0123456789_/";
204   char *ptr = buffer + strlen(GCM_PREFIX);
205   size_t ok_len;
206   while ((ok_len = strspn(ptr, whitelist)) != strlen(ptr)) {
207     ptr[ok_len] = '_';
208     ptr += ok_len;
209   }
210
211   return 0;
212 } /* }}} int metric_type */
213
214 /* The metric type, including its DNS name prefix. The type is not URL-encoded.
215  * All user-defined custom metric types have the DNS name custom.googleapis.com.
216  * Metric types should use a natural hierarchical grouping. */
217 static int format_metric_type(yajl_gen gen, data_set_t const *ds,
218                               value_list_t const *vl, int ds_index) {
219   /* {{{ */
220   char buffer[4 * DATA_MAX_NAME_LEN];
221   metric_type(buffer, sizeof(buffer), ds, vl, ds_index);
222
223   return json_string(gen, buffer);
224 } /* }}} int format_metric_type */
225
226 /* TimeInterval
227  *
228  * {
229  *   "endTime": string,
230  *   "startTime": string,
231  * }
232  */
233 static int format_time_interval(yajl_gen gen, int ds_type,
234                                 value_list_t const *vl) {
235   /* {{{ */
236   yajl_gen_map_open(gen);
237
238   int status = json_string(gen, "endTime") || json_time(gen, vl->time);
239   if (status != 0)
240     return status;
241
242   if (ds_type != DS_TYPE_GAUGE) {
243     cdtime_t start_time = 0;
244     if (uc_meta_data_get_unsigned_int(vl, "gcm:start_time", &start_time) != 0) {
245       start_time = vl->time;
246       uc_meta_data_add_unsigned_int(vl, "gcm:start_time", start_time);
247     }
248
249     int status = json_string(gen, "startTime") || json_time(gen, start_time);
250     if (status != 0)
251       return status;
252   }
253
254   yajl_gen_map_close(gen);
255   return 0;
256 } /* }}} int format_time_interval */
257
258 /* Point
259  *
260  * {
261  *   "interval": {
262  *     object(TimeInterval)
263  *   },
264  *   "value": {
265  *     object(TypedValue)
266  *   },
267  * }
268  */
269 static int format_point(yajl_gen gen, data_set_t const *ds,
270                         value_list_t const *vl, int ds_index) {
271   /* {{{ */
272   yajl_gen_map_open(gen);
273
274   int ds_type = ds->ds[ds_index].type;
275
276   int status = json_string(gen, "interval") ||
277                format_time_interval(gen, ds_type, vl) ||
278                json_string(gen, "value") ||
279                format_gcm_typed_value(gen, ds_type, vl->values[ds_index]);
280   if (status != 0)
281     return status;
282
283   yajl_gen_map_close(gen);
284   return 0;
285 } /* }}} int format_point */
286
287 /* Metric
288  *
289  * {
290  *   "type": string,
291  *   "labels": {
292  *     string: string,
293  *     ...
294  *   },
295  * }
296  */
297 static int format_metric(yajl_gen gen, data_set_t const *ds,
298                          value_list_t const *vl, int ds_index) {
299   /* {{{ */
300   yajl_gen_map_open(gen);
301
302   int status = json_string(gen, "type") ||
303                format_metric_type(gen, ds, vl, ds_index) ||
304                json_string(gen, "labels");
305   if (status != 0) {
306     return status;
307   }
308
309   yajl_gen_map_open(gen);
310   status = json_string(gen, "host") || json_string(gen, vl->host) ||
311            json_string(gen, "plugin_instance") ||
312            json_string(gen, vl->plugin_instance) ||
313            json_string(gen, "type_instance") ||
314            json_string(gen, vl->type_instance);
315   if (status != 0) {
316     return status;
317   }
318   yajl_gen_map_close(gen);
319
320   yajl_gen_map_close(gen);
321   return 0;
322 } /* }}} int format_metric */
323
324 /* TimeSeries
325  *
326  * {
327  *   "metric": {
328  *     object(Metric)
329  *   },
330  *   "resource": {
331  *     object(MonitoredResource)
332  *   },
333  *   "metricKind": enum(MetricKind),
334  *   "valueType": enum(ValueType),
335  *   "points": [
336  *     {
337  *       object(Point)
338  *     }
339  *   ],
340  * }
341  */
342 static int format_time_series(yajl_gen gen, data_set_t const *ds,
343                               value_list_t const *vl, int ds_index,
344                               sd_resource_t *res) {
345   /* {{{ */
346   yajl_gen_map_open(gen);
347
348   int ds_type = ds->ds[ds_index].type;
349
350   int status =
351       json_string(gen, "metric") || format_metric(gen, ds, vl, ds_index) ||
352       json_string(gen, "resource") || format_gcm_resource(gen, res) ||
353       json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
354       json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
355       json_string(gen, "points");
356   if (status != 0)
357     return status;
358
359   yajl_gen_array_open(gen);
360   if ((status = format_point(gen, ds, vl, ds_index)) != 0) {
361     return status;
362   }
363   yajl_gen_array_close(gen);
364
365   yajl_gen_map_close(gen);
366   return 0;
367 } /* }}} int format_time_series */
368
369 /* Request body
370  *
371  * {
372  *   "timeSeries": [
373  *     {
374  *       object(TimeSeries)
375  *     }
376  *   ],
377  * }
378  */
379 static int sd_output_initialize(sd_output_t *out) /* {{{ */
380 {
381   yajl_gen_map_open(out->gen);
382
383   int status = json_string(out->gen, "timeSeries");
384   if (status != 0) {
385     return status;
386   }
387
388   yajl_gen_array_open(out->gen);
389   return 0;
390 } /* }}} int sd_output_initialize */
391
392 static int sd_output_finalize(sd_output_t *out) /* {{{ */
393 {
394   yajl_gen_array_close(out->gen);
395   yajl_gen_map_close(out->gen);
396
397   return 0;
398 } /* }}} int sd_output_finalize */
399
400 static void sd_output_reset_staged(sd_output_t *out) /* {{{ */
401 {
402   void *key = NULL;
403
404   while (c_avl_pick(out->staged, &key, &(void *){NULL}) == 0)
405     sfree(key);
406 } /* }}} void sd_output_reset_staged */
407
408 sd_output_t *sd_output_create(sd_resource_t *res) /* {{{ */
409 {
410   sd_output_t *out = calloc(1, sizeof(*out));
411   if (out == NULL)
412     return NULL;
413
414   out->res = res;
415
416   out->gen = yajl_gen_alloc(/* funcs = */ NULL);
417   if (out->gen == NULL) {
418     sd_output_destroy(out);
419     return NULL;
420   }
421
422   out->staged = c_avl_create((void *)strcmp);
423   if (out->staged == NULL) {
424     sd_output_destroy(out);
425     return NULL;
426   }
427
428   out->metric_descriptors = c_avl_create((void *)strcmp);
429   if (out->metric_descriptors == NULL) {
430     sd_output_destroy(out);
431     return NULL;
432   }
433
434   sd_output_initialize(out);
435
436   return out;
437 } /* }}} sd_output_t *sd_output_create */
438
439 void sd_output_destroy(sd_output_t *out) /* {{{ */
440 {
441   if (out == NULL)
442     return;
443
444   if (out->metric_descriptors != NULL) {
445     void *key = NULL;
446     while (c_avl_pick(out->metric_descriptors, &key, &(void *){NULL}) == 0) {
447       sfree(key);
448     }
449     c_avl_destroy(out->metric_descriptors);
450     out->metric_descriptors = NULL;
451   }
452
453   if (out->staged != NULL) {
454     sd_output_reset_staged(out);
455     c_avl_destroy(out->staged);
456     out->staged = NULL;
457   }
458
459   if (out->gen != NULL) {
460     yajl_gen_free(out->gen);
461     out->gen = NULL;
462   }
463
464   if (out->res != NULL) {
465     sd_resource_destroy(out->res);
466     out->res = NULL;
467   }
468
469   sfree(out);
470 } /* }}} void sd_output_destroy */
471
472 int sd_output_add(sd_output_t *out, data_set_t const *ds,
473                   value_list_t const *vl) /* {{{ */
474 {
475   /* first, check that we have all appropriate metric descriptors. */
476   for (size_t i = 0; i < ds->ds_num; i++) {
477     char buffer[4 * DATA_MAX_NAME_LEN];
478     metric_type(buffer, sizeof(buffer), ds, vl, i);
479
480     if (c_avl_get(out->metric_descriptors, buffer, NULL) != 0) {
481       return ENOENT;
482     }
483   }
484
485   char key[6 * DATA_MAX_NAME_LEN];
486   int status = FORMAT_VL(key, sizeof(key), vl);
487   if (status != 0) {
488     ERROR("sd_output_add: FORMAT_VL failed with status %d.", status);
489     return status;
490   }
491
492   if (c_avl_get(out->staged, key, NULL) == 0) {
493     return EEXIST;
494   }
495
496   for (size_t i = 0; i < ds->ds_num; i++) {
497     int status = format_time_series(out->gen, ds, vl, i, out->res);
498     if (status != 0) {
499       ERROR("sd_output_add: format_time_series failed with status %d.", status);
500       return status;
501     }
502   }
503
504   c_avl_insert(out->staged, strdup(key), NULL);
505
506   size_t json_buffer_size = 0;
507   yajl_gen_get_buf(out->gen, &(unsigned char const *){NULL}, &json_buffer_size);
508   if (json_buffer_size > 65535)
509     return ENOBUFS;
510
511   return 0;
512 } /* }}} int sd_output_add */
513
514 int sd_output_register_metric(sd_output_t *out, data_set_t const *ds,
515                               value_list_t const *vl) {
516   /* {{{ */
517   for (size_t i = 0; i < ds->ds_num; i++) {
518     char buffer[4 * DATA_MAX_NAME_LEN];
519     metric_type(buffer, sizeof(buffer), ds, vl, i);
520
521     char *key = strdup(buffer);
522     int status = c_avl_insert(out->metric_descriptors, key, NULL);
523     if (status != 0) {
524       sfree(key);
525       return status;
526     }
527   }
528
529   return 0;
530 } /* }}} int sd_output_register_metric */
531
532 char *sd_output_reset(sd_output_t *out) /* {{{ */
533 {
534   sd_output_finalize(out);
535
536   unsigned char const *json_buffer = NULL;
537   yajl_gen_get_buf(out->gen, &json_buffer, &(size_t){0});
538   char *ret = strdup((void const *)json_buffer);
539
540   sd_output_reset_staged(out);
541
542   yajl_gen_free(out->gen);
543   out->gen = yajl_gen_alloc(/* funcs = */ NULL);
544
545   sd_output_initialize(out);
546
547   return ret;
548 } /* }}} char *sd_output_reset */
549
550 sd_resource_t *sd_resource_create(char const *type) /* {{{ */
551 {
552   sd_resource_t *res = malloc(sizeof(*res));
553   if (res == NULL)
554     return NULL;
555   memset(res, 0, sizeof(*res));
556
557   res->type = strdup(type);
558   if (res->type == NULL) {
559     sfree(res);
560     return NULL;
561   }
562
563   res->labels = NULL;
564   res->labels_num = 0;
565
566   return res;
567 } /* }}} sd_resource_t *sd_resource_create */
568
569 void sd_resource_destroy(sd_resource_t *res) /* {{{ */
570 {
571   if (res == NULL)
572     return;
573
574   for (size_t i = 0; i < res->labels_num; i++) {
575     sfree(res->labels[i].key);
576     sfree(res->labels[i].value);
577   }
578   sfree(res->labels);
579   sfree(res->type);
580   sfree(res);
581 } /* }}} void sd_resource_destroy */
582
583 int sd_resource_add_label(sd_resource_t *res, char const *key,
584                           char const *value) /* {{{ */
585 {
586   if ((res == NULL) || (key == NULL) || (value == NULL))
587     return EINVAL;
588
589   sd_label_t *l =
590       realloc(res->labels, sizeof(*res->labels) * (res->labels_num + 1));
591   if (l == NULL)
592     return ENOMEM;
593
594   res->labels = l;
595   l = res->labels + res->labels_num;
596
597   l->key = strdup(key);
598   l->value = strdup(value);
599   if ((l->key == NULL) || (l->value == NULL)) {
600     sfree(l->key);
601     sfree(l->value);
602     return ENOMEM;
603   }
604
605   res->labels_num++;
606   return 0;
607 } /* }}} int sd_resource_add_label */
608
609 /* LabelDescriptor
610  *
611  * {
612  *   "key": string,
613  *   "valueType": enum(ValueType),
614  *   "description": string,
615  * }
616  */
617 static int format_label_descriptor(yajl_gen gen, char const *key) {
618   /* {{{ */
619   yajl_gen_map_open(gen);
620
621   int status = json_string(gen, "key") || json_string(gen, key) ||
622                json_string(gen, "valueType") || json_string(gen, "STRING");
623   if (status != 0) {
624     return status;
625   }
626
627   yajl_gen_map_close(gen);
628   return 0;
629 } /* }}} int format_label_descriptor */
630
631 /* MetricDescriptor
632  *
633  * {
634  *   "name": string,
635  *   "type": string,
636  *   "labels": [
637  *     {
638  *       object(LabelDescriptor)
639  *     }
640  *   ],
641  *   "metricKind": enum(MetricKind),
642  *   "valueType": enum(ValueType),
643  *   "unit": string,
644  *   "description": string,
645  *   "displayName": string,
646  * }
647  */
648 int sd_format_metric_descriptor(char *buffer, size_t buffer_size,
649                                 data_set_t const *ds, value_list_t const *vl,
650                                 int ds_index) {
651   /* {{{ */
652   yajl_gen gen = yajl_gen_alloc(/* funcs = */ NULL);
653   if (gen == NULL) {
654     return ENOMEM;
655   }
656
657   int ds_type = ds->ds[ds_index].type;
658
659   yajl_gen_map_open(gen);
660
661   int status =
662       json_string(gen, "type") || format_metric_type(gen, ds, vl, ds_index) ||
663       json_string(gen, "metricKind") || format_metric_kind(gen, ds_type) ||
664       json_string(gen, "valueType") || format_value_type(gen, ds_type) ||
665       json_string(gen, "labels");
666   if (status != 0) {
667     yajl_gen_free(gen);
668     return status;
669   }
670
671   char const *labels[] = {"host", "plugin_instance", "type_instance"};
672   yajl_gen_array_open(gen);
673
674   for (size_t i = 0; i < STATIC_ARRAY_SIZE(labels); i++) {
675     int status = format_label_descriptor(gen, labels[i]);
676     if (status != 0) {
677       yajl_gen_free(gen);
678       return status;
679     }
680   }
681
682   yajl_gen_array_close(gen);
683   yajl_gen_map_close(gen);
684
685   unsigned char const *tmp = NULL;
686   yajl_gen_get_buf(gen, &tmp, &(size_t){0});
687   sstrncpy(buffer, (void const *)tmp, buffer_size);
688
689   yajl_gen_free(gen);
690   return 0;
691 } /* }}} int sd_format_metric_descriptor */