On Fri, Oct 07, 2022 at 12:37:10PM +0200, Claudio Jeker wrote:
> This diff adds `bgpctl show metric` which is a command that dumps some
> stats out in openmetric format. This format can be ingested by e.g.
> prometheus and used for monitoring.
> 
> The openmetric handling is in ometric.[ch]. It is fairly basic and not
> intended for long running processes. There is a struct ometric (which is
> one individual metric point). This metric point can have many different
> values with each value including an optional set of labels. Since the
> labels are used over and over again, I used a refcount on them.
> Also since most strings used in these functions are string literals I also
> don't copy them. Only the values of labels are copied since those are
> for example per peer.
> 
> Using a small extra diff in bgplgd I can export the metrics into
> prometheus and visualize them with grafana.
> 
> Consider this an MVP that can be extended with all the infos we want.

This looks pretty good to me. I like the approach and I couldn't spot
anything really wrong with it.

Some very minor comments inline for your consideration.

> -- 
> :wq Claudio
> 
> Index: Makefile
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/Makefile,v
> retrieving revision 1.17
> diff -u -p -r1.17 Makefile
> --- Makefile  2 May 2020 14:33:33 -0000       1.17
> +++ Makefile  27 Sep 2022 15:50:40 -0000
> @@ -3,7 +3,8 @@
>  .PATH:               ${.CURDIR}/../bgpd
>  
>  PROG=        bgpctl
> -SRCS=        bgpctl.c output.c output_json.c parser.c mrtparser.c util.c 
> json.c
> +SRCS=        bgpctl.c output.c output_json.c output_ometric.c parser.c \
> +     mrtparser.c util.c json.c ometric.c
>  CFLAGS+= -Wall
>  CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
>  CFLAGS+= -Wmissing-declarations
> Index: bgpctl.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
> retrieving revision 1.283
> diff -u -p -r1.283 bgpctl.c
> --- bgpctl.c  31 Aug 2022 15:00:53 -0000      1.283
> +++ bgpctl.c  25 Sep 2022 08:35:43 -0000
> @@ -79,7 +79,7 @@ int
>  main(int argc, char *argv[])
>  {
>       struct sockaddr_un       sa_un;
> -     int                      fd, n, done, ch, verbose = 0;
> +     int                      fd, n, done, numdone, ch, verbose = 0;
>       struct imsg              imsg;
>       struct network_config    net;
>       struct parse_result     *res;
> @@ -256,6 +256,12 @@ main(int argc, char *argv[])
>       case SHOW_RIB_MEM:
>               imsg_compose(ibuf, IMSG_CTL_SHOW_RIB_MEM, 0, 0, -1, NULL, 0);
>               break;
> +     case SHOW_METRIC:
> +             output = &ometric_output;
> +             numdone = 2;
> +             imsg_compose(ibuf, IMSG_CTL_SHOW_NEIGHBOR, 0, 0, -1, NULL, 0);
> +             imsg_compose(ibuf, IMSG_CTL_SHOW_RIB_MEM, 0, 0, -1, NULL, 0);
> +             break;
>       case RELOAD:
>               imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1,
>                   res->reason, sizeof(res->reason));
> @@ -366,18 +372,14 @@ main(int argc, char *argv[])
>               break;
>       }
>  
> +     output->head(res);
> +
> + again:
>       while (ibuf->w.queued)
> -             if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
> +             if (msgbuf_write(&ibuf->w) <= 0)
>                       err(1, "write error");
>  
> -     output->head(res);
> -
>       while (!done) {
> -             if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
> -                     err(1, "imsg_read error");
> -             if (n == 0)
> -                     errx(1, "pipe closed");
> -
>               while (!done) {
>                       if ((n = imsg_get(ibuf, &imsg)) == -1)
>                               err(1, "imsg_get error");
> @@ -387,6 +389,20 @@ main(int argc, char *argv[])
>                       done = show(&imsg, res);
>                       imsg_free(&imsg);
>               }
> +
> +             if (done)
> +                     break;
> +
> +             if ((n = imsg_read(ibuf)) == -1)
> +                     err(1, "imsg_read error");
> +             if (n == 0)
> +                     errx(1, "pipe closed");
> +
> +     }
> +
> +     if (res->action == SHOW_METRIC && --numdone > 0) {
> +             done = 0;
> +             goto again;
>       }
>  
>       output->tail();
> @@ -416,21 +432,29 @@ show(struct imsg *imsg, struct parse_res
>  
>       switch (imsg->hdr.type) {
>       case IMSG_CTL_SHOW_NEIGHBOR:
> +             if (output->neighbor == NULL)
> +                     break;
>               p = imsg->data;
>               output->neighbor(p, res);
>               break;
>       case IMSG_CTL_SHOW_TIMER:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(t))
>                       errx(1, "wrong imsg len");
> +             if (output->timer == NULL)
> +                     break;
>               memcpy(&t, imsg->data, sizeof(t));
>               if (t.type > 0 && t.type < Timer_Max)
>                       output->timer(&t);
>               break;
>       case IMSG_CTL_SHOW_INTERFACE:
> +             if (output->interface == NULL)
> +                     break;
>               iface = imsg->data;
>               output->interface(iface);
>               break;
>       case IMSG_CTL_SHOW_NEXTHOP:
> +             if (output->nexthop == NULL)
> +                     break;
>               nh = imsg->data;
>               output->nexthop(nh);
>               break;
> @@ -438,18 +462,24 @@ show(struct imsg *imsg, struct parse_res
>       case IMSG_CTL_SHOW_NETWORK:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(*kf))
>                       errx(1, "wrong imsg len");
> +             if (output->fib == NULL)
> +                     break;
>               kf = imsg->data;
>               output->fib(kf);
>               break;
>       case IMSG_CTL_SHOW_FIB_TABLES:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(*kt))
>                       errx(1, "wrong imsg len");
> +             if (output->fib_table == NULL)
> +                     break;
>               kt = imsg->data;
>               output->fib_table(kt);
>               break;
>       case IMSG_CTL_SHOW_RIB:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(rib))
>                       errx(1, "wrong imsg len");
> +             if (output->rib == NULL)
> +                     break;
>               memcpy(&rib, imsg->data, sizeof(rib));
>               aslen = imsg->hdr.len - IMSG_HEADER_SIZE - sizeof(rib);
>               asdata = imsg->data;
> @@ -462,6 +492,8 @@ show(struct imsg *imsg, struct parse_res
>                       warnx("bad IMSG_CTL_SHOW_RIB_COMMUNITIES received");
>                       break;
>               }
> +             if (output->communities == NULL)
> +                     break;
>               output->communities(imsg->data, ilen, res);
>               break;
>       case IMSG_CTL_SHOW_RIB_ATTR:
> @@ -470,23 +502,31 @@ show(struct imsg *imsg, struct parse_res
>                       warnx("bad IMSG_CTL_SHOW_RIB_ATTR received");
>                       break;
>               }
> +             if (output->attr == NULL)
> +                     break;
>               output->attr(imsg->data, ilen, res->flags, 0);
>               break;
>       case IMSG_CTL_SHOW_RIB_MEM:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(stats))
>                       errx(1, "wrong imsg len");
> +             if (output->rib_mem == NULL)
> +                     break;
>               memcpy(&stats, imsg->data, sizeof(stats));
>               output->rib_mem(&stats);
>               return (1);
>       case IMSG_CTL_SHOW_SET:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(set))
>                       errx(1, "wrong imsg len");
> +             if (output->set == NULL)
> +                     break;
>               memcpy(&set, imsg->data, sizeof(set));
>               output->set(&set);
>               break;
>       case IMSG_CTL_SHOW_RTR:
>               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(rtr))
>                       errx(1, "wrong imsg len");
> +             if (output->rtr == NULL)
> +                     break;
>               memcpy(&rtr, imsg->data, sizeof(rtr));
>               output->rtr(&rtr);
>               break;
> @@ -495,6 +535,8 @@ show(struct imsg *imsg, struct parse_res
>                       warnx("got IMSG_CTL_RESULT with wrong len");
>                       break;
>               }
> +             if (output->result == NULL)
> +                     break;
>               memcpy(&rescode, imsg->data, sizeof(rescode));
>               output->result(rescode);
>               return (1);
> Index: bgpctl.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.h,v
> retrieving revision 1.16
> diff -u -p -r1.16 bgpctl.h
> --- bgpctl.h  31 Aug 2022 15:00:53 -0000      1.16
> +++ bgpctl.h  23 Sep 2022 16:24:51 -0000
> @@ -37,7 +37,7 @@ struct output {
>       void    (*tail)(void);
>  };
>  
> -extern const struct output show_output, json_output;
> +extern const struct output show_output, json_output, ometric_output;
>  extern const size_t pt_sizes[];
>  
>  #define EOL0(flag)   ((flag & F_CTL_SSV) ? ';' : '\n')
> Index: ometric.c
> ===================================================================
> RCS file: ometric.c
> diff -N ometric.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ ometric.c 7 Oct 2022 10:26:08 -0000
> @@ -0,0 +1,422 @@
> +/*   $OpenBSD$ */
> +
> +/*
> + * Copyright (c) 2022 Claudio Jeker <clau...@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <sys/queue.h>
> +
> +#include <err.h>
> +#include <stdarg.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "ometric.h"
> +
> +struct olabel {
> +     STAILQ_ENTRY(olabel)     entry;
> +     const char              *key;
> +     char                    *value;
> +};
> +

extra blank line

> +
> +struct olabels {
> +     STAILQ_HEAD(, olabel)    labels;
> +     struct olabels          *next;
> +     int                      refcnt;
> +};
> +
> +enum ovalue_type {
> +     OVT_INTEGER,
> +     OVT_DOUBLE,
> +};
> +
> +struct ovalue {
> +     STAILQ_ENTRY(ovalue)     entry;
> +     struct olabels          *labels;
> +     union {
> +             uint64_t        i;
> +             double          f;
> +     }                        value;
> +     enum ovalue_type         valtype;
> +};
> +
> +STAILQ_HEAD(ovalues, ovalue);
> +
> +struct ometric {
> +     STAILQ_ENTRY(ometric)    entry;
> +     struct ovalues           vals;
> +     const char              *name;
> +     const char              *help;
> +     const char *const       *stateset;
> +     size_t                   setsize;
> +     enum ometric_type        type;
> +};
> +
> +STAILQ_HEAD(, ometric)       ometrics = STAILQ_HEAD_INITIALIZER(ometrics);
> +
> +/*
> + * Allocate and return new ometric. The name and help string need to remain
> + * valid until the ometric is freed. Normally constant strings should be 
> used.
> + */
> +struct ometric *
> +ometric_new(enum ometric_type type, const char *name, const char *help)
> +{
> +     struct ometric *om;
> +
> +     if ((om = malloc(sizeof(*om))) == NULL)
> +             err(1, NULL);
> +
> +     om->name = name;
> +     om->help = help;
> +     om->type = type;
> +     STAILQ_INIT(&om->vals);
> +
> +     STAILQ_INSERT_TAIL(&ometrics, om, entry);
> +
> +     return om;
> +}
> +
> +/*
> + * Same as above but for a stateset. The states is an array of constant 
> strings
> + * with statecnt elements. The states, name and help pointers need to remain
> + * valid until the ometric is freed.
> + */
> +struct ometric *
> +ometric_new_state(const char * const *states, size_t statecnt, const char 
> *name,
> +    const char *help)
> +{
> +     struct ometric *om;

Matter of taste, but I'd probably have made an ometric_new_internal()
that sets all fields, so ometric_new() and ometric_new_state() could be
thin wrappers. This would also avoid leaving stateset and setsize
uninitialized in ometric_new() (which I find a bit nasty).

Or use calloc().

> +
> +     if ((om = malloc(sizeof(*om))) == NULL)
> +             err(1, NULL);
> +
> +     om->name = name;
> +     om->help = help;
> +     om->type = OMT_STATESET;
> +     om->stateset = states;
> +     om->setsize = statecnt;
> +     STAILQ_INIT(&om->vals);
> +
> +     STAILQ_INSERT_TAIL(&ometrics, om, entry);
> +
> +     return om;
> +}
> +
> +void
> +ometric_free_all(void)
> +{
> +     struct ometric *om;
> +     struct ovalue *ov;
> +
> +     while ((om = STAILQ_FIRST(&ometrics)) != NULL) {
> +             STAILQ_REMOVE_HEAD(&ometrics, entry);
> +             while ((ov = STAILQ_FIRST(&om->vals)) != NULL) {
> +                     STAILQ_REMOVE_HEAD(&om->vals, entry);
> +                     olabels_free(ov->labels);
> +                     free(ov);
> +             }
> +             free(om);
> +     }
> +}
> +
> +static struct olabels *
> +olabels_ref(struct olabels *ol)
> +{
> +     struct olabels *x = ol;
> +
> +     while (x != NULL) {
> +             x->refcnt++;
> +             x = x->next;
> +     }
> +
> +     return ol;
> +}
> +
> +/*
> + * Create a new set of labels based on keys and values arrays.
> + * keys must end in a NULL element. values needs to hold as many elements
> + * but the elements can be NULL. values are copied for the olabel but
> + * keys needs to point to constant memory.
> + */
> +struct olabels *
> +olabels_new(const char * const *keys, const char **values)
> +{
> +     struct olabels *ol;
> +     struct olabel  *l;
> +
> +     if ((ol = malloc(sizeof(*ol))) == NULL)
> +             err(1, NULL);
> +     STAILQ_INIT(&ol->labels);
> +     ol->refcnt = 1;
> +     ol->next = NULL;
> +
> +     while (*keys != NULL) {
> +             if (*values && **values != '\0') {
> +                     if ((l = malloc(sizeof(*l))) == NULL)
> +                             err(1, NULL);
> +                     l->key = *keys;
> +                     if ((l->value = strdup(*values)) == NULL)
> +                             err(1, NULL);
> +                     STAILQ_INSERT_TAIL(&ol->labels, l, entry);
> +             }
> +
> +             keys++;
> +             values++;
> +     }
> +
> +     return ol;
> +}
> +
> +/*
> + * Free olables once nothing uses them anymore.
> + */
> +void
> +olabels_free(struct olabels *ol)
> +{
> +     struct olabels *next;
> +     struct olabel  *l;
> +
> +     for ( ; ol != NULL; ol = next) {
> +             next = ol->next;
> +
> +             if (--ol->refcnt == 0) {
> +                     while ((l = STAILQ_FIRST(&ol->labels)) != NULL) {
> +                             STAILQ_REMOVE_HEAD(&ol->labels, entry);
> +                             free(l->value);
> +                             free(l);
> +                     }
> +                     free(ol);
> +             }
> +     }
> +}
> +
> +/*
> + * Add one extra label onto the label stack. Once no longer used the
> + * value needs to be freed with olabels_free().
> + */
> +static struct olabels *
> +olabels_add_extra(struct olabels *ol, const char *key, const char *value)
> +{
> +     const char *keys[2] = { key, NULL };
> +     const char *values[2] = { value, NULL };
> +     struct olabels *new;
> +
> +     if (value == NULL || *value == '\0')
> +             return ol;
> +
> +     new = olabels_new(keys, values);
> +     new->next = olabels_ref(ol);
> +
> +     return new;
> +}
> +
> +/*
> + * Output function called last.
> + */
> +static const char *
> +ometric_type(enum ometric_type type)
> +{
> +     switch (type) {
> +     case OMT_GAUGE:
> +             return "gauge";
> +     case OMT_COUNTER:
> +             return "counter";
> +     case OMT_STATESET:
> +             return "stateset";
> +     case OMT_HISTOGRAM:
> +             return "histogram";
> +     case OMT_SUMMARY:
> +             return "summary";
> +     default:
> +             return "unknown";
> +     }
> +}
> +
> +static void
> +ometric_output_labels(const struct olabels *ol)
> +{
> +     struct olabel *l;
> +     const char *comma = "";
> +
> +     if (ol == NULL) {
> +             printf(" ");
> +             return;
> +     }
> +
> +     printf("{");
> +
> +     while (ol != NULL) {
> +             STAILQ_FOREACH(l, &ol->labels, entry) {
> +                     printf("%s%s=\"%s\"", comma, l->key, l->value);
> +                     comma = ",";
> +             }
> +             ol = ol->next;
> +     }
> +
> +     printf("} ");
> +}
> +
> +static void
> +ometric_output_value(const struct ovalue *ov)
> +{
> +     switch (ov->valtype) {
> +     case OVT_INTEGER:
> +             printf("%llu", ov->value.i);
> +             return;
> +     case OVT_DOUBLE:
> +             printf("%g", ov->value.f);
> +             return;
> +     }
> +}
> +
> +/*
> + * Output all metric values with TYPE and optional HELP strings.
> + */
> +void
> +ometric_output_all(void)
> +{
> +     struct ometric *om;
> +     struct ovalue *ov;
> +
> +     STAILQ_FOREACH(om, &ometrics, entry) {
> +             if (om->help)
> +                     printf("# HELP %s %s\n", om->name, om->help);
> +             printf("# TYPE %s %s\n", om->name, ometric_type(om->type));
> +
> +             STAILQ_FOREACH(ov, &om->vals, entry) {
> +                     printf("%s", om->name);
> +                     ometric_output_labels(ov->labels);
> +                     ometric_output_value(ov);
> +                     printf("\n");
> +             }
> +     }
> +
> +     printf("# EOF\n");
> +}
> +
> +/*
> + * Value setters
> + */
> +static void
> +ometric_set_int_value(struct ometric *om, uint64_t val, struct olabels *ol)
> +{
> +     struct ovalue *ov;
> +
> +     if ((ov = malloc(sizeof(*ov))) == NULL)
> +             err(1, NULL);
> +
> +     ov->value.i = val;
> +     ov->valtype = OVT_INTEGER;
> +     ov->labels = olabels_ref(ol);
> +
> +     STAILQ_INSERT_TAIL(&om->vals, ov, entry);
> +}
> +
> +/*
> + * Set an integer value with label ol. ol can be NULL.
> + */
> +void
> +ometric_set_int(struct ometric *om, uint64_t val, struct olabels *ol)
> +{
> +     if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
> +             errx(1, "%s incorrect ometric type", __func__);
> +
> +     ometric_set_int_value(om, val, ol);
> +}
> +
> +/*
> + * Set an floating point value with label ol. ol can be NULL.

s/an/a

> + */
> +void
> +ometric_set_float(struct ometric *om, double val, struct olabels *ol)
> +{
> +     struct ovalue *ov;
> +
> +     if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
> +             errx(1, "%s incorrect ometric type", __func__);
> +
> +     if ((ov = malloc(sizeof(*ov))) == NULL)
> +             err(1, NULL);
> +
> +     ov->value.f = val;
> +     ov->valtype = OVT_DOUBLE;
> +     ov->labels = olabels_ref(ol);
> +
> +     STAILQ_INSERT_TAIL(&om->vals, ov, entry);
> +}
> +
> +/*
> + * Add an info value (which is the value 1 but with extra key-value pairs).
> + */
> +void
> +ometric_set_info(struct ometric *om, const char **keys, const char **values,
> +    struct olabels *ol)
> +{
> +     struct olabels *extra = NULL;
> +
> +     if (om->type != OMT_INFO)
> +             errx(1, "%s incorrect ometric type", __func__);
> +
> +     if (keys != NULL) {
> +             extra = olabels_new(keys, values);
> +             extra->next = olabels_ref(ol);
> +     }
> +
> +     ometric_set_int_value(om, 1, extra != NULL ? extra : ol);
> +     olabels_free(extra);
> +}
> +
> +/*
> + * Set a stateset to one of its states.
> + */
> +void
> +ometric_set_state(struct ometric *om, const char *state, struct olabels *ol)
> +{
> +     struct olabels *extra;
> +     size_t i;
> +     int val;
> +
> +     if (om->type != OMT_STATESET)
> +             errx(1, "%s incorrect ometric type", __func__);
> +
> +     for (i = 0; i < om->setsize; i++) {     
> +             if (strcasecmp(state, om->stateset[i]) == 0)
> +                     val = 1;
> +             else
> +                     val = 0;

could simplify this to

                val = strcasecmp(state, om->stateset[i]) == 0;

but I'm not sure if this is more readable

> +
> +             extra = olabels_add_extra(ol, om->name, om->stateset[i]);
> +             ometric_set_int_value(om, val, extra);
> +             olabels_free(extra);
> +     }
> +}
> +
> +/*
> + * Set a value with an extra label, the key should be a constant string while
> + * the value is copied into the extra label.
> + */
> +void
> +ometric_set_int_with_label(struct ometric *om, uint64_t val, const char *key,
> +    const char *value, struct olabels *ol)
> +{
> +     struct olabels *extra;
> +
> +     extra = olabels_add_extra(ol, key, value);
> +     ometric_set_int(om, val, extra);
> +     olabels_free(extra);
> +}
> Index: ometric.h
> ===================================================================
> RCS file: ometric.h
> diff -N ometric.h
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ ometric.h 7 Oct 2022 10:25:30 -0000
> @@ -0,0 +1,49 @@
> +/*   $OpenBSD$ */
> +
> +/*
> + * Copyright (c) 2022 Claudio Jeker <clau...@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +enum ometric_type {
> +     OMT_UNKNOWN,
> +     OMT_GAUGE,
> +     OMT_COUNTER,
> +     OMT_STATESET,
> +     OMT_HISTOGRAM,
> +     OMT_SUMMARY,
> +     OMT_INFO,
> +};
> +
> +struct ometric;
> +struct olabels;
> +
> +struct ometric       *ometric_new(enum ometric_type, const char *, const 
> char *);
> +struct ometric       *ometric_new_state(const char * const *, size_t, const 
> char *,
> +                 const char *);
> +void          ometric_free_all(void);
> +struct olabels       *olabels_new(const char * const *, const char **);
> +void          olabels_free(struct olabels *);
> +
> +void          ometric_output_all(void);
> +
> +/* XXX how to pass attributes */
> +/* functions to set gauge and counter metrics */
> +void ometric_set_int(struct ometric *, uint64_t, struct olabels *);
> +void ometric_set_float(struct ometric *, double, struct olabels *);
> +void ometric_set_info(struct ometric *, const char **, const char **,
> +         struct olabels *); 
> +void ometric_set_state(struct ometric *, const char *, struct olabels *); 
> +void ometric_set_int_with_label(struct ometric *, uint64_t, const char *,
> +         const char *, struct olabels *);
> Index: output.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/output.c,v
> retrieving revision 1.29
> diff -u -p -r1.29 output.c
> --- output.c  31 Aug 2022 15:00:53 -0000      1.29
> +++ output.c  23 Sep 2022 16:32:40 -0000
> @@ -1120,5 +1120,5 @@ const struct output show_output = {
>       .set = show_rib_set,
>       .rtr = show_rtr,
>       .result = show_result,
> -     .tail = show_tail
> +     .tail = show_tail,
>  };
> Index: output_json.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/output_json.c,v
> retrieving revision 1.23
> diff -u -p -r1.23 output_json.c
> --- output_json.c     31 Aug 2022 15:00:53 -0000      1.23
> +++ output_json.c     23 Sep 2022 16:32:44 -0000
> @@ -1057,5 +1057,5 @@ const struct output json_output = {
>       .set = json_rib_set,
>       .rtr = json_rtr,
>       .result = json_result,
> -     .tail = json_tail
> +     .tail = json_tail,
>  };
> Index: output_ometric.c
> ===================================================================
> RCS file: output_ometric.c
> diff -N output_ometric.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ output_ometric.c  7 Oct 2022 10:26:44 -0000
> @@ -0,0 +1,334 @@
> +/*   $OpenBSD: output_json.c,v 1.23 2022/08/31 15:00:53 claudio Exp $ */
> +
> +/*
> + * Copyright (c) 2022 Claudio Jeker <clau...@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <err.h>
> +#include <limits.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include "bgpd.h"
> +#include "session.h"
> +#include "rde.h"
> +#include "version.h"
> +
> +#include "bgpctl.h"
> +#include "parser.h"
> +#include "ometric.h"
> +
> +struct ometric *bgpd_info, *bgpd_scrape_time;
> +struct ometric *peer_info, *peer_state, *peer_up_time, *peer_down_time,
> +                 *peer_last_read, *peer_last_write;
> +struct ometric *peer_prefixes_tranmit, *peer_prefixes_receive;

typo: peer_prefixes_transmit

> +struct ometric *peer_message_transmit, *peer_message_recieve;
> +struct ometric *peer_update_transmit, *peer_update_pending,
> +                *peer_update_receive;
> +struct ometric *peer_withdraw_transmit, *peer_withdraw_pending,
> +                *peer_withdraw_receive;
> +struct ometric *peer_rr_req_transmit, *peer_rr_req_receive;
> +struct ometric *peer_rr_borr_transmit, *peer_rr_borr_receive;
> +struct ometric *peer_rr_eorr_transmit, *peer_rr_eorr_receive;
> +struct ometric *rde_mem_size, *rde_mem_count, *rde_mem_ref_count;
> +struct ometric *rde_set_size, *rde_set_count, *rde_table_count;
> +
> +struct timeval start_time, end_time;
> +
> +static void
> +ometric_head(struct parse_result *arg)
> +{
> +     struct olabels *ol = NULL;
> +     const char *keys[4] = { "nodename", "domainname", "release", NULL };
> +     const char *values[4];
> +     char hostname[HOST_NAME_MAX + 1];
> +     char *domainname;
> +
> +     bgpd_info = ometric_new(OMT_INFO, "bgpd_info", "bgpd information");
> +     bgpd_scrape_time = ometric_new(OMT_GAUGE, "bgpd_scrape_seconds",
> +         "bgpd scrape time in seconds");
> +
> +     gettimeofday(&start_time, NULL);
> +
> +     if (gethostname(hostname, sizeof(hostname)))
> +             err(1, "gethostname");
> +     if ((domainname = strchr(hostname, '.')))
> +             *domainname++ = '\0';
> +
> +     values[0] = hostname;
> +     values[1] = domainname;
> +     values[2] = BGPD_VERSION;
> +     values[3] = NULL;
> +
> +     ol = olabels_new(keys, values);
> +
> +     ometric_set_info(bgpd_info, NULL, NULL, ol);
> +
> +     olabels_free(ol);
> +
> +     /* neighbor stats, attrs are remote_as, remote_addr, description,
> +        group */

