root/usr.sbin/bgpctl/output_ometric.c
/*      $OpenBSD: output_ometric.c,v 1.27 2026/03/17 09:30:11 claudio Exp $ */

/*
 * Copyright (c) 2022 Claudio Jeker <claudio@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/time.h>

#include <err.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.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_state_raw, *peer_last_change,
                    *peer_last_read, *peer_last_write;
struct ometric *peer_prefixes_transmit, *peer_prefixes_receive;
struct ometric *peer_message_transmit, *peer_message_receive;
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 *peer_queue_count, *peer_queue_size;
struct ometric *rde_mem_size, *rde_mem_count, *rde_mem_ref_count;
struct ometric *rde_set_size, *rde_set_count, *rde_table_count;
struct ometric *rde_queue_size, *rde_queue_count;
struct ometric *rde_evloop_count, *rde_evloop_time;

struct timespec 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;

        clock_gettime(CLOCK_MONOTONIC, &start_time);

        bgpd_info = ometric_new(OMT_INFO, "bgpd", "bgpd information");
        bgpd_scrape_time = ometric_new(OMT_GAUGE, "bgpd_scrape_seconds",
            "bgpd scrape time in seconds");

        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);

        /*
         * per neighbor stats: attrs will be remote_as, remote_addr,
         * description and group
         */
        peer_info = ometric_new(OMT_INFO, "bgpd_peer",
            "peer information");
        peer_state = ometric_new_state(statenames, nitems(statenames),
            "bgpd_peer_state", "peer session state");
        peer_state_raw = ometric_new(OMT_GAUGE, "bgpd_peer_state_raw",
            "peer session state raw int value");
        peer_last_change = ometric_new(OMT_GAUGE,
            "bgpd_peer_last_change_seconds",
            "time in seconds since peer's last up/down state change");
        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_transmit = 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",
            "per message type count of transmitted messages");
        peer_message_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_message_receive",
            "per message type count of received messages");

        peer_update_transmit = ometric_new(OMT_COUNTER,
            "bgpd_peer_update_transmit",
            "number of prefixes sent as update");
        peer_update_pending = ometric_new(OMT_COUNTER,
            "bgpd_peer_update_pending",
            "number of pending update prefixes");
        peer_update_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_update_receive",
            "number of prefixes received as update");

        peer_withdraw_transmit = ometric_new(OMT_COUNTER,
            "bgpd_peer_withdraw_transmit",
            "number of withdrawn prefixes sent to peer");
        peer_withdraw_pending = ometric_new(OMT_COUNTER,
            "bgpd_peer_withdraw_pending",
            "number of pending withdrawn prefixes");
        peer_withdraw_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_withdraw_receive",
            "number of withdrawn prefixes received from peer");

        peer_rr_req_transmit = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_req_transmit",
            "number of route-refresh request transmitted to peer");
        peer_rr_req_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_req_receive",
            "number of route-refresh request received from peer");
        peer_rr_borr_transmit = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_borr_transmit",
            "number of ext. route-refresh BORR messages transmitted to peer");
        peer_rr_borr_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_borr_receive",
            "number of ext. route-refresh BORR messages received from peer");
        peer_rr_eorr_transmit = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_eorr_transmit",
            "number of ext. route-refresh EORR messages transmitted to peer");
        peer_rr_eorr_receive = ometric_new(OMT_COUNTER,
            "bgpd_peer_route_refresh_eorr_receive",
            "number of ext. route-refresh EORR messages received from peer");
        peer_queue_count = ometric_new(OMT_GAUGE,
            "bgpd_peer_queue_usage_objects", "number of object on queue");
        peer_queue_size = ometric_new(OMT_GAUGE,
            "bgpd_peer_queue_memory_usage_bytes",
            "memory usage of queue in bytes");

        /* RDE memory statistics */
        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_usage_objects", "number of object in use");
        rde_mem_ref_count = ometric_new(OMT_GAUGE,
            "bgpd_rde_memory_usage_references", "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_usage_objects", "number of object in set");
        rde_table_count = ometric_new(OMT_GAUGE,
            "bgpd_rde_set_usage_tables", "number of as_set tables");

        rde_queue_size = ometric_new(OMT_GAUGE,
            "bgpd_rde_queue_bytes", "memory usage of queued objects in bytes");
        rde_queue_count = ometric_new(OMT_GAUGE,
            "bgpd_rde_queue_objects", "number of object in queue");

        rde_evloop_count = ometric_new(OMT_COUNTER,
            "bgpd_rde_evloop", "number of times the evloop ran");
        rde_evloop_time = ometric_new(OMT_COUNTER,
            "bgpd_rde_evloop_seconds", "RDE evloop time usage");
}

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];
        char *descr;

        /* skip neighbor templates */
        if (p->conf.template)
                return;

        descr = fmt_peer(p->conf.descr, &p->conf.remote_addr,
            p->conf.remote_masklen);

        values[0] = log_addr(&p->conf.remote_addr);
        values[1] = log_as(p->conf.remote_as);
        values[2] = 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);
        ometric_set_int(peer_state_raw, p->state, ol);

        ometric_set_int(peer_last_change,
            get_rel_monotime(p->stats.last_updown), ol);

        if (p->state == STATE_ESTABLISHED) {
                ometric_set_int(peer_last_read,
                    get_rel_monotime(p->stats.last_read), ol);
                ometric_set_int(peer_last_write,
                    get_rel_monotime(p->stats.last_write), ol);
        }

        ometric_set_int(peer_prefixes_transmit, p->stats.prefix_out_cnt, ol);
        ometric_set_int(peer_prefixes_receive, p->stats.prefix_cnt, ol);

        ometric_set_int_with_labels(peer_message_transmit,
            p->stats.msg_sent_open, OKV("messages"), OKV("open"), ol);
        ometric_set_int_with_labels(peer_message_transmit,
            p->stats.msg_sent_notification, OKV("messages"),
            OKV("notification"), ol);
        ometric_set_int_with_labels(peer_message_transmit,
            p->stats.msg_sent_update, OKV("messages"), OKV("update"), ol);
        ometric_set_int_with_labels(peer_message_transmit,
            p->stats.msg_sent_keepalive, OKV("messages"), OKV("keepalive"), ol);
        ometric_set_int_with_labels(peer_message_transmit,
            p->stats.msg_sent_rrefresh, OKV("messages"), OKV("route_refresh"),
            ol);

        ometric_set_int_with_labels(peer_message_receive,
            p->stats.msg_rcvd_open, OKV("messages"), OKV("open"), ol);
        ometric_set_int_with_labels(peer_message_receive,
            p->stats.msg_rcvd_notification, OKV("messages"),
            OKV("notification"), ol);
        ometric_set_int_with_labels(peer_message_receive,
            p->stats.msg_rcvd_update, OKV("messages"), OKV("update"), ol);
        ometric_set_int_with_labels(peer_message_receive,
            p->stats.msg_rcvd_keepalive, OKV("messages"), OKV("keepalive"), ol);
        ometric_set_int_with_labels(peer_message_receive,
            p->stats.msg_rcvd_rrefresh, OKV("messages"), OKV("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);

        ometric_set_int_with_labels(peer_queue_count, p->stats.ibufq_msg_count,
            OKV("type"), OKV("ibuf_queue"), ol);
        ometric_set_int_with_labels(peer_queue_count, p->stats.rib_entry_count,
            OKV("type"), OKV("rib_entry"), ol);
        ometric_set_int_with_labels(peer_queue_size,
            p->stats.ibufq_payload_size, OKV("type"), OKV("ibuf_queue"), ol);

        olabels_free(ol);
        free(descr);
}

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_labels(rde_mem_count, count,
                    OKV("type"), OKV(v), NULL);
        if (size != UINT64_MAX)
                ometric_set_int_with_labels(rde_mem_size, size,
                    OKV("type"), OKV(v), NULL);
        if (refs != UINT64_MAX)
                ometric_set_int_with_labels(rde_mem_ref_count, refs,
                    OKV("type"), OKV(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_size[i];
                ometric_rib_mem_element(aid_vals[i].name, stats->pt_cnt[i],
                    stats->pt_size[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("adjout_prefix", stats->adjout_prefix_cnt,
            stats->adjout_prefix_size, UINT64_MAX);
        ometric_rib_mem_element("pend_attr", stats->pend_attr_cnt,
            stats->pend_attr_cnt * sizeof(struct pend_attr), UINT64_MAX);
        ometric_rib_mem_element("pend_prefix", stats->pend_prefix_cnt,
            stats->pend_prefix_cnt * sizeof(struct pend_prefix), UINT64_MAX);
        ometric_rib_mem_element("adjout_attr", stats->adjout_attr_cnt,
            stats->adjout_attr_cnt * sizeof(struct adjout_attr),
            stats->adjout_attr_refs);
        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("bitmap", stats->bitmap_cnt,
            stats->bitmap_size, UINT64_MAX);
        ometric_rib_mem_element("hashtable", stats->hash_cnt,
            stats->hash_size, stats->hash_refs);

        ometric_rib_mem_element("total", UINT64_MAX,
            pts + stats->prefix_cnt * sizeof(struct prefix) +
            stats->adjout_prefix_cnt * sizeof(struct adjout_prefix) +
            stats->adjout_attr_cnt * sizeof(struct adjout_attr) +
            stats->pend_prefix_cnt * sizeof(struct pend_prefix) +
            stats->pend_attr_cnt * sizeof(struct pend_attr) +
            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 + stats->bitmap_size + stats->hash_size,
            UINT64_MAX);

        ometric_rib_mem_element("filter", stats->filter_cnt,
            stats->filter_size, stats->filter_refs);
        ometric_rib_mem_element("filter_set", stats->filter_set_cnt,
            stats->filter_set_size, stats->filter_set_refs);
        ometric_rib_mem_element("filter_total", UINT64_MAX,
            stats->filter_size + stats->filter_set_size, UINT64_MAX);

        ometric_set_int(rde_table_count, stats->aset_cnt, NULL);

        ometric_set_int_with_labels(rde_set_size, stats->aset_size,
            OKV("type"), OKV("as_set"), NULL);
        ometric_set_int_with_labels(rde_set_count, stats->aset_nmemb,
            OKV("type"), OKV("as_set"), NULL);
        ometric_set_int_with_labels(rde_set_size, stats->pset_size,
            OKV("type"), OKV("prefix_set"), NULL);
        ometric_set_int_with_labels(rde_set_size, stats->aspa_size,
            OKV("type"), OKV("aspa_set"), NULL);
        ometric_set_int_with_labels(rde_set_count, stats->pset_cnt,
            OKV("type"), OKV("prefix_set"), NULL);
        ometric_set_int_with_labels(rde_set_count, stats->aspa_cnt,
            OKV("type"), OKV("aspa_set"), NULL);
        ometric_rib_mem_element("set_total", UINT64_MAX,
            stats->aset_size + stats->pset_size + stats->aspa_size, UINT64_MAX);

        ometric_set_int_with_labels(rde_queue_count, stats->rde_ibufq_msg_count,
            OKV("type"), OKV("ibuf_queue"), NULL);
        ometric_set_int_with_labels(rde_queue_count, stats->rde_rib_entry_count,
            OKV("type"), OKV("rib_entry"), NULL);
        ometric_set_int_with_labels(rde_queue_size,
            stats->rde_ibufq_payload_size, OKV("type"), OKV("ibuf_queue"),
            NULL);

        ometric_set_int(rde_evloop_count, stats->rde_event_loop_count, NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_loop_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("main"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_io_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("io"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_peer_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("peer"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_adjout_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("adjout"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_ribdump_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("ribdumps"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_nexthop_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("nexthop"), NULL);
        ometric_set_float_with_labels(rde_evloop_time,
            (double)stats->rde_event_update_usec / (1000.0 * 1000.0) ,
            OKV("stage"), OKV("update"), NULL);
}

static void
ometric_tail(void)
{
        struct timespec elapsed_time;

        clock_gettime(CLOCK_MONOTONIC, &end_time);
        timespecsub(&end_time, &start_time, &elapsed_time);

        ometric_set_timespec(bgpd_scrape_time, &elapsed_time, NULL);
        ometric_output_all(stdout);

        ometric_free_all();
}

const struct output ometric_output = {
        .head = ometric_head,
        .neighbor = ometric_neighbor_stats,
        .rib_mem = ometric_rib_mem,
        .tail = ometric_tail,
};