/*	$OpenBSD: output_ometric.c,v 1.22 2025/12/29 07:49:05 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 *rde_mem_size, *rde_mem_count, *rde_mem_ref_count;
struct ometric *rde_set_size, *rde_set_count, *rde_table_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");

	/* 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_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);

	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("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, 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_count, stats->pset_cnt,
	    OKV("type"), OKV("prefix_set"), NULL);
	ometric_rib_mem_element("set_total", UINT64_MAX,
	    stats->aset_size + stats->pset_size, UINT64_MAX);

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