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