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, >