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 <[email protected]>
> + *
> + * 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 <[email protected]>
> + *
> + * 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 <[email protected]>
> + *
> + * 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,
>