KNF for comment

> +     peer_info = ometric_new(OMT_INFO, "bgpd_peer_info",
> +         "peer information");
> +     peer_state = ometric_new_state(statenames,
> +         sizeof(statenames) / sizeof(statenames[0]), "bgpd_peer_state",
> +         "peer session state");
> +     peer_up_time = ometric_new(OMT_GAUGE, "bgpd_peer_up_seconds",
> +         "peer session up time in seconds");
> +     peer_down_time = ometric_new(OMT_GAUGE, "bgpd_peer_down_seconds",
> +         "peer session down time in seconds");
> +     peer_last_read = ometric_new(OMT_GAUGE, "bgpd_peer_last_read_seconds",
> +         "peer time since last read in seconds");
> +     peer_last_write = ometric_new(OMT_GAUGE, "bgpd_peer_last_write_seconds",
> +         "peer time since last write in seconds");
> +
> +     peer_prefixes_tranmit = ometric_new(OMT_GAUGE,
> +         "bgpd_peer_prefixes_transmit",
> +         "number of prefixes sent to peer");
> +     peer_prefixes_receive = ometric_new(OMT_GAUGE,
> +         "bgpd_peer_prefixes_receive",
> +         "number of prefixes received from peer");
> +
> +     peer_message_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_message_transmit_total",
> +         "per message type count of tranmitted messages");
> +     peer_message_recieve = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_message_receive_total",
> +         "per message type count of received messages");
> +
> +     peer_update_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_update_transmit_total",
> +         "number of prefixes sent as update");
> +     peer_update_pending = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_update_pending_total",
> +         "number of pending update prefixes");
> +     peer_update_receive = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_update_receive_total",
> +         "number of prefixes received as update");
> +
> +     peer_withdraw_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_withdraw_transmit_total",
> +         "number of witdrawn prefixes sent to peer");
> +     peer_withdraw_pending = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_withdraw_pending_total",
> +         "number of pending withdrawn prefixes");
> +     peer_withdraw_receive = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_withdraw_receive_total",
> +         "number of withdrawn prefixes received from peer");
> +
> +     peer_rr_req_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_req_transmit_total",
> +         "number of route-refresh request transmitted to peer");
> +     peer_rr_req_receive = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_req_receive_total",
> +         "number of route-refresh request received from peer");
> +     peer_rr_borr_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_borr_transmit_total",
> +         "number of ext. route-refresh BORR messages transmitted to peer");
> +     peer_rr_borr_receive = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_borr_receive_total",
> +         "number of ext. route-refresh BORR messages received from peer");
> +     peer_rr_eorr_transmit = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_eorr_transmit_total",
> +         "number of ext. route-refresh EORR messages transmitted to peer");
> +     peer_rr_eorr_receive = ometric_new(OMT_COUNTER,
> +         "bgpd_peer_route_refresh_eorr_receive_total",
> +         "number of ext. route-refresh EORR messages received from peer");
> +
> +     rde_mem_size = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_memory_usage_bytes", "memory usage in bytes");
> +     rde_mem_count = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_memory_count", "number of object in use");
> +     rde_mem_ref_count = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_memory_reference_count", "number of references held");
> +
> +     rde_set_size = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_set_usage_bytes", "memory usage of set in bytes");
> +     rde_set_count = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_set_count", "number of object in set");
> +     rde_table_count = ometric_new(OMT_GAUGE,
> +         "bgpd_rde_table_count", "number of as_set tables");
> +}
> +
> +static void
> +ometric_neighbor_stats(struct peer *p, struct parse_result *arg)
> +{
> +     struct olabels *ol = NULL;
> +     const char *keys[5] = {
> +         "remote_addr", "remote_as", "description", "group", NULL };
> +     const char *values[5];
> +
> +     /* skip neighbor templates */
> +     if (p->conf.template)
> +             return;
> +
> +     values[0] = log_addr(&p->conf.remote_addr);
> +     values[1] = log_as(p->conf.remote_as);
> +     values[2] = p->conf.descr;
> +     values[3] = p->conf.group;
> +     values[4] = NULL;
> +
> +     ol = olabels_new(keys, values);
> +
> +     ometric_set_info(peer_info, NULL, NULL, ol);
> +     ometric_set_state(peer_state, statenames[p->state], ol);
> +
> +     if (p->state == STATE_ESTABLISHED) {
> +             ometric_set_int(peer_up_time,
> +                 get_monotime(p->stats.last_updown), ol);
> +             ometric_set_int(peer_last_read,
> +                 get_monotime(p->stats.last_read), ol);
> +             ometric_set_int(peer_last_write,
> +                 get_monotime(p->stats.last_write), ol);
> +     } else if (p->stats.last_updown != 0)
> +             ometric_set_int(peer_down_time,
> +                 get_monotime(p->stats.last_updown), ol);
> +

extra blank line

> +
> +     ometric_set_int(peer_prefixes_tranmit, p->stats.prefix_out_cnt, ol);
> +     ometric_set_int(peer_prefixes_receive, p->stats.prefix_cnt, ol);
> +
> +     ometric_set_int_with_label(peer_message_transmit,
> +         p->stats.msg_sent_open, "message", "open", ol);
> +     ometric_set_int_with_label(peer_message_transmit,
> +         p->stats.msg_sent_notification, "message", "notification", ol);
> +     ometric_set_int_with_label(peer_message_transmit,
> +         p->stats.msg_sent_update, "message", "update", ol);
> +     ometric_set_int_with_label(peer_message_transmit,
> +         p->stats.msg_sent_keepalive, "message", "keepalive", ol);
> +     ometric_set_int_with_label(peer_message_transmit,
> +         p->stats.msg_sent_rrefresh, "message", "route_refresh", ol);
> +
> +     ometric_set_int_with_label(peer_message_recieve,
> +         p->stats.msg_rcvd_open, "message", "open", ol);
> +     ometric_set_int_with_label(peer_message_recieve,
> +         p->stats.msg_rcvd_notification, "message", "notification", ol);
> +     ometric_set_int_with_label(peer_message_recieve,
> +         p->stats.msg_rcvd_update, "message", "update", ol);
> +     ometric_set_int_with_label(peer_message_recieve,
> +         p->stats.msg_rcvd_keepalive, "message", "keepalive", ol);
> +     ometric_set_int_with_label(peer_message_recieve,
> +         p->stats.msg_rcvd_rrefresh, "message", "route_refresh", ol);
> +
> +     ometric_set_int(peer_update_transmit, p->stats.prefix_sent_update, ol);
> +     ometric_set_int(peer_update_pending, p->stats.pending_update, ol);
> +     ometric_set_int(peer_update_receive, p->stats.prefix_rcvd_update, ol);
> +     ometric_set_int(peer_withdraw_transmit, p->stats.prefix_sent_withdraw,
> +         ol);
> +     ometric_set_int(peer_withdraw_pending, p->stats.pending_withdraw, ol);
> +     ometric_set_int(peer_withdraw_receive, p->stats.prefix_rcvd_withdraw,
> +         ol);
> +
> +     ometric_set_int(peer_rr_req_transmit, p->stats.refresh_sent_req, ol);
> +     ometric_set_int(peer_rr_req_receive, p->stats.refresh_rcvd_req, ol);
> +     ometric_set_int(peer_rr_borr_transmit, p->stats.refresh_sent_borr, ol);
> +     ometric_set_int(peer_rr_borr_receive, p->stats.refresh_rcvd_borr, ol);
> +     ometric_set_int(peer_rr_eorr_transmit, p->stats.refresh_sent_eorr, ol);
> +     ometric_set_int(peer_rr_eorr_receive, p->stats.refresh_rcvd_eorr, ol);
> +
> +     olabels_free(ol);
> +}
> +
> +static void
> +ometric_rib_mem_element(const char *v, uint64_t count, uint64_t size,
> +    uint64_t refs)
> +{
> +     if (count != UINT64_MAX)
> +             ometric_set_int_with_label(rde_mem_count, count, "type", v,
> +                 NULL);
> +     if (size != UINT64_MAX)
> +             ometric_set_int_with_label(rde_mem_size, size, "type", v, NULL);
> +     if (refs != UINT64_MAX)
> +             ometric_set_int_with_label(rde_mem_ref_count, refs, "type", v,
> +                 NULL);
> +}
> +
> +static void
> +ometric_rib_mem(struct rde_memstats *stats)
> +{
> +     size_t pts = 0;
> +     int i;
> +
> +     for (i = 0; i < AID_MAX; i++) {
> +             if (stats->pt_cnt[i] == 0)
> +                     continue;
> +             pts += stats->pt_cnt[i] * pt_sizes[i];
> +             ometric_rib_mem_element(aid_vals[i].name, stats->pt_cnt[i],
> +                 stats->pt_cnt[i] * pt_sizes[i], UINT64_MAX);
> +     }
> +     ometric_rib_mem_element("rib", stats->rib_cnt,
> +         stats->rib_cnt * sizeof(struct rib_entry), UINT64_MAX);
> +     ometric_rib_mem_element("prefix", stats->prefix_cnt,
> +         stats->prefix_cnt * sizeof(struct prefix), UINT64_MAX);
> +     ometric_rib_mem_element("rde_aspath", stats->path_cnt,
> +         stats->path_cnt * sizeof(struct rde_aspath),
> +         stats->path_refs);
> +     ometric_rib_mem_element("aspath", stats->aspath_cnt,
> +         stats->aspath_size, UINT64_MAX);
> +     ometric_rib_mem_element("community_entries", stats->comm_cnt,
> +         stats->comm_cnt * sizeof(struct rde_community), UINT64_MAX);
> +     ometric_rib_mem_element("community", stats->comm_nmemb,
> +         stats->comm_size * sizeof(struct community), stats->comm_refs);
> +     ometric_rib_mem_element("attributes_entries", stats->attr_cnt,
> +         stats->attr_cnt * sizeof(struct attr), stats->attr_refs);
> +     ometric_rib_mem_element("attributes", stats->attr_dcnt,
> +         stats->attr_data, UINT64_MAX);
> +
> +     ometric_rib_mem_element("total", UINT64_MAX, 
> +         pts + stats->prefix_cnt * sizeof(struct prefix) +
> +         stats->rib_cnt * sizeof(struct rib_entry) +
> +         stats->path_cnt * sizeof(struct rde_aspath) +
> +         stats->aspath_size + stats->attr_cnt * sizeof(struct attr) +
> +         stats->attr_data, UINT64_MAX);
> +
> +     ometric_set_int(rde_table_count, stats->aset_cnt, NULL);
> +     ometric_set_int_with_label(rde_set_size, stats->aset_size,
> +        "type", "as_set", NULL);
> +     ometric_set_int_with_label(rde_set_count, stats->aset_nmemb,
> +        "type", "as_set", NULL);
> +     ometric_set_int_with_label(rde_set_size, stats->pset_size,
> +        "type", "prefix_set", NULL);
> +     ometric_set_int_with_label(rde_set_count, stats->pset_cnt,
> +        "type", "prefix_set", NULL);
> +     ometric_rib_mem_element("set_total", UINT64_MAX, 
> +         stats->aset_size + stats->pset_size, UINT64_MAX);
> +}
> +
> +static void
> +ometric_tail(void)
> +{
> +     struct timeval elapsed_time;
> +     double scrape;
> +
> +     gettimeofday(&end_time, NULL);
> +     timersub(&end_time, &start_time, &elapsed_time);
> +
> +     scrape = (double)elapsed_time.tv_sec +
> +         (double)elapsed_time.tv_usec / 1000000;
> +
> +     ometric_set_float(bgpd_scrape_time, scrape, NULL);
> +     ometric_output_all();
> +
> +     ometric_free_all();
> +}
> +
> +const struct output ometric_output = {
> +     .head = ometric_head,
> +     .neighbor = ometric_neighbor_stats,
> +     .rib_mem = ometric_rib_mem,
> +     .tail = ometric_tail,
> +};
> Index: parser.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/parser.c,v
> retrieving revision 1.114
> diff -u -p -r1.114 parser.c
> --- parser.c  17 Aug 2022 15:16:12 -0000      1.114
> +++ parser.c  23 Sep 2022 16:43:03 -0000
> @@ -141,6 +141,7 @@ static const struct token t_show[] = {
>       { KEYWORD,      "sets",         SHOW_SET,       NULL},
>       { KEYWORD,      "rtr",          SHOW_RTR,       NULL},
>       { KEYWORD,      "mrt",          SHOW_MRT,       t_show_mrt},
> +     { KEYWORD,      "metric",       SHOW_METRIC,    NULL},
>       { ENDTOKEN,     "",             NONE,           NULL}
>  };
>  
> Index: parser.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/parser.h,v
> retrieving revision 1.42
> diff -u -p -r1.42 parser.h
> --- parser.h  6 Feb 2022 09:52:32 -0000       1.42
> +++ parser.h  23 Sep 2022 16:23:08 -0000
> @@ -37,6 +37,7 @@ enum actions {
>       SHOW_RIB_MEM,
>       SHOW_NEXTHOP,
>       SHOW_INTERFACE,
> +     SHOW_METRIC,
>       RELOAD,
>       FIB,
>       FIB_COUPLE,
> 


Reply via email to