Home | History | Annotate | Line # | Download | only in dist
      1 /*
      2  * metrics.c -- prometheus metrics endpoint
      3  *
      4  * Copyright (c) 2001-2025, NLnet Labs. All rights reserved.
      5  *
      6  * See LICENSE for the license.
      7  *
      8  */
      9 
     10 #include "config.h"
     11 
     12 #ifdef USE_METRICS
     13 
     14 #include <unistd.h>
     15 #include <assert.h>
     16 #include <fcntl.h>
     17 #include <sys/stat.h>
     18 #include <errno.h>
     19 #include <event2/event.h>
     20 #include <event2/http.h>
     21 
     22 #include "nsd.h"
     23 #include "xfrd.h"
     24 #include "options.h"
     25 #include "remote.h"
     26 #include "metrics.h"
     27 
     28 /** if you want zero to be inhibited in stats output.
     29  * it omits zeroes for types that have no acronym and unused-rcodes */
     30 const int metrics_inhibit_zero = 1;
     31 
     32 /**
     33  * list of connection accepting file descriptors
     34  */
     35 struct metrics_acceptlist {
     36 	struct metrics_acceptlist* next;
     37 	int accept_fd;
     38 	char* ident;
     39 	struct daemon_metrics* metrics;
     40 };
     41 
     42 /**
     43  * The metrics daemon state.
     44  */
     45 struct daemon_metrics {
     46 	/** the master process for this metrics daemon */
     47 	struct xfrd_state* xfrd;
     48 	/** commpoints for accepting HTTP connections */
     49 	struct metrics_acceptlist* accept_list;
     50 	/** last time stats was reported */
     51 	struct timeval stats_time, boot_time;
     52 	/** libevent http server */
     53 	struct evhttp *http_server;
     54 };
     55 
     56 static void
     57 metrics_http_callback(struct evhttp_request *req, void *p);
     58 
     59 struct daemon_metrics*
     60 daemon_metrics_create(struct nsd_options* cfg)
     61 {
     62 	struct daemon_metrics* metrics = (struct daemon_metrics*)xalloc_zero(
     63 		sizeof(*metrics));
     64 	assert(cfg->metrics_enable);
     65 
     66 	/* and try to open the ports */
     67 	if(!daemon_metrics_open_ports(metrics, cfg)) {
     68 		log_msg(LOG_ERR, "could not open metrics port");
     69 		daemon_metrics_delete(metrics);
     70 		return NULL;
     71 	}
     72 
     73 	if(gettimeofday(&metrics->boot_time, NULL) == -1)
     74 		log_msg(LOG_ERR, "gettimeofday: %s", strerror(errno));
     75 	metrics->stats_time = metrics->boot_time;
     76 
     77 	return metrics;
     78 }
     79 
     80 void daemon_metrics_close(struct daemon_metrics* metrics)
     81 {
     82 	struct metrics_acceptlist *h, *nh;
     83 	if(!metrics) return;
     84 
     85 	/* close listen sockets */
     86 	h = metrics->accept_list;
     87 	while(h) {
     88 		nh = h->next;
     89 		close(h->accept_fd);
     90 		free(h->ident);
     91 		free(h);
     92 		h = nh;
     93 	}
     94 	metrics->accept_list = NULL;
     95 
     96 	if (metrics->http_server) {
     97 		evhttp_free(metrics->http_server);
     98 	}
     99 }
    100 
    101 void daemon_metrics_delete(struct daemon_metrics* metrics)
    102 {
    103 	if(!metrics) return;
    104 	daemon_metrics_close(metrics);
    105 	free(metrics);
    106 }
    107 
    108 static int
    109 create_tcp_accept_sock(struct addrinfo* addr, int* noproto)
    110 {
    111 #if defined(SO_REUSEADDR) || (defined(INET6) && (defined(IPV6_V6ONLY) || defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU)))
    112 	int on = 1;
    113 #endif
    114 	int s;
    115 	*noproto = 0;
    116 	if ((s = socket(addr->ai_family, addr->ai_socktype, 0)) == -1) {
    117 #if defined(INET6)
    118 		if (addr->ai_family == AF_INET6 &&
    119 			errno == EAFNOSUPPORT) {
    120 			*noproto = 1;
    121 			log_msg(LOG_WARNING, "fallback to TCP4, no IPv6: not supported");
    122 			return -1;
    123 		}
    124 #endif /* INET6 */
    125 		log_msg(LOG_ERR, "can't create a socket: %s", strerror(errno));
    126 		return -1;
    127 	}
    128 #ifdef  SO_REUSEADDR
    129 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
    130 		log_msg(LOG_ERR, "setsockopt(..., SO_REUSEADDR, ...) failed: %s", strerror(errno));
    131 	}
    132 #endif /* SO_REUSEADDR */
    133 #if defined(INET6) && defined(IPV6_V6ONLY)
    134 	if (addr->ai_family == AF_INET6 &&
    135 		setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
    136 	{
    137 		log_msg(LOG_ERR, "setsockopt(..., IPV6_V6ONLY, ...) failed: %s", strerror(errno));
    138 		close(s);
    139 		return -1;
    140 	}
    141 #endif
    142 	/* set it nonblocking */
    143 	/* (StevensUNP p463), if tcp listening socket is blocking, then
    144 	   it may block in accept, even if select() says readable. */
    145 	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
    146 		log_msg(LOG_ERR, "cannot fcntl tcp: %s", strerror(errno));
    147 	}
    148 	/* Bind it... */
    149 	if (bind(s, (struct sockaddr *)addr->ai_addr, addr->ai_addrlen) != 0) {
    150 		log_msg(LOG_ERR, "can't bind tcp socket: %s", strerror(errno));
    151 		close(s);
    152 		return -1;
    153 	}
    154 	/* Listen to it... */
    155 	if (listen(s, TCP_BACKLOG_METRICS) == -1) {
    156 		log_msg(LOG_ERR, "can't listen: %s", strerror(errno));
    157 		close(s);
    158 		return -1;
    159 	}
    160 	return s;
    161 }
    162 
    163 /**
    164  * Add and open a new metrics port
    165  * @param metrics: metrics with result list.
    166  * @param ip: ip str
    167  * @param nr: port nr
    168  * @param noproto_is_err: if lack of protocol support is an error.
    169  * @return false on failure.
    170  */
    171 static int
    172 metrics_add_open(struct daemon_metrics* metrics, struct nsd_options* cfg, const char* ip,
    173 	int nr, int noproto_is_err)
    174 {
    175 	struct addrinfo hints;
    176 	struct addrinfo* res;
    177 	struct metrics_acceptlist* hl;
    178 	int noproto = 0;
    179 	int fd, r;
    180 	char port[15];
    181 	snprintf(port, sizeof(port), "%d", nr);
    182 	port[sizeof(port)-1]=0;
    183 	memset(&hints, 0, sizeof(hints));
    184 	assert(ip);
    185 
    186 	if(ip[0] == '/') {
    187 		/* This looks like a local socket */
    188 		fd = create_local_accept_sock(ip, &noproto);
    189 		/*
    190 		 * Change socket ownership and permissions so users other
    191 		 * than root can access it provided they are in the same
    192 		 * group as the user we run as.
    193 		 */
    194 		if(fd != -1) {
    195 #ifdef HAVE_CHOWN
    196 			if(chmod(ip, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) == -1) {
    197 				VERBOSITY(3, (LOG_INFO, "cannot chmod metrics socket %s: %s", ip, strerror(errno)));
    198 			}
    199 			if (cfg->username && cfg->username[0] &&
    200 				nsd.uid != (uid_t)-1) {
    201 				if(chown(ip, nsd.uid, nsd.gid) == -1)
    202 					VERBOSITY(2, (LOG_INFO, "cannot chown %u.%u %s: %s",
    203 					  (unsigned)nsd.uid, (unsigned)nsd.gid,
    204 					  ip, strerror(errno)));
    205 			}
    206 #else
    207 			(void)cfg;
    208 #endif
    209 		}
    210 	} else {
    211 		hints.ai_socktype = SOCK_STREAM;
    212 		hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
    213 		/* if we had no interface ip name, "default" is what we
    214 		 * would do getaddrinfo for. */
    215 		if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) {
    216 			log_msg(LOG_ERR, "metrics interface %s:%s getaddrinfo: %s %s",
    217 				ip, port, gai_strerror(r),
    218 #ifdef EAI_SYSTEM
    219 				r==EAI_SYSTEM?(char*)strerror(errno):""
    220 #else
    221 				""
    222 #endif
    223 				);
    224 			return 0;
    225 		}
    226 
    227 		/* open fd */
    228 		fd = create_tcp_accept_sock(res, &noproto);
    229 		freeaddrinfo(res);
    230 	}
    231 
    232 	if(fd == -1 && noproto) {
    233 		if(!noproto_is_err)
    234 			return 1; /* return success, but do nothing */
    235 		log_msg(LOG_ERR, "cannot open metrics interface %s %d : "
    236 			"protocol not supported", ip, nr);
    237 		return 0;
    238 	}
    239 	if(fd == -1) {
    240 		log_msg(LOG_ERR, "cannot open metrics interface %s %d", ip, nr);
    241 		return 0;
    242 	}
    243 
    244 	/* alloc */
    245 	hl = (struct metrics_acceptlist*)xalloc_zero(sizeof(*hl));
    246 	hl->metrics = metrics;
    247 	hl->ident = strdup(ip);
    248 	if(!hl->ident) {
    249 		log_msg(LOG_ERR, "malloc failure");
    250 		close(fd);
    251 		free(hl);
    252 		return 0;
    253 	}
    254 	hl->next = metrics->accept_list;
    255 	metrics->accept_list = hl;
    256 
    257 	hl->accept_fd = fd;
    258 	return 1;
    259 }
    260 
    261 int
    262 daemon_metrics_open_ports(struct daemon_metrics* metrics, struct nsd_options* cfg)
    263 {
    264 	assert(cfg->metrics_enable && cfg->metrics_port);
    265 	if(cfg->metrics_interface) {
    266 		ip_address_option_type* p;
    267 		for(p = cfg->metrics_interface; p; p = p->next) {
    268 			if(!metrics_add_open(metrics, cfg, p->address, cfg->metrics_port, 1)) {
    269 				return 0;
    270 			}
    271 		}
    272 	} else {
    273 		/* defaults */
    274 		if(cfg->do_ip6 && !metrics_add_open(metrics, cfg, "::1", cfg->metrics_port, 0)) {
    275 			return 0;
    276 		}
    277 		if(cfg->do_ip4 &&
    278 			!metrics_add_open(metrics, cfg, "127.0.0.1", cfg->metrics_port, 1)) {
    279 			return 0;
    280 		}
    281 	}
    282 	return 1;
    283 }
    284 
    285 void
    286 daemon_metrics_attach(struct daemon_metrics* metrics, struct xfrd_state* xfrd)
    287 {
    288 	int fd;
    289 	struct metrics_acceptlist* p;
    290 	if(!metrics) return;
    291 	metrics->xfrd = xfrd;
    292 
    293 	metrics->http_server = evhttp_new(xfrd->event_base);
    294 	for(p = metrics->accept_list; p; p = p->next) {
    295 		fd = p->accept_fd;
    296 		if (evhttp_accept_socket(metrics->http_server, fd)) {
    297 			log_msg(LOG_ERR, "metrics: cannot set http server to accept socket");
    298 		}
    299 
    300 		/* only handle requests to metrics_path, anything else returns 404 */
    301 		evhttp_set_cb(metrics->http_server,
    302                       metrics->xfrd->nsd->options->metrics_path,
    303                       metrics_http_callback, p);
    304 		/* evhttp_set_gencb(metrics->http_server, metrics_http_callback_generic, p); */
    305 	}
    306 }
    307 
    308 /* Callback for handling the active http request to the specific URI */
    309 static void
    310 metrics_http_callback(struct evhttp_request *req, void *p)
    311 {
    312 	struct evbuffer *reply = NULL;
    313 	struct daemon_metrics *metrics = ((struct metrics_acceptlist *)p)->metrics;
    314 
    315 	/* currently only GET requests are supported/allowed */
    316 	enum evhttp_cmd_type cmd = evhttp_request_get_command(req);
    317 	if (cmd != EVHTTP_REQ_GET /* && cmd != EVHTTP_REQ_HEAD */) {
    318 		evhttp_send_error(req, HTTP_BADMETHOD, 0);
    319 		return;
    320 	}
    321 
    322 	reply = evbuffer_new();
    323 
    324 	if (!reply) {
    325 		evhttp_send_error(req, HTTP_INTERNAL, 0);
    326 		log_msg(LOG_ERR, "failed to allocate reply buffer\n");
    327 		return;
    328 	}
    329 
    330 	evhttp_add_header(evhttp_request_get_output_headers(req),
    331 	                  "Content-Type", "text/plain; version=0.0.4");
    332 #ifdef BIND8_STATS
    333 	process_stats(NULL, reply, metrics->xfrd, 1);
    334 	evhttp_send_reply(req, HTTP_OK, NULL, reply);
    335 	VERBOSITY(3, (LOG_INFO, "metrics operation completed, response sent"));
    336 #else
    337 	evhttp_send_reply(req, HTTP_NOCONTENT, "No Content - Statistics disabled", reply);
    338 	log_msg(LOG_NOTICE, "metrics requested, but no stats enabled at compile time\n");
    339 	(void)metrics;
    340 #endif /* BIND8_STATS */
    341 
    342 	evbuffer_free(reply);
    343 }
    344 
    345 #ifdef BIND8_STATS
    346 /** print long number*/
    347 static int
    348 print_longnum(struct evbuffer *buf, char* desc, uint64_t x)
    349 {
    350 	if(x > (uint64_t)1024*1024*1024) {
    351 		/*more than a Gb*/
    352 		size_t front = (size_t)(x / (uint64_t)1000000);
    353 		size_t back = (size_t)(x % (uint64_t)1000000);
    354 		return evbuffer_add_printf(buf, "%s%lu%6.6lu\n", desc,
    355 			(unsigned long)front, (unsigned long)back);
    356 	} else {
    357 		return evbuffer_add_printf(buf, "%s%lu\n", desc, (unsigned long)x);
    358 	}
    359 }
    360 
    361 static void
    362 print_metric_help_and_type(struct evbuffer *buf, char *prefix, char *name,
    363 						   char *help, char *type)
    364 {
    365 	evbuffer_add_printf(buf, "# HELP %s%s %s\n# TYPE %s%s %s\n",
    366 	                    prefix, name, help, prefix, name, type);
    367 }
    368 
    369 static void
    370 print_stat_block(struct evbuffer *buf, struct nsdst* st,
    371                   char *name)
    372 {
    373 	size_t i;
    374 
    375 	const char* rcstr[] = {"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
    376 		"NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH",
    377 		"NOTZONE", "RCODE11", "RCODE12", "RCODE13", "RCODE14", "RCODE15",
    378 		"BADVERS"
    379 	};
    380 
    381 	char prefix[512] = {0};
    382 	if (name) {
    383 		snprintf(prefix, sizeof(prefix), "nsd_zonestats_%s_", name);
    384 	} else {
    385 		snprintf(prefix, sizeof(prefix), "nsd_");
    386 	}
    387 
    388 	/* nsd_queries_by_type_total */
    389 	print_metric_help_and_type(buf, prefix, "queries_by_type_total",
    390 	                           "Total number of queries received by type.",
    391 	                           "counter");
    392 	for(i=0; i<= 255; i++) {
    393 		if(metrics_inhibit_zero && st->qtype[i] == 0 &&
    394 			strncmp(rrtype_to_string(i), "TYPE", 4) == 0)
    395 			continue;
    396 		evbuffer_add_printf(buf, "%squeries_by_type_total{type=\"%s\"} %lu\n",
    397 			prefix, rrtype_to_string(i), (unsigned long)st->qtype[i]);
    398 	}
    399 
    400 	/* nsd_queries_by_class_total */
    401 	print_metric_help_and_type(buf, prefix, "queries_by_class_total",
    402 	                           "Total number of queries received by class.",
    403 	                           "counter");
    404 	for(i=0; i<4; i++) {
    405 		if(metrics_inhibit_zero && st->qclass[i] == 0 && i != CLASS_IN)
    406 			continue;
    407 		evbuffer_add_printf(buf, "%squeries_by_class_total{class=\"%s\"} %lu\n",
    408 			prefix, rrclass_to_string(i), (unsigned long)st->qclass[i]);
    409 	}
    410 
    411 	/* nsd_queries_by_opcode_total */
    412 	print_metric_help_and_type(buf, prefix, "queries_by_opcode_total",
    413 	                           "Total number of queries received by opcode.",
    414 	                           "counter");
    415 	for(i=0; i<6; i++) {
    416 		if(metrics_inhibit_zero && st->opcode[i] == 0 && i != OPCODE_QUERY)
    417 			continue;
    418 		evbuffer_add_printf(buf, "%squeries_by_opcode_total{opcode=\"%s\"} %lu\n",
    419 			prefix, opcode2str(i), (unsigned long)st->opcode[i]);
    420 	}
    421 
    422 	/* nsd_queries_by_rcode_total */
    423 	print_metric_help_and_type(buf, prefix, "queries_by_rcode_total",
    424 	                           "Total number of queries received by rcode.",
    425 	                           "counter");
    426 	for(i=0; i<17; i++) {
    427 		if(metrics_inhibit_zero && st->rcode[i] == 0 &&
    428 			i > RCODE_YXDOMAIN) /*NSD does not use larger*/
    429 			continue;
    430 		evbuffer_add_printf(buf, "%squeries_by_rcode_total{rcode=\"%s\"} %lu\n",
    431 			prefix, rcstr[i], (unsigned long)st->rcode[i]);
    432 	}
    433 
    434 	/* nsd_queries_by_transport_total */
    435 	print_metric_help_and_type(buf, prefix, "queries_by_transport_total",
    436 		"Total number of queries received by transport.",
    437 		"counter");
    438 	evbuffer_add_printf(buf, "%squeries_by_transport_total{transport=\"udp\"} %lu\n", prefix, (unsigned long)st->qudp);
    439 	evbuffer_add_printf(buf, "%squeries_by_transport_total{transport=\"udp6\"} %lu\n", prefix, (unsigned long)st->qudp6);
    440 
    441 	/* nsd_queries_with_edns_total */
    442 	print_metric_help_and_type(buf, prefix, "queries_with_edns_total",
    443 		"Total number of queries received with EDNS OPT.",
    444 		"counter");
    445 	evbuffer_add_printf(buf, "%squeries_with_edns_total %lu\n", prefix, (unsigned long)st->edns);
    446 
    447 	/* nsd_queries_with_edns_failed_total */
    448 	print_metric_help_and_type(buf, prefix, "queries_with_edns_failed_total",
    449 		"Total number of queries received with EDNS OPT where EDNS parsing failed.",
    450 		"counter");
    451 	evbuffer_add_printf(buf, "%squeries_with_edns_failed_total %lu\n", prefix, (unsigned long)st->ednserr);
    452 
    453 	/* nsd_connections_total */
    454 	print_metric_help_and_type(buf, prefix, "connections_total",
    455 		"Total number of connections.",
    456 		"counter");
    457 	evbuffer_add_printf(buf, "%sconnections_total{transport=\"tcp\"} %lu\n", prefix, (unsigned long)st->ctcp);
    458 	evbuffer_add_printf(buf, "%sconnections_total{transport=\"tcp6\"} %lu\n", prefix, (unsigned long)st->ctcp6);
    459 	evbuffer_add_printf(buf, "%sconnections_total{transport=\"tls\"} %lu\n", prefix, (unsigned long)st->ctls);
    460 	evbuffer_add_printf(buf, "%sconnections_total{transport=\"tls6\"} %lu\n", prefix, (unsigned long)st->ctls6);
    461 
    462 	/* nsd_xfr_requests_served_total */
    463 	print_metric_help_and_type(buf, prefix, "xfr_requests_served_total",
    464 		"Total number of answered zone transfers.",
    465 		"counter");
    466 	evbuffer_add_printf(buf, "%sxfr_requests_served_total{xfrtype=\"AXFR\"} %lu\n", prefix, (unsigned long)st->raxfr);
    467 	evbuffer_add_printf(buf, "%sxfr_requests_served_total{xfrtype=\"IXFR\"} %lu\n", prefix, (unsigned long)st->rixfr);
    468 
    469 	/* nsd_queries_dropped_total */
    470 	print_metric_help_and_type(buf, prefix, "queries_dropped_total",
    471 		"Total number of dropped queries.",
    472 		"counter");
    473 	evbuffer_add_printf(buf, "%squeries_dropped_total %lu\n", prefix, (unsigned long)st->dropped);
    474 
    475 	/* nsd_queries_rx_failed_total */
    476 	print_metric_help_and_type(buf, prefix, "queries_rx_failed_total",
    477 		"Total number of queries where receive failed.",
    478 		"counter");
    479 	evbuffer_add_printf(buf, "%squeries_rx_failed_total %lu\n", prefix, (unsigned long)st->rxerr);
    480 
    481 	/* nsd_answers_tx_failed_total */
    482 	print_metric_help_and_type(buf, prefix, "answers_tx_failed_total",
    483 		"Total number of answers where transmit failed.",
    484 		"counter");
    485 	evbuffer_add_printf(buf, "%sanswers_tx_failed_total %lu\n", prefix, (unsigned long)st->txerr);
    486 
    487 	/* nsd_answers_without_aa_total */
    488 	print_metric_help_and_type(buf, prefix, "answers_without_aa_total",
    489 		"Total number of NOERROR answers without AA flag set.",
    490 		"counter");
    491 	evbuffer_add_printf(buf, "%sanswers_without_aa_total %lu\n", prefix, (unsigned long)st->nona);
    492 
    493 	/* nsd_answers_truncated_total */
    494 	print_metric_help_and_type(buf, prefix, "answers_truncated_total",
    495 		"Total number of truncated answers.",
    496 		"counter");
    497 	evbuffer_add_printf(buf, "%sanswers_truncated_total %lu\n", prefix, (unsigned long)st->truncated);
    498 }
    499 
    500 #ifdef USE_ZONE_STATS
    501 void
    502 metrics_zonestat_print_one(struct evbuffer *buf, char *name,
    503                            struct nsdst *zst)
    504 {
    505 	char prefix[512] = {0};
    506 	snprintf(prefix, sizeof(prefix), "nsd_zonestats_%s_", name);
    507 
    508 	print_metric_help_and_type(buf, prefix, "queries_total",
    509 		"Total number of queries received.", "counter");
    510 	evbuffer_add_printf(buf, "nsd_zonestats_%s_queries_total %lu\n", name,
    511 		(unsigned long)(zst->qudp + zst->qudp6 + zst->ctcp +
    512 			zst->ctcp6 + zst->ctls + zst->ctls6));
    513 	print_stat_block(buf, zst, name);
    514 }
    515 #endif /*USE_ZONE_STATS*/
    516 
    517 void
    518 metrics_print_stats(struct evbuffer *buf, xfrd_state_type *xfrd,
    519                     struct timeval *now, int clear, struct nsdst *st,
    520                     struct nsdst **zonestats, struct timeval *rc_stats_time)
    521 {
    522 	size_t i;
    523 	struct timeval elapsed, uptime;
    524 
    525 	/* nsd_queries_total */
    526 	print_metric_help_and_type(buf, "nsd_", "queries_total",
    527 	                           "Total number of queries received.", "counter");
    528 	/*per CPU and total*/
    529 	for(i=0; i<xfrd->nsd->child_count; i++) {
    530 		evbuffer_add_printf(buf, "nsd_queries_total{server=\"%d\"} %lu\n",
    531 			(int)i, (unsigned long)xfrd->nsd->children[i].query_count);
    532 	}
    533 
    534 	print_stat_block(buf, st, NULL);
    535 
    536 	/* uptime (in seconds) */
    537 	timeval_subtract(&uptime, now, &xfrd->nsd->metrics->boot_time);
    538 	print_metric_help_and_type(buf, "nsd_", "time_up_seconds_total",
    539 	                           "Uptime since server boot in seconds.", "counter");
    540 	evbuffer_add_printf(buf, "nsd_time_up_seconds_total %lu.%6.6lu\n",
    541 		(unsigned long)uptime.tv_sec, (unsigned long)uptime.tv_usec);
    542 
    543 	/* time elapsed since last nsd-control stats reset (in seconds) */
    544 	/* if remote-control is disabled aka rc_stats_time == NULL
    545 	 * use metrics' stats_time */
    546 	if (rc_stats_time) {
    547 		timeval_subtract(&elapsed, now, rc_stats_time);
    548 	} else {
    549 		timeval_subtract(&elapsed, now, &xfrd->nsd->metrics->stats_time);
    550 	}
    551 	print_metric_help_and_type(buf, "nsd_", "time_elapsed_seconds",
    552 	                           "Time since last statistics printout and "
    553 	                           "reset (by nsd-control stats) in seconds.",
    554 	                           "untyped");
    555 	evbuffer_add_printf(buf, "nsd_time_elapsed_seconds %lu.%6.6lu\n",
    556 		(unsigned long)elapsed.tv_sec, (unsigned long)elapsed.tv_usec);
    557 
    558 	/*mem info, database on disksize*/
    559 	print_metric_help_and_type(buf, "nsd_", "size_db_on_disk_bytes",
    560 	                           "Size of DNS database on disk.", "gauge");
    561 	print_longnum(buf, "nsd_size_db_on_disk_bytes ", st->db_disk);
    562 
    563 	print_metric_help_and_type(buf, "nsd_", "size_db_in_mem_bytes",
    564 	                           "Size of DNS database in memory.", "gauge");
    565 	print_longnum(buf, "nsd_size_db_in_mem_bytes ", st->db_mem);
    566 
    567 	print_metric_help_and_type(buf, "nsd_", "size_xfrd_in_mem_bytes",
    568 	                           "Size of zone transfers and notifies in xfrd process, excluding TSIG data.",
    569 	                           "gauge");
    570 	print_longnum(buf, "nsd_size_xfrd_in_mem_bytes ", region_get_mem(xfrd->region));
    571 
    572 	print_metric_help_and_type(buf, "nsd_", "size_config_on_disk_bytes",
    573 	                           "Size of zonelist file on disk, excluding nsd.conf.",
    574 	                           "gauge");
    575 	print_longnum(buf, "nsd_size_config_on_disk_bytes ",
    576 		xfrd->nsd->options->zonelist_off);
    577 
    578 	print_metric_help_and_type(buf, "nsd_", "size_config_in_mem_bytes",
    579 	                           "Size of config data in memory.", "gauge");
    580 	print_longnum(buf, "nsd_size_config_in_mem_bytes ", region_get_mem(
    581 		xfrd->nsd->options->region));
    582 
    583 	/* number of zones serverd */
    584 	print_metric_help_and_type(buf, "nsd_", "zones_primary",
    585 	                           "Number of primary zones served.", "gauge");
    586 	evbuffer_add_printf(buf, "nsd_zones_primary %lu\n",
    587 		(unsigned long)(xfrd->notify_zones->count - xfrd->zones->count));
    588 
    589 	print_metric_help_and_type(buf, "nsd_", "zones_secondary",
    590 	                           "Number of secondary zones served.", "gauge");
    591 	evbuffer_add_printf(buf, "nsd_zones_secondary %lu\n",
    592 		(unsigned long)xfrd->zones->count);
    593 
    594 #ifdef USE_ZONE_STATS
    595 	zonestat_print(NULL, buf, xfrd, clear, zonestats); /*per-zone statistics*/
    596 #else
    597 	(void)clear; (void)zonestats;
    598 #endif
    599 }
    600 
    601 #endif /*BIND8_STATS*/
    602 
    603 #endif /* USE_METRICS */
    604