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. -- :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; +}; + + +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; + + 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. + */ +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; + + 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; +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 */ + 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); + + + 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,