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