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