From 7f5608206e921c668cc83460fc1e53ef515107e1 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 15 Jan 2026 14:38:44 +0000 Subject: [PATCH 1/7] Use standard reference counting for isc_histomulti Use reference counting for isc_histomulti module so that it's possible to attach/detach to/from the objects when used in the statistics channel in the coming commits. --- lib/isc/histo.c | 20 ++++++++++++++------ lib/isc/include/isc/histo.h | 29 +++++++++++++++-------------- lib/ns/server.c | 16 ++++++++-------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/lib/isc/histo.c b/lib/isc/histo.c index aac7081db6..acfd3256c4 100644 --- a/lib/isc/histo.c +++ b/lib/isc/histo.c @@ -74,6 +74,7 @@ struct isc_histo { struct isc_histomulti { uint magic; uint size; + isc_refcount_t references; isc_histo_t *hg[]; }; @@ -386,22 +387,23 @@ isc_histomulti_create(isc_mem_t *mctx, uint sigbits, isc_histomulti_t **hmp) { isc_histo_create(mctx, sigbits, &hm->hg[i]); } + isc_refcount_init(&hm->references, 1); + *hmp = hm; } -void -isc_histomulti_destroy(isc_histomulti_t **hmp) { - REQUIRE(hmp != NULL); - REQUIRE(HISTOMULTI_VALID(*hmp)); +static void +isc__histomulti_destroy(isc_histomulti_t *hm) { + REQUIRE(HISTOMULTI_VALID(hm)); - isc_histomulti_t *hm = *hmp; isc_mem_t *mctx = hm->hg[0]->mctx; - *hmp = NULL; for (uint i = 0; i < hm->size; i++) { isc_histo_destroy(&hm->hg[i]); } + isc_refcount_destroy(&hm->references); + isc_mem_put(mctx, hm, STRUCT_FLEX_SIZE(hm, hg, hm->size)); } @@ -426,6 +428,12 @@ isc_histomulti_inc(isc_histomulti_t *hm, uint64_t value) { isc_histomulti_add(hm, value, 1); } +#ifdef ISC_HISTO_TRACE +ISC_REFCOUNT_TRACE_IMPL(isc_histomulti, isc__histomulti_destroy); +#else +ISC_REFCOUNT_IMPL(isc_histomulti, isc__histomulti_destroy); +#endif /* ISC_HISTO_TRACE */ + /**********************************************************************/ /* diff --git a/lib/isc/include/isc/histo.h b/lib/isc/include/isc/histo.h index 4c9123fbbc..80c2b15faf 100644 --- a/lib/isc/include/isc/histo.h +++ b/lib/isc/include/isc/histo.h @@ -278,20 +278,6 @@ isc_histomulti_create(isc_mem_t *mctx, uint sigbits, isc_histomulti_t **hmp); *\li `*hmp` is a pointer to a multithreaded sharded histogram. */ -void -isc_histomulti_destroy(isc_histomulti_t **hmp); -/*%< - * Destroy a multithreaded sharded histogram - * - * Requires: - *\li `hmp != NULL` - *\li `*hmp` is a pointer to a valid multithreaded sharded histogram - * - * Ensures: - *\li all memory allocated by the histogram has been released - *\li `*hmp == NULL` - */ - void isc_histomulti_merge(isc_histo_t **targetp, const isc_histomulti_t *source); /*%< @@ -326,6 +312,21 @@ isc_histomulti_add(isc_histomulti_t *hm, uint64_t value, uint64_t inc); *\li `hm` is a pointer to a valid histomulti */ +#ifdef ISC_HISTO_TRACE +#define isc_histomulti_ref(ptr) \ + dns_histomulti__ref(ptr, __func__, __FILE__, __LINE__) +#define isc_histomulti_unref(ptr) \ + isc_histomulti__unref(ptr, __func__, __FILE__, __LINE__) +#define isc_histomulti_attach(ptr, ptrp) \ + isc_histomulti__attach(ptr, ptrp, __func__, __FILE__, __LINE__) +#define isc_histomulti_detach(ptrp) \ + isc_histomulti__detach(ptrp, __func__, __FILE__, __LINE__) + +ISC_REFCOUNT_TRACE_DECL(isc_histomulti); +#else +ISC_REFCOUNT_DECL(isc_histomulti); +#endif /* ISC_HISTO_TRACE */ + /**********************************************************************/ void diff --git a/lib/ns/server.c b/lib/ns/server.c index e0843905d9..857c6d0b6a 100644 --- a/lib/ns/server.c +++ b/lib/ns/server.c @@ -176,29 +176,29 @@ ns_server_detach(ns_server_t **sctxp) { } if (sctx->udpinstats4 != NULL) { - isc_histomulti_destroy(&sctx->udpinstats4); + isc_histomulti_detach(&sctx->udpinstats4); } if (sctx->tcpinstats4 != NULL) { - isc_histomulti_destroy(&sctx->tcpinstats4); + isc_histomulti_detach(&sctx->tcpinstats4); } if (sctx->udpoutstats4 != NULL) { - isc_histomulti_destroy(&sctx->udpoutstats4); + isc_histomulti_detach(&sctx->udpoutstats4); } if (sctx->tcpoutstats4 != NULL) { - isc_histomulti_destroy(&sctx->tcpoutstats4); + isc_histomulti_detach(&sctx->tcpoutstats4); } if (sctx->udpinstats6 != NULL) { - isc_histomulti_destroy(&sctx->udpinstats6); + isc_histomulti_detach(&sctx->udpinstats6); } if (sctx->tcpinstats6 != NULL) { - isc_histomulti_destroy(&sctx->tcpinstats6); + isc_histomulti_detach(&sctx->tcpinstats6); } if (sctx->udpoutstats6 != NULL) { - isc_histomulti_destroy(&sctx->udpoutstats6); + isc_histomulti_detach(&sctx->udpoutstats6); } if (sctx->tcpoutstats6 != NULL) { - isc_histomulti_destroy(&sctx->tcpoutstats6); + isc_histomulti_detach(&sctx->tcpoutstats6); } sctx->magic = 0; From e41fbea8432e987d1004c38c30a88d6bd6183c11 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 15 Jan 2026 14:46:06 +0000 Subject: [PATCH 2/7] Replace the outgoing queries RTT histogram code with isc_histomulti The granularity of the simple histogram with fixed number of ranges sometimes isn't good enough. As there's a need to implement a new histogram statistics for the incoming query times (RTT), it was decided to also update the existing RTT statistics of the outgoing queries so that they look similar and use common code. Remove the old histogram code from the resolver and from the statistics channel. Reimplement the outgoing queries RTT histogram using the isc_histomulti module, and prepare the necessary base for implementing the incoming queries RTT histogram. The statistics channel will be updated to expose the new histograms in an upcoming commit. --- bin/named/server.c | 23 ++++++++++++ bin/named/statschannel.c | 22 ----------- lib/dns/include/dns/resolver.h | 55 ++++++++++++++++----------- lib/dns/include/dns/stats.h | 61 ++++++++++++++++++------------ lib/dns/resolver.c | 69 +++++++++++++++++++++++----------- 5 files changed, 140 insertions(+), 90 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index 85e8c68652..160e3d5990 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -3678,6 +3678,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, const cfg_obj_t *disablelist = NULL; isc_stats_t *resstats = NULL; dns_stats_t *resquerystats = NULL; + isc_histomulti_t *resqueryinrttstats = NULL; + isc_histomulti_t *resqueryoutrttstats = NULL; bool auto_root = false; named_cache_t *nsc = NULL; bool zero_no_soattl; @@ -4259,6 +4261,9 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, dns_resolver_getstats(pview->resolver, &resstats); dns_resolver_getquerystats(pview->resolver, &resquerystats); + dns_resolver_getqueryrttstats(pview->resolver, + &resqueryinrttstats, + &resqueryoutrttstats); dns_view_detach(&pview); } } @@ -4319,11 +4324,23 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, isc_stats_create(mctx, &resstats, dns_resstatscounter_max); } dns_resolver_setstats(view->resolver, resstats); + if (resquerystats == NULL) { dns_rdatatypestats_create(mctx, &resquerystats); } dns_resolver_setquerystats(view->resolver, resquerystats); + if (resqueryinrttstats == NULL) { + isc_histomulti_create(mctx, DNS_RTTHISTO_SIGBITS, + &resqueryinrttstats); + } + if (resqueryoutrttstats == NULL) { + isc_histomulti_create(mctx, DNS_RTTHISTO_SIGBITS, + &resqueryoutrttstats); + } + dns_resolver_setqueryrttstats(view->resolver, resqueryinrttstats, + resqueryoutrttstats); + /* * Set the ADB cache size to 1/8th of the max-cache-size or * MAX_ADB_SIZE_FOR_CACHESHARE when the cache is shared. @@ -5487,6 +5504,12 @@ cleanup: if (resquerystats != NULL) { dns_stats_detach(&resquerystats); } + if (resqueryinrttstats != NULL) { + isc_histomulti_detach(&resqueryinrttstats); + } + if (resqueryoutrttstats != NULL) { + isc_histomulti_detach(&resqueryoutrttstats); + } if (order != NULL) { dns_order_detach(&order); } diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 51184ffe44..f720d7c80e 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -441,28 +441,6 @@ init_desc(void) { SET_RESSTATDESC(valnegsuccess, "DNSSEC NX validation succeeded", "ValNegOk"); SET_RESSTATDESC(valfail, "DNSSEC validation failed", "ValFail"); - SET_RESSTATDESC(queryrtt0, - "queries with RTT < " DNS_RESOLVER_QRYRTTCLASS0STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS0STR); - SET_RESSTATDESC(queryrtt1, - "queries with RTT " DNS_RESOLVER_QRYRTTCLASS0STR - "-" DNS_RESOLVER_QRYRTTCLASS1STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS1STR); - SET_RESSTATDESC(queryrtt2, - "queries with RTT " DNS_RESOLVER_QRYRTTCLASS1STR - "-" DNS_RESOLVER_QRYRTTCLASS2STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS2STR); - SET_RESSTATDESC(queryrtt3, - "queries with RTT " DNS_RESOLVER_QRYRTTCLASS2STR - "-" DNS_RESOLVER_QRYRTTCLASS3STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS3STR); - SET_RESSTATDESC(queryrtt4, - "queries with RTT " DNS_RESOLVER_QRYRTTCLASS3STR - "-" DNS_RESOLVER_QRYRTTCLASS4STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS4STR); - SET_RESSTATDESC(queryrtt5, - "queries with RTT > " DNS_RESOLVER_QRYRTTCLASS4STR "ms", - "QryRTT" DNS_RESOLVER_QRYRTTCLASS4STR "+"); SET_RESSTATDESC(nfetch, "active fetches", "NumFetch"); SET_RESSTATDESC(buckets, "bucket size", "BucketSize"); SET_RESSTATDESC(refused, "REFUSED received", "REFUSED"); diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 5c56d5d86d..6ec13fc631 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -143,21 +144,6 @@ enum { DNS_FETCHOPT_EDNSVERSIONMASK = 0xff000000, }; -/* - * Upper bounds of class of query RTT (ms). Corresponds to - * dns_resstatscounter_queryrttX statistics counters. - */ -#define DNS_RESOLVER_QRYRTTCLASS0 10 -#define DNS_RESOLVER_QRYRTTCLASS0STR "10" -#define DNS_RESOLVER_QRYRTTCLASS1 100 -#define DNS_RESOLVER_QRYRTTCLASS1STR "100" -#define DNS_RESOLVER_QRYRTTCLASS2 500 -#define DNS_RESOLVER_QRYRTTCLASS2STR "500" -#define DNS_RESOLVER_QRYRTTCLASS3 800 -#define DNS_RESOLVER_QRYRTTCLASS3STR "800" -#define DNS_RESOLVER_QRYRTTCLASS4 1600 -#define DNS_RESOLVER_QRYRTTCLASS4STR "1600" - /* * XXXRTH Should this API be made semi-private? (I.e. * _dns_resolver_create()). @@ -586,8 +572,7 @@ dns_resolver_setstats(dns_resolver_t *res, isc_stats_t *stats); * * Requires: * \li 'res' is valid. - * - *\li stats is a valid statistics supporting resolver statistics counters + * \li stats is a valid statistics supporting resolver statistics counters * (see dns/stats.h). */ @@ -600,8 +585,7 @@ dns_resolver_getstats(dns_resolver_t *res, isc_stats_t **statsp); * * Requires: * \li 'res' is valid. - * - *\li 'statsp' != NULL && '*statsp' != NULL + * \li 'statsp' != NULL && '*statsp' != NULL */ void @@ -623,8 +607,7 @@ dns_resolver_setquerystats(dns_resolver_t *res, dns_stats_t *stats); * * Requires: * \li 'res' is valid. - * - *\li stats is a valid statistics created by dns_rdatatypestats_create(). + * \li 'stats' is a valid statistics created by dns_rdatatypestats_create(). */ void @@ -636,8 +619,36 @@ dns_resolver_getquerystats(dns_resolver_t *res, dns_stats_t **statsp); * * Requires: * \li 'res' is valid. + * \li 'statsp' != NULL && '*statsp' != NULL + */ + +void +dns_resolver_setqueryrttstats(dns_resolver_t *res, isc_histomulti_t *hmin, + isc_histomulti_t *hmout); +/*%< + * Set a query RTT statistics histograms 'hmin' and 'hmout' for 'res'. Once the + * statistic histograms are installed, the resolver will start updating them + * with the incoming and outgoing queries' RTT values accordingly. * - *\li 'statsp' != NULL && '*statsp' != NULL + * Requires: + * \li 'res' is valid. + * \li 'stats' is a valid statistics created by dns_rdatatypestats_create(). + * \li 'hmin' is a valid isc_histomulti_t object. + * \li 'hmout' is a valid isc_histomulti_t object. + */ + +void +dns_resolver_getqueryrttstats(dns_resolver_t *res, isc_histomulti_t **hmpin, + isc_histomulti_t **hmpout); +/*%< + * Get the query RTT statistics histograms for 'res'. If the histograms are set + * then the corresponding '*hmp{in,out}' are attached to them; otherwise, + * '*hmp{in,out}' are untouched. + * + * Requires: + * \li 'res' is valid. + * \li 'hmpin' == NULL || '*hmpin' == NULL + * \li 'hmpout' == NULL || '*hmpout' == NULL */ void diff --git a/lib/dns/include/dns/stats.h b/lib/dns/include/dns/stats.h index b40f5bc999..0bbdde4638 100644 --- a/lib/dns/include/dns/stats.h +++ b/lib/dns/include/dns/stats.h @@ -19,6 +19,7 @@ #include +#include #include /*% @@ -52,30 +53,24 @@ enum { dns_resstatscounter_dispabort = 21, dns_resstatscounter_dispsockfail = 22, dns_resstatscounter_querytimeout = 23, - dns_resstatscounter_queryrtt0 = 24, - dns_resstatscounter_queryrtt1 = 25, - dns_resstatscounter_queryrtt2 = 26, - dns_resstatscounter_queryrtt3 = 27, - dns_resstatscounter_queryrtt4 = 28, - dns_resstatscounter_queryrtt5 = 29, - dns_resstatscounter_nfetch = 30, - dns_resstatscounter_disprequdp = 31, - dns_resstatscounter_dispreqtcp = 32, - dns_resstatscounter_buckets = 33, - dns_resstatscounter_refused = 34, - dns_resstatscounter_cookienew = 35, - dns_resstatscounter_cookieout = 36, - dns_resstatscounter_cookiein = 37, - dns_resstatscounter_cookieok = 38, - dns_resstatscounter_badvers = 39, - dns_resstatscounter_badcookie = 40, - dns_resstatscounter_zonequota = 41, - dns_resstatscounter_serverquota = 42, - dns_resstatscounter_clientquota = 43, - dns_resstatscounter_nextitem = 44, - dns_resstatscounter_priming = 45, - dns_resstatscounter_forwardonlyfail = 46, - dns_resstatscounter_max = 47, + dns_resstatscounter_nfetch = 24, + dns_resstatscounter_disprequdp = 25, + dns_resstatscounter_dispreqtcp = 26, + dns_resstatscounter_buckets = 27, + dns_resstatscounter_refused = 28, + dns_resstatscounter_cookienew = 29, + dns_resstatscounter_cookieout = 30, + dns_resstatscounter_cookiein = 31, + dns_resstatscounter_cookieok = 32, + dns_resstatscounter_badvers = 33, + dns_resstatscounter_badcookie = 34, + dns_resstatscounter_zonequota = 35, + dns_resstatscounter_serverquota = 36, + dns_resstatscounter_clientquota = 37, + dns_resstatscounter_nextitem = 38, + dns_resstatscounter_priming = 39, + dns_resstatscounter_forwardonlyfail = 40, + dns_resstatscounter_max = 41, /* * DNSSEC stats. @@ -203,6 +198,24 @@ enum { dns_sizecounter_out_max = DNS_SIZEHISTO_MAXOUT + 1, }; +/* + * This gives enough amount of buckets in the lower end of the values (which + * are the values up to about 30000 milliseconds (MAXIMUM_QUERY_TIMEOUT) to make + * sense for the resolver) to be useful and not to be too many. E.g. bucket + * number 30 covers values between 56 and 59, while bucket number 102 covers the + * values between 28672 and 30719. + */ +#define DNS_RTTHISTO_SIGBITS 3 +#define DNS_RTTHISTO_MAX 102 + +/* + * For consistency with other stats counters + */ +enum { + dns_queryrttcounter_in_max = DNS_RTTHISTO_MAX + 1, + dns_queryrttcounter_out_max = DNS_RTTHISTO_MAX + 1, +}; + /*% * Attributes for statistics counters of RRset and Rdatatype types. * diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 68efd7580f..3dc51cdb8b 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -207,7 +208,11 @@ #define DEFAULT_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U) #endif /* ifndef DEFAULT_QUERY_TIMEOUT */ -/* The maximum time in seconds for the whole query to live. */ +/* + * The maximum time in seconds for the whole query to live. + * + * Note: if increased, DNS_RTTHISTO_MAX should also be updated. + */ #ifndef MAXIMUM_QUERY_TIMEOUT #define MAXIMUM_QUERY_TIMEOUT 30000 #endif /* ifndef MAXIMUM_QUERY_TIMEOUT */ @@ -598,6 +603,8 @@ struct dns_resolver { isc_result_t quotaresp[2]; isc_stats_t *stats; dns_stats_t *querystats; + isc_histomulti_t *queryinrttstats; + isc_histomulti_t *queryoutrttstats; /* Additions for serve-stale feature. */ unsigned int retryinterval; /* in milliseconds */ @@ -1255,31 +1262,17 @@ fctx_cancelquery(resquery_t **queryp, isc_time_t *finish, bool no_response, * We have both the start and finish times for this * packet, so we can compute a real RTT. */ - unsigned int rttms; - rtt = (unsigned int)isc_time_microdiff(finish, &query->start); - rttms = rtt / US_PER_MS; factor = DNS_ADB_RTTADJDEFAULT; - if (rttms < DNS_RESOLVER_QRYRTTCLASS0) { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt0); - } else if (rttms < DNS_RESOLVER_QRYRTTCLASS1) { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt1); - } else if (rttms < DNS_RESOLVER_QRYRTTCLASS2) { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt2); - } else if (rttms < DNS_RESOLVER_QRYRTTCLASS3) { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt3); - } else if (rttms < DNS_RESOLVER_QRYRTTCLASS4) { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt4); - } else { - inc_stats(fctx->res, - dns_resstatscounter_queryrtt5); + if (fctx->res->queryoutrttstats != NULL) { + const unsigned int rttms = rtt / US_PER_MS; + isc_histomulti_inc( + fctx->res->queryoutrttstats, + rttms < MAXIMUM_QUERY_TIMEOUT + ? rttms + : MAXIMUM_QUERY_TIMEOUT); } } else { uint32_t value; @@ -9642,6 +9635,12 @@ dns_resolver__destroy(dns_resolver_t *res) { dns_nametree_detach(&res->algorithms); dns_nametree_detach(&res->digests); + if (res->queryoutrttstats != NULL) { + isc_histomulti_detach(&res->queryoutrttstats); + } + if (res->queryinrttstats != NULL) { + isc_histomulti_detach(&res->queryinrttstats); + } if (res->querystats != NULL) { dns_stats_detach(&res->querystats); } @@ -10884,6 +10883,32 @@ dns_resolver_getquerystats(dns_resolver_t *res, dns_stats_t **statsp) { } } +void +dns_resolver_setqueryrttstats(dns_resolver_t *res, isc_histomulti_t *hmin, + isc_histomulti_t *hmout) { + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(res->queryinrttstats == NULL); + REQUIRE(res->queryoutrttstats == NULL); + + isc_histomulti_attach(hmin, &res->queryinrttstats); + isc_histomulti_attach(hmout, &res->queryoutrttstats); +} + +void +dns_resolver_getqueryrttstats(dns_resolver_t *res, isc_histomulti_t **hmpin, + isc_histomulti_t **hmpout) { + REQUIRE(VALID_RESOLVER(res)); + REQUIRE(hmpin == NULL || *hmpin == NULL); + REQUIRE(hmpout == NULL || *hmpout == NULL); + + if (hmpin != NULL && res->queryinrttstats != NULL) { + isc_histomulti_attach(res->queryinrttstats, hmpin); + } + if (hmpout != NULL && res->queryoutrttstats != NULL) { + isc_histomulti_attach(res->queryoutrttstats, hmpout); + } +} + void dns_resolver_freefresp(dns_fetchresponse_t **frespp) { REQUIRE(frespp != NULL); From 393f932dbff8bc8bc01149261c4ba5eaa22a8579 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 15 Jan 2026 14:58:24 +0000 Subject: [PATCH 3/7] Keep client->inner.tnow and client->inner.now in sync The incoming queries RTT statistics are going to need correct time information for calculations. --- lib/ns/query.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ns/query.c b/lib/ns/query.c index b457d61173..8e7891d60c 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -2703,7 +2703,8 @@ stale_refresh_aftermath(ns_client_t *client, isc_result_t result) { * database, starting the stale-refresh-time window for it. * This is a condensed form of query_lookup(). */ - client->inner.now = isc_stdtime_now(); + client->inner.tnow = isc_time_now(); + client->inner.now = isc_time_seconds(&client->inner.tnow); client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; qctx_init(client, NULL, 0, &qctx); @@ -6076,7 +6077,8 @@ fetch_callback(void *arg) { /* * Update client->now. */ - client->inner.now = isc_stdtime_now(); + client->inner.tnow = isc_time_now(); + client->inner.now = isc_time_seconds(&client->inner.tnow); } else { /* * This is a fetch completion event for a canceled fetch. @@ -6538,7 +6540,8 @@ query_hookresume(void *arg) { INSIST(rev->ctx == client->query.hookasyncctx); client->query.hookasyncctx = NULL; canceled = false; - client->inner.now = isc_stdtime_now(); + client->inner.tnow = isc_time_now(); + client->inner.now = isc_time_seconds(&client->inner.tnow); } else { canceled = true; } From 450a4189b31c55013d863aab5c32bea0a24d069f Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 15 Jan 2026 15:03:09 +0000 Subject: [PATCH 4/7] Calculate incoming queries RTT statistics In client_senddone() update the RTT statistics for the incoming queries. --- lib/ns/client.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/ns/client.c b/lib/ns/client.c index 3c42a85bf0..c0a5649a3b 100644 --- a/lib/ns/client.c +++ b/lib/ns/client.c @@ -325,7 +325,24 @@ client_senddone(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { */ client->inner.sendhandle = NULL; - if (result != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + isc_histomulti_t *hmpin = NULL; + + if (client->inner.view != NULL && + client->inner.view->resolver != NULL) + { + dns_resolver_getqueryrttstats( + client->inner.view->resolver, &hmpin, NULL); + } + if (hmpin != NULL) { + const unsigned int rtt = (unsigned int) + isc_time_microdiff(&client->inner.tnow, + &client->inner.requesttime); + const unsigned int rttms = rtt / US_PER_MS; + isc_histomulti_inc(hmpin, rttms); + isc_histomulti_detach(&hmpin); + } + } else { if (!TCP_CLIENT(client) && result == ISC_R_MAXSIZE) { ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3), From 0affd0dbcbf074fa1e63d6916598dcc8ca15147d Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 15 Jan 2026 15:04:45 +0000 Subject: [PATCH 5/7] Show the incoming/outgoing queries' RTT statistics in stats channel Expose the new isc_histo_t-based RTT statistics in the statistics channel for the XML and JSON versions. --- bin/named/bind9.xsl | 46 +++++++ bin/named/statschannel.c | 250 ++++++++++++++++++++++++++++++++------- 2 files changed, 251 insertions(+), 45 deletions(-) diff --git a/bin/named/bind9.xsl b/bin/named/bind9.xsl index 359c2d99e4..bc018c0093 100644 --- a/bin/named/bind9.xsl +++ b/bin/named/bind9.xsl @@ -941,6 +941,52 @@ + + +

Incoming Queries Response Time for View

+ + + + + + + + + even + odd + + + + + + + +
MillisecondsCount
+
+
+ + +

Outgoing Queries Response Time for View

+ + + + + + + + + even + odd + + + + + + + +
MillisecondsCount
+
+

Memory Usage Summary

diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index f720d7c80e..08020fd494 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -164,6 +164,8 @@ static const char *tcpinsizestats_xmldesc[dns_sizecounter_in_max]; static const char *tcpoutsizestats_xmldesc[dns_sizecounter_out_max]; static const char *dnstapstats_xmldesc[dns_dnstapcounter_max]; static const char *gluecachestats_xmldesc[dns_gluecachestatscounter_max]; +static const char *queryrttinstats_xmldesc[dns_queryrttcounter_in_max]; +static const char *queryrttoutstats_xmldesc[dns_queryrttcounter_in_max]; #else /* if defined(EXTENDED_STATS) */ #define nsstats_xmldesc NULL #define resstats_xmldesc NULL @@ -203,6 +205,8 @@ static int tcpinsizestats_index[dns_sizecounter_in_max]; static int tcpoutsizestats_index[dns_sizecounter_out_max]; static int dnstapstats_index[dns_dnstapcounter_max]; static int gluecachestats_index[dns_gluecachestatscounter_max]; +static int queryrttinstats_index[dns_queryrttcounter_in_max]; +static int queryrttoutstats_index[dns_queryrttcounter_out_max]; static void set_desc(int counter, int maxcounter, const char *fdesc, const char **fdescs, @@ -223,7 +227,7 @@ set_desc(int counter, int maxcounter, const char *fdesc, const char **fdescs, } static const char * -get_histo_desc(const char *prefix, int i, int inf, bool ext) { +get_sizehisto_desc(const char *prefix, int i, int inf, bool ext) { static char buf[(DNS_SIZEHISTO_MAXIN + DNS_SIZEHISTO_MAXOUT) * 80]; static size_t used = 0; char *desc = buf + used; @@ -246,6 +250,51 @@ get_histo_desc(const char *prefix, int i, int inf, bool ext) { return desc; } +#if defined(EXTENDED_STATS) +static const char * +get_rtthisto_desc(const char *prefix, unsigned int i, unsigned int inf, + uint64_t min, uint64_t max, bool ext) { + static char buf[DNS_RTTHISTO_MAX * 80]; + static size_t used = 0; + char *desc = buf + used; + size_t space = sizeof(buf) - used; + int len = 0; + + if (!ext && i < inf) { + if (min == max) { + if (min == 0) { + len = snprintf(desc, space, "%s ~0 ms", prefix); + } else { + len = snprintf(desc, space, "%s %" PRIu64 " ms", + prefix, min); + } + } else { + len = snprintf(desc, space, + "%s %" PRIu64 "-%" PRIu64 " ms", prefix, + min, max); + } + } else if (!ext && i >= inf) { + len = snprintf(desc, space, "%s %" PRIu64 "+ ms", prefix, min); + } else if (ext && i < inf) { + if (min == max) { + if (min == 0) { + len = snprintf(desc, space, "~0"); + } else { + len = snprintf(desc, space, "%" PRIu64, min); + } + } else { + len = snprintf(desc, space, "%" PRIu64 "-%" PRIu64, min, + max); + } + } else if (ext && i >= inf) { + len = snprintf(desc, space, "%" PRIu64 "+", min); + } + INSIST(0 < len && (size_t)len < space); + used += len + 1; + return desc; +} +#endif /* if defined(EXTENDED_STATS) */ + static void init_desc(void) { int i; @@ -742,11 +791,11 @@ init_desc(void) { for (i = 0; i < DNS_SIZEHISTO_MAXOUT; i++) { udpoutsizestats_index[i] = i; tcpoutsizestats_index[i] = i; - udpoutsizestats_desc[i] = get_histo_desc( + udpoutsizestats_desc[i] = get_sizehisto_desc( "responses sent", i, DNS_SIZEHISTO_MAXOUT, false); tcpoutsizestats_desc[i] = udpoutsizestats_desc[i]; #if defined(EXTENDED_STATS) - udpoutsizestats_xmldesc[i] = get_histo_desc( + udpoutsizestats_xmldesc[i] = get_sizehisto_desc( "responses sent", i, DNS_SIZEHISTO_MAXOUT, true); tcpoutsizestats_xmldesc[i] = udpoutsizestats_xmldesc[i]; #endif /* if defined(EXTENDED_STATS) */ @@ -755,7 +804,7 @@ init_desc(void) { for (i = 0; i <= DNS_SIZEHISTO_MAXIN; i++) { udpinsizestats_index[i] = i; tcpinsizestats_index[i] = i; - udpinsizestats_desc[i] = get_histo_desc( + udpinsizestats_desc[i] = get_sizehisto_desc( "requests received", i, DNS_SIZEHISTO_MAXIN, false); tcpinsizestats_desc[i] = udpinsizestats_desc[i]; #if defined(EXTENDED_STATS) @@ -764,10 +813,25 @@ init_desc(void) { tcpinsizestats_xmldesc[i] = tcpoutsizestats_xmldesc[i]; } else { udpinsizestats_xmldesc[i] = - get_histo_desc("requests received", i, - DNS_SIZEHISTO_MAXIN, true); + get_sizehisto_desc("requests received", i, + DNS_SIZEHISTO_MAXIN, true); tcpinsizestats_xmldesc[i] = udpinsizestats_xmldesc[i]; } +#endif /* if defined(EXTENDED_STATS) */ + } + + for (i = 0; i <= DNS_RTTHISTO_MAX; i++) { + queryrttinstats_index[i] = i; + queryrttoutstats_index[i] = i; + +#if defined(EXTENDED_STATS) + /* + * The descriptions are filled in during dumping (only once), + * because we don't have the isc_histomulti_t objects here + * to determine their non-linear minimum and maximum values. + */ + queryrttinstats_xmldesc[i] = NULL; + queryrttoutstats_xmldesc[i] = NULL; #endif /* if defined(EXTENDED_STATS) */ } } @@ -810,13 +874,26 @@ dump_stats(isc_stats_t *stats, isc_statsformat_t type, void *arg, #if defined(EXTENDED_STATS) static isc_result_t dump_histo(isc_histomulti_t *hm, isc_statsformat_t type, void *arg, - const char *category, const char **desc, int ncounters, int *indices, - uint64_t *values, int options) { + const char *category, const char **desc, const char *desc_prefix, + int ncounters, int *indices, uint64_t *values, int options) { isc_histo_t *hg = NULL; + uint64_t min, max; isc_histomulti_merge(&hg, hm); for (int i = 0; i < ncounters; i++) { - isc_histo_get(hg, i, NULL, NULL, &values[i]); + isc_histo_get(hg, i, &min, &max, &values[i]); + + /* + * RTT descriptions are generated during the first call of + * this function for the corresponding histogram, the other + * descriptions are pregenerated and the caller shouldn't + * provide a prefix. + */ + if (desc[i] == NULL && desc_prefix != NULL) { + desc[i] = get_rtthisto_desc(desc_prefix, i, + DNS_RTTHISTO_MAX, min, max, + true); + } } isc_histo_destroy(&hg); @@ -1743,6 +1820,8 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, uint64_t udpoutsizestat_values[DNS_SIZEHISTO_MAXOUT + 1]; uint64_t tcpinsizestat_values[DNS_SIZEHISTO_MAXIN + 1]; uint64_t tcpoutsizestat_values[DNS_SIZEHISTO_MAXOUT + 1]; + uint64_t queryrttinstat_values[DNS_RTTHISTO_MAX + 1]; + uint64_t queryrttoutstat_values[DNS_RTTHISTO_MAX + 1]; #ifdef HAVE_DNSTAP uint64_t dnstapstat_values[dns_dnstapcounter_max]; #endif /* ifdef HAVE_DNSTAP */ @@ -1899,7 +1978,7 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, ISC_XMLCHAR "request-size")); CHECK(dump_histo(server->sctx->udpinstats4, isc_statsformat_xml, - writer, NULL, udpinsizestats_xmldesc, + writer, NULL, udpinsizestats_xmldesc, NULL, dns_sizecounter_in_max, udpinsizestats_index, udpinsizestat_values, 0)); @@ -1909,10 +1988,11 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", ISC_XMLCHAR "response-size")); - CHECK(dump_histo( - server->sctx->udpoutstats4, isc_statsformat_xml, writer, - NULL, udpoutsizestats_xmldesc, dns_sizecounter_out_max, - udpoutsizestats_index, udpoutsizestat_values, 0)); + CHECK(dump_histo(server->sctx->udpoutstats4, + isc_statsformat_xml, writer, NULL, + udpoutsizestats_xmldesc, NULL, + dns_sizecounter_out_max, udpoutsizestats_index, + udpoutsizestat_values, 0)); TRY0(xmlTextWriterEndElement(writer)); /* */ TRY0(xmlTextWriterEndElement(writer)); /* */ @@ -1923,7 +2003,7 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, ISC_XMLCHAR "request-size")); CHECK(dump_histo(server->sctx->tcpinstats4, isc_statsformat_xml, - writer, NULL, tcpinsizestats_xmldesc, + writer, NULL, tcpinsizestats_xmldesc, NULL, dns_sizecounter_in_max, tcpinsizestats_index, tcpinsizestat_values, 0)); @@ -1932,10 +2012,11 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", ISC_XMLCHAR "response-size")); - CHECK(dump_histo( - server->sctx->tcpoutstats4, isc_statsformat_xml, writer, - NULL, tcpoutsizestats_xmldesc, dns_sizecounter_out_max, - tcpoutsizestats_index, tcpoutsizestat_values, 0)); + CHECK(dump_histo(server->sctx->tcpoutstats4, + isc_statsformat_xml, writer, NULL, + tcpoutsizestats_xmldesc, NULL, + dns_sizecounter_out_max, tcpoutsizestats_index, + tcpoutsizestat_values, 0)); TRY0(xmlTextWriterEndElement(writer)); /* */ TRY0(xmlTextWriterEndElement(writer)); /* */ @@ -1948,7 +2029,7 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, ISC_XMLCHAR "request-size")); CHECK(dump_histo(server->sctx->udpinstats6, isc_statsformat_xml, - writer, NULL, udpinsizestats_xmldesc, + writer, NULL, udpinsizestats_xmldesc, NULL, dns_sizecounter_in_max, udpinsizestats_index, udpinsizestat_values, 0)); @@ -1958,10 +2039,11 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", ISC_XMLCHAR "response-size")); - CHECK(dump_histo( - server->sctx->udpoutstats6, isc_statsformat_xml, writer, - NULL, udpoutsizestats_xmldesc, dns_sizecounter_out_max, - udpoutsizestats_index, udpoutsizestat_values, 0)); + CHECK(dump_histo(server->sctx->udpoutstats6, + isc_statsformat_xml, writer, NULL, + udpoutsizestats_xmldesc, NULL, + dns_sizecounter_out_max, udpoutsizestats_index, + udpoutsizestat_values, 0)); TRY0(xmlTextWriterEndElement(writer)); /* */ TRY0(xmlTextWriterEndElement(writer)); /* */ @@ -1972,7 +2054,7 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, ISC_XMLCHAR "request-size")); CHECK(dump_histo(server->sctx->tcpinstats6, isc_statsformat_xml, - writer, NULL, tcpinsizestats_xmldesc, + writer, NULL, tcpinsizestats_xmldesc, NULL, dns_sizecounter_in_max, tcpinsizestats_index, tcpinsizestat_values, 0)); @@ -1982,10 +2064,11 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", ISC_XMLCHAR "response-size")); - CHECK(dump_histo( - server->sctx->tcpoutstats6, isc_statsformat_xml, writer, - NULL, tcpoutsizestats_xmldesc, dns_sizecounter_out_max, - tcpoutsizestats_index, tcpoutsizestat_values, 0)); + CHECK(dump_histo(server->sctx->tcpoutstats6, + isc_statsformat_xml, writer, NULL, + tcpoutsizestats_xmldesc, NULL, + dns_sizecounter_out_max, tcpoutsizestats_index, + tcpoutsizestat_values, 0)); TRY0(xmlTextWriterEndElement(writer)); /* */ TRY0(xmlTextWriterEndElement(writer)); /* */ @@ -2005,6 +2088,7 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, isc_stats_t *istats = NULL; dns_stats_t *dstats = NULL; dns_adb_t *adb = NULL; + isc_histomulti_t *hmpin = NULL, *hmpout = NULL; TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "view")); TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", @@ -2098,6 +2182,40 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(dns_cache_renderxml(view->cache, writer)); TRY0(xmlTextWriterEndElement(writer)); /* */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "rtt")); + + dns_resolver_getqueryrttstats(view->resolver, &hmpin, &hmpout); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR "in-queries-rtt")); + if (hmpin != NULL) { + CHECK(dump_histo(hmpin, isc_statsformat_xml, writer, + NULL, queryrttinstats_xmldesc, + "RTT (in)", dns_queryrttcounter_in_max, + queryrttinstats_index, + queryrttinstat_values, 0)); + isc_histomulti_detach(&hmpin); + } + TRY0(xmlTextWriterEndElement(writer)); /* */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counters")); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "type", + ISC_XMLCHAR + "out-queries-rtt")); + if (hmpout != NULL) { + CHECK(dump_histo(hmpout, isc_statsformat_xml, writer, + NULL, queryrttoutstats_xmldesc, + "RTT (out)", + dns_queryrttcounter_out_max, + queryrttoutstats_index, + queryrttoutstat_values, 0)); + isc_histomulti_detach(&hmpout); + } + TRY0(xmlTextWriterEndElement(writer)); /* */ + + TRY0(xmlTextWriterEndElement(writer)); /* */ + TRY0(xmlTextWriterEndElement(writer)); /* view */ view = ISC_LIST_NEXT(view, link); @@ -2796,6 +2914,8 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, uint64_t udpoutsizestat_values[dns_sizecounter_out_max]; uint64_t tcpinsizestat_values[dns_sizecounter_in_max]; uint64_t tcpoutsizestat_values[dns_sizecounter_out_max]; + uint64_t queryrttinstat_values[DNS_RTTHISTO_MAX + 1]; + uint64_t queryrttoutstat_values[DNS_RTTHISTO_MAX + 1]; #ifdef HAVE_DNSTAP uint64_t dnstapstat_values[dns_dnstapcounter_max]; #endif /* ifdef HAVE_DNSTAP */ @@ -3005,6 +3125,7 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, ISC_LIST_FOREACH(server->viewlist, view, link) { json_object *za, *xa, *v = json_object_new_object(); dns_adb_t *adb = NULL; + isc_histomulti_t *hmpin = NULL, *hmpout = NULL; CHECKMEM(v); json_object_object_add(viewlist, view->name, v); @@ -3148,6 +3269,45 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, json_object_object_add(res, "adb", counters); } + + dns_resolver_getqueryrttstats(view->resolver, + &hmpin, &hmpout); + if (hmpin != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + CHECK(dump_histo( + hmpin, isc_statsformat_json, + counters, NULL, + queryrttinstats_xmldesc, + "RTT (in)", + dns_queryrttcounter_in_max, + queryrttinstats_index, + queryrttinstat_values, 0)); + isc_histomulti_detach(&hmpin); + + json_object_object_add(res, + "in-queries-rtt", + counters); + } + if (hmpout != NULL) { + counters = json_object_new_object(); + CHECKMEM(counters); + + CHECK(dump_histo( + hmpout, isc_statsformat_json, + counters, NULL, + queryrttoutstats_xmldesc, + "RTT (out)", + dns_queryrttcounter_out_max, + queryrttoutstats_index, + queryrttoutstat_values, 0)); + isc_histomulti_detach(&hmpout); + + json_object_object_add( + res, "out-queries-rtt", + counters); + } } } } @@ -3219,49 +3379,49 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, CHECK(dump_histo(server->sctx->udpinstats4, isc_statsformat_json, udpreq4, NULL, - udpinsizestats_xmldesc, dns_sizecounter_in_max, - udpinsizestats_index, udpinsizestat_values, - 0)); + udpinsizestats_xmldesc, NULL, + dns_sizecounter_in_max, udpinsizestats_index, + udpinsizestat_values, 0)); CHECK(dump_histo(server->sctx->udpoutstats4, isc_statsformat_json, udpresp4, NULL, - udpoutsizestats_xmldesc, + udpoutsizestats_xmldesc, NULL, dns_sizecounter_out_max, udpoutsizestats_index, udpoutsizestat_values, 0)); CHECK(dump_histo(server->sctx->tcpinstats4, isc_statsformat_json, tcpreq4, NULL, - tcpinsizestats_xmldesc, dns_sizecounter_in_max, - tcpinsizestats_index, tcpinsizestat_values, - 0)); + tcpinsizestats_xmldesc, NULL, + dns_sizecounter_in_max, tcpinsizestats_index, + tcpinsizestat_values, 0)); CHECK(dump_histo(server->sctx->tcpoutstats4, isc_statsformat_json, tcpresp4, NULL, - tcpoutsizestats_xmldesc, + tcpoutsizestats_xmldesc, NULL, dns_sizecounter_out_max, tcpoutsizestats_index, tcpoutsizestat_values, 0)); CHECK(dump_histo(server->sctx->udpinstats6, isc_statsformat_json, udpreq6, NULL, - udpinsizestats_xmldesc, dns_sizecounter_in_max, - udpinsizestats_index, udpinsizestat_values, - 0)); + udpinsizestats_xmldesc, NULL, + dns_sizecounter_in_max, udpinsizestats_index, + udpinsizestat_values, 0)); CHECK(dump_histo(server->sctx->udpoutstats6, isc_statsformat_json, udpresp6, NULL, - udpoutsizestats_xmldesc, + udpoutsizestats_xmldesc, NULL, dns_sizecounter_out_max, udpoutsizestats_index, udpoutsizestat_values, 0)); CHECK(dump_histo(server->sctx->tcpinstats6, isc_statsformat_json, tcpreq6, NULL, - tcpinsizestats_xmldesc, dns_sizecounter_in_max, - tcpinsizestats_index, tcpinsizestat_values, - 0)); + tcpinsizestats_xmldesc, NULL, + dns_sizecounter_in_max, tcpinsizestats_index, + tcpinsizestat_values, 0)); CHECK(dump_histo(server->sctx->tcpoutstats6, isc_statsformat_json, tcpresp6, NULL, - tcpoutsizestats_xmldesc, + tcpoutsizestats_xmldesc, NULL, dns_sizecounter_out_max, tcpoutsizestats_index, tcpoutsizestat_values, 0)); From 165a776137d02a688addf403bef452f8276315c5 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Fri, 16 Jan 2026 14:02:54 +0000 Subject: [PATCH 6/7] Add RTT statistics tests both for XML and JSON outputs Add a resolver instance "ns4" in the statschannel test and a "ans5" instance which adds latency to the queries delegeated to it from the resolver. Make queries which add latency, and compare the expected values to the values received from the statistics channel. --- bin/tests/system/statschannel/ans5/ans.py | 56 +++++++++++++++++++ bin/tests/system/statschannel/generic.py | 35 ++++++++++++ bin/tests/system/statschannel/ns4/example2.db | 28 ++++++++++ .../system/statschannel/ns4/named.conf.j2 | 44 +++++++++++++++ bin/tests/system/statschannel/tests_json.py | 19 +++++++ .../statschannel/tests_sh_statschannel.py | 1 + bin/tests/system/statschannel/tests_xml.py | 35 ++++++++++++ 7 files changed, 218 insertions(+) create mode 100644 bin/tests/system/statschannel/ans5/ans.py create mode 100644 bin/tests/system/statschannel/ns4/example2.db create mode 100644 bin/tests/system/statschannel/ns4/named.conf.j2 diff --git a/bin/tests/system/statschannel/ans5/ans.py b/bin/tests/system/statschannel/ans5/ans.py new file mode 100644 index 0000000000..6f7dcc2a4a --- /dev/null +++ b/bin/tests/system/statschannel/ans5/ans.py @@ -0,0 +1,56 @@ +""" +Copyright (C) Internet Systems Consortium, Inc. ("ISC") + +SPDX-License-Identifier: MPL-2.0 + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, you can obtain one at https://mozilla.org/MPL/2.0/. + +See the COPYRIGHT file distributed with this work for additional +information regarding copyright ownership. +""" + +from collections.abc import AsyncGenerator + +import dns.rcode +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + ControllableAsyncDnsServer, + DnsResponseSend, + QueryContext, + ResponseHandler, +) + + +class DelayedAddressAnswerHandler(ResponseHandler): + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + if qctx.qtype in (dns.rdatatype.A, dns.rdatatype.AAAA): + addr = "192.0.2.1" if qctx.qtype == dns.rdatatype.A else "2001:db8:beef::1" + rrset = dns.rrset.from_text(qctx.qname, 300, qctx.qclass, qctx.qtype, addr) + qctx.response.answer.append(rrset) + + delay = 0 + if ( + len(qctx.qname.labels) >= 2 + and qctx.qname.labels[1] == b"latency" + and qctx.qname.labels[0].isdigit() + ): + delay = int(qctx.qname.labels[0]) / 1000 + yield DnsResponseSend(qctx.response, delay=delay) + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + server.install_response_handler(DelayedAddressAnswerHandler()) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/statschannel/generic.py b/bin/tests/system/statschannel/generic.py index 0b0ee82de0..02a7eea383 100644 --- a/bin/tests/system/statschannel/generic.py +++ b/bin/tests/system/statschannel/generic.py @@ -58,6 +58,11 @@ def check_zone_timers(loaded, expires, refresh, loaded_exp): check_loaded(loaded, loaded_exp, now) +def check_rtt(rtt, rtt_expected): + for val in rtt_expected: + assert rtt[val[0]] == val[1] + + # # The output is gibberish, but at least make sure it does not crash. # @@ -225,3 +230,33 @@ def test_traffic(fetch_traffic, **kwargs): data = fetch_traffic(statsip, statsport) check_traffic(data, exp) + + +def test_rtt(fetch_views, **kwargs): + statsip = kwargs["statsip"] + statsport = kwargs["statsport"] + + # auth query, 0 delay is expected, only for "in" + msg = create_msg("a.example2.", "TXT") + ans = isctest.query.tcp(msg, statsip, attempts=1) + isctest.check.noerror(ans) + + # resolver query with a 530ms delay for both "in" and "out" + msg = create_msg("530.latency.example2.", "A") + ans = isctest.query.tcp(msg, statsip, attempts=1) + isctest.check.noerror(ans) + + # resolver query with a 540ms delay for both "in" and "out" + msg = create_msg("540.latency.example2.", "A") + ans = isctest.query.tcp(msg, statsip, attempts=1) + isctest.check.noerror(ans) + + # resolver query with a 730ms delay for both "in" and "out" + msg = create_msg("730.latency.example2.", "A") + ans = isctest.query.tcp(msg, statsip, attempts=1) + isctest.check.noerror(ans) + + data = fetch_views(statsip, statsport) + + check_rtt(data["in-queries-rtt"], [["~0", 1], ["512-575", 2], ["704-767", 1]]) + check_rtt(data["out-queries-rtt"], [["512-575", 2], ["704-767", 1]]) diff --git a/bin/tests/system/statschannel/ns4/example2.db b/bin/tests/system/statschannel/ns4/example2.db new file mode 100644 index 0000000000..2d623eb8cd --- /dev/null +++ b/bin/tests/system/statschannel/ns4/example2.db @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$ORIGIN . +$TTL 300 ; 5 minutes +example2 IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example2. NS ns4.example2. +ns4.example2. A 10.53.0.4 + +$ORIGIN example2. +a A 10.0.0.1 + +latency NS ns5.example2. +ns5.example2. A 10.53.0.5 diff --git a/bin/tests/system/statschannel/ns4/named.conf.j2 b/bin/tests/system/statschannel/ns4/named.conf.j2 new file mode 100644 index 0000000000..4e2c17ee2b --- /dev/null +++ b/bin/tests/system/statschannel/ns4/named.conf.j2 @@ -0,0 +1,44 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + notify no; + minimal-responses no; + version none; // make statistics independent of the version number +}; + +statistics-channels { inet 10.53.0.4 port @EXTRAPORT1@ allow { localhost; }; }; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example2" { + type primary; + file "example2.db"; + allow-transfer { any; }; +}; diff --git a/bin/tests/system/statschannel/tests_json.py b/bin/tests/system/statschannel/tests_json.py index 30348f4e69..f1da9e9c82 100755 --- a/bin/tests/system/statschannel/tests_json.py +++ b/bin/tests/system/statschannel/tests_json.py @@ -24,6 +24,7 @@ pytestmark = [ isctest.mark.with_json_c, pytest.mark.extra_artifacts( [ + "ans5/ans.run", "ns2/*.jnl", "ns2/*.signed", "ns2/dsset-*", @@ -62,6 +63,19 @@ def fetch_traffic_json(statsip, statsport): return data["traffic"] +def fetch_rtt_json(statsip, statsport): + r = requests.get(f"http://{statsip}:{statsport}/json/v1", timeout=600) + assert r.status_code == 200 + + views = r.json()["views"] + data = { + "in-queries-rtt": views["_default"]["resolver"]["in-queries-rtt"], + "out-queries-rtt": views["_default"]["resolver"]["out-queries-rtt"], + } + + return data + + def load_timers_json(zone, primary=True): name = zone["name"] @@ -119,3 +133,8 @@ def test_zone_with_many_keys_json(statsport): @pytest.mark.flaky(max_runs=2) def test_traffic_json(statsport): generic.test_traffic(fetch_traffic_json, statsip="10.53.0.2", statsport=statsport) + + +@pytest.mark.flaky(max_runs=2) +def test_rtt_json(statsport): + generic.test_rtt(fetch_rtt_json, statsip="10.53.0.4", statsport=statsport) diff --git a/bin/tests/system/statschannel/tests_sh_statschannel.py b/bin/tests/system/statschannel/tests_sh_statschannel.py index 7b5788010d..473646cf1b 100644 --- a/bin/tests/system/statschannel/tests_sh_statschannel.py +++ b/bin/tests/system/statschannel/tests_sh_statschannel.py @@ -14,6 +14,7 @@ import pytest pytestmark = pytest.mark.extra_artifacts( [ "K*", + "ans5/ans.run", "bind9.xsl.1", "bind9.xsl.2", "compressed.headers", diff --git a/bin/tests/system/statschannel/tests_xml.py b/bin/tests/system/statschannel/tests_xml.py index 0707681de7..133c2f0963 100755 --- a/bin/tests/system/statschannel/tests_xml.py +++ b/bin/tests/system/statschannel/tests_xml.py @@ -27,6 +27,7 @@ pytestmark = [ pytest.mark.extra_artifacts( [ "ns2/K*", + "ans5/ans.run", "ns2/*.jnl", "ns2/*.signed", "ns2/dsset-*", @@ -91,6 +92,35 @@ def fetch_traffic_xml(statsip, statsport): return traffic +def fetch_rtt_xml(statsip, statsport): + def load_counters(data): + out = {} + for counter in data.findall("counter"): + out[counter.attrib["name"]] = int(counter.text) + + return out + + r = requests.get(f"http://{statsip}:{statsport}/xml/v3", timeout=600) + assert r.status_code == 200 + + root = ET.fromstring(r.text) + + default_view = None + for view in root.find("views").iter("view"): + if view.attrib["name"] == "_default": + default_view = view + break + assert default_view is not None + + rtt = {} + for counters in default_view.find("rtt").findall("counters"): + key = counters.attrib["type"] + values = load_counters(counters) + rtt[key] = values + + return rtt + + def load_timers_xml(zone, primary=True): name = zone.attrib["name"] @@ -149,3 +179,8 @@ def test_zone_with_many_keys_xml(statsport): @pytest.mark.flaky(max_runs=2) def test_traffic_xml(statsport): generic.test_traffic(fetch_traffic_xml, statsip="10.53.0.2", statsport=statsport) + + +@pytest.mark.flaky(max_runs=2) +def test_rtt_xml(statsport): + generic.test_rtt(fetch_rtt_xml, statsip="10.53.0.4", statsport=statsport) From 38841ba78b21b40f4929bc750d7af2fcd4712a9b Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 29 Jan 2026 13:30:31 +0000 Subject: [PATCH 7/7] Document response time RTT counetrs --- doc/arm/reference.rst | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 6e14b12959..341f1b3483 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -8468,9 +8468,6 @@ Resolver Statistics Counters ``ValFail`` This indicates the number of failed DNSSEC validations. -``QryRTTnn`` - This provides a frequency table on query round-trip times (RTTs). Each ``nn`` specifies the corresponding frequency. In the sequence of ``nn_1``, ``nn_2``, ..., ``nn_m``, the value of ``nn_i`` is the number of queries whose RTTs are between ``nn_(i-1)`` (inclusive) and ``nn_i`` (exclusive) milliseconds. For the sake of convenience, we define ``nn_0`` to be 0. The last entry should be represented as ``nn_m+``, which means the number of queries whose RTTs are equal to or greater than ``nn_m`` milliseconds. - ``NumFetch`` This indicates the number of active fetches. @@ -8516,6 +8513,21 @@ Resolver Statistics Counters ``Priming`` This indicates the number of priming fetches performed by the resolver. +.. _resolver_rtt_stats: + +Resolver Queries Response Time counters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:iscman:`named` provides query response round-trip time (RTT) counters +(histogram) both for incoming and outgoing queries in milliseconds. Response +times which are lower than 1ms are calculated under the ``~0`` counter. +Response times from 1ms to 15ms are calculated under their own counters, e.g. a +response that took 5ms to complete is calculated under the ``5`` counter. +Response times starting from 16ms are calculated under the ``MinMS-MaxMS`` +(inclusive) range counters. For example, a response that took 18ms to complete +is calculated under the ``18-19`` counter, and a response that took 550ms to +complete is calculated under the ``512-575`` counter. + .. _socket_stats: Socket I/O Statistics Counters