diff options
author | Daniel Salzman <daniel.salzman@nic.cz> | 2024-03-19 14:04:29 +0100 |
---|---|---|
committer | Daniel Salzman <daniel.salzman@nic.cz> | 2024-03-22 13:54:34 +0100 |
commit | bbfa41735192945817057521013822b9f36e1eb5 (patch) | |
tree | 41529b331e53e8e0796b2b69cbc61129a6367da9 | |
parent | rrl: switch #include to relative paths (diff) | |
download | knot-bbfa41735192945817057521013822b9f36e1eb5.tar.xz knot-bbfa41735192945817057521013822b9f36e1eb5.zip |
mod-rrl: code cleanup
-rw-r--r-- | Knot.files | 5 | ||||
-rw-r--r-- | src/knot/modules/rrl/Makefile.inc | 2 | ||||
-rw-r--r-- | src/knot/modules/rrl/functions.c | 38 | ||||
-rw-r--r-- | src/knot/modules/rrl/functions.h | 36 | ||||
-rw-r--r-- | src/knot/modules/rrl/rrl.c | 78 | ||||
-rw-r--r-- | tests/modules/test_rrl.c | 57 |
6 files changed, 71 insertions, 145 deletions
diff --git a/Knot.files b/Knot.files index f2b61b530..279ca4c57 100644 --- a/Knot.files +++ b/Knot.files @@ -285,6 +285,11 @@ src/knot/modules/probe/probe.c src/knot/modules/queryacl/queryacl.c src/knot/modules/rrl/functions.c src/knot/modules/rrl/functions.h +src/knot/modules/rrl/kru-avx2.c +src/knot/modules/rrl/kru-decay.inc.c +src/knot/modules/rrl/kru-generic.c +src/knot/modules/rrl/kru.h +src/knot/modules/rrl/kru.inc.c src/knot/modules/rrl/rrl.c src/knot/modules/stats/stats.c src/knot/modules/synthrecord/synthrecord.c diff --git a/src/knot/modules/rrl/Makefile.inc b/src/knot/modules/rrl/Makefile.inc index c0d347d27..9a26b0571 100644 --- a/src/knot/modules/rrl/Makefile.inc +++ b/src/knot/modules/rrl/Makefile.inc @@ -4,6 +4,8 @@ knot_modules_rrl_la_SOURCES = knot/modules/rrl/rrl.c \ knot/modules/rrl/kru-generic.c \ knot/modules/rrl/kru-avx2.c \ knot/modules/rrl/kru.h +noinst_HEADERS = knot/modules/rrl/kru-decay.inc.c \ + knot/modules/rrl/kru.inc.c EXTRA_DIST += knot/modules/rrl/rrl.rst if STATIC_MODULE_rrl diff --git a/src/knot/modules/rrl/functions.c b/src/knot/modules/rrl/functions.c index 393ffbf4f..1329909f2 100644 --- a/src/knot/modules/rrl/functions.c +++ b/src/knot/modules/rrl/functions.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,16 +14,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <assert.h> -#include <time.h> #include <stdatomic.h> +#include <time.h> #include "knot/modules/rrl/functions.h" +#include "knot/modules/rrl/kru.h" #include "contrib/musl/inet_ntop.h" -#include "contrib/openbsd/strlcat.h" #include "contrib/sockaddr.h" #include "contrib/time.h" -#include "libdnssec/error.h" #include "libdnssec/random.h" /* CIDR block prefix lengths for v4/v6 */ @@ -39,6 +37,10 @@ #define RRL_V6_PREFIXES_CNT (sizeof(RRL_V6_PREFIXES) / sizeof(*RRL_V6_PREFIXES)) #define RRL_MAX_PREFIXES_CNT ((RRL_V4_PREFIXES_CNT > RRL_V6_PREFIXES_CNT) ? RRL_V4_PREFIXES_CNT : RRL_V6_PREFIXES_CNT) +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +#endif + struct rrl_table { kru_price_t v4_prices[RRL_V4_PREFIXES_CNT]; kru_price_t v6_prices[RRL_V6_PREFIXES_CNT]; @@ -49,6 +51,8 @@ struct rrl_table { static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage *ss) { + assert(ss); + const void *addr; if (ss->ss_family == AF_INET6) { @@ -64,7 +68,7 @@ static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage * static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, const uint8_t prefix) { - if (mod == NULL || ss == NULL) { + if (mod == NULL) { return; } @@ -76,6 +80,10 @@ static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period) { + if (size == 0 || instant_limit == 0 || rate_limit == 0) { + return NULL; + } + size--; size_t capacity_log = 1; while (size >>= 1) capacity_log++; @@ -104,20 +112,17 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit rrl->log_period = log_period; - { - struct timespec now_ts = {0}; - clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); - uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; - rrl->log_time = now - log_period; - } + struct timespec now_ts = {0}; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); + uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; + rrl->log_time = now - log_period; return rrl; } -int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *zone, knotd_mod_t *mod) +int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod) { - if (!rrl || !req || !remote) { + if (rrl == NULL || remote == NULL) { return KNOT_EINVAL; } @@ -144,7 +149,8 @@ int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, uint32_t log_time_orig = atomic_load_explicit(&rrl->log_time, memory_order_relaxed); if (rrl->log_period && limited_prefix && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) { do { - if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now, memory_order_relaxed, memory_order_relaxed)) { + if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now, + memory_order_relaxed, memory_order_relaxed)) { rrl_log_limited(mod, remote, limited_prefix); break; } diff --git a/src/knot/modules/rrl/functions.h b/src/knot/modules/rrl/functions.h index c3b1cfaea..934df369c 100644 --- a/src/knot/modules/rrl/functions.h +++ b/src/knot/modules/rrl/functions.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,37 +17,20 @@ #pragma once #include <stdint.h> -#include <pthread.h> #include <sys/socket.h> -#include "libknot/libknot.h" #include "knot/include/module.h" -#include "contrib/openbsd/siphash.h" -#include "knot/modules/rrl/kru.h" - typedef struct rrl_table rrl_table_t; -/*! \brief RRL request flags. */ -typedef enum { - RRL_REQ_NOFLAG = 0 << 0, /*!< No flags. */ - RRL_REQ_WILDCARD = 1 << 1 /*!< Query to wildcard name. */ -} rrl_req_flag_t; - -/*! - * \brief RRL request descriptor. - */ -typedef struct { - const uint8_t *wire; - uint16_t len; - rrl_req_flag_t flags; - knot_pkt_t *query; -} rrl_req_t; - /*! * \brief Create a RRL table. + * * \param size Fixed table size. * \param rate Rate (in pkts/sec). + * \param instant_limit Instant limit (number). + * \param log_period If nonzero, maximum logging period (in milliseconds). + * * \return created table or NULL. */ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period); @@ -57,24 +40,25 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit * * \param rrl RRL table. * \param remote Source address. - * \param req RRL request (containing resp., flags and question). - * \param zone Zone name related to the response (or NULL). * \param mod Query module (needed for logging). + * * \retval KNOT_EOK if passed. * \retval KNOT_ELIMIT when the limit is reached. */ -int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, - rrl_req_t *req, const knot_dname_t *zone, knotd_mod_t *mod); +int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod); /*! * \brief Roll a dice whether answer slips or not. + * * \param n_slip Number represents every Nth answer that is slipped. + * * \return true or false */ bool rrl_slip_roll(int n_slip); /*! * \brief Destroy RRL table. + * * \param rrl RRL table. */ void rrl_destroy(rrl_table_t *rrl); diff --git a/src/knot/modules/rrl/rrl.c b/src/knot/modules/rrl/rrl.c index 662fd894e..2c11ee80d 100644 --- a/src/knot/modules/rrl/rrl.c +++ b/src/knot/modules/rrl/rrl.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,11 +15,10 @@ */ #include "knot/include/module.h" -#include "knot/nameserver/process_query.h" // Dependency on qdata->extra! #include "knot/modules/rrl/functions.h" #define MOD_RATE_LIMIT "\x0A""rate-limit" -#define MOD_INSTANT_LIMIT "\x0D""instant-limit" +#define MOD_INSTANT_LIMIT "\x0D""instant-limit" #define MOD_SLIP "\x04""slip" #define MOD_TBL_SIZE "\x0A""table-size" #define MOD_WHITELIST "\x09""whitelist" @@ -44,7 +43,7 @@ int rrl_conf_check(knotd_conf_check_args_t *args) return KNOT_EINVAL; } if (rate_limit.single.integer > 1000ll * instant_limit.single.integer) { - args->err_str = "rate limit per msec is higher than instant limit"; + args->err_str = "rate limit per millisecond is higher than instant limit"; return KNOT_EINVAL; } @@ -57,32 +56,6 @@ typedef struct { knotd_conf_t whitelist; } rrl_ctx_t; -static const knot_dname_t *name_from_rrsig(const knot_rrset_t *rr) -{ - if (rr == NULL) { - return NULL; - } - if (rr->type != KNOT_RRTYPE_RRSIG) { - return NULL; - } - - // This is a signature. - return knot_rrsig_signer_name(rr->rrs.rdata); -} - -static const knot_dname_t *name_from_authrr(const knot_rrset_t *rr) -{ - if (rr == NULL) { - return NULL; - } - if (rr->type != KNOT_RRTYPE_NS && rr->type != KNOT_RRTYPE_SOA) { - return NULL; - } - - // This is a valid authority RR. - return rr->owner; -} - static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod) { @@ -105,41 +78,7 @@ static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt, return state; } - rrl_req_t req = { - .wire = pkt->wire, - .query = qdata->query - }; - - if (!EMPTY_LIST(qdata->extra->wildcards)) { - req.flags = RRL_REQ_WILDCARD; - } - - // Take the zone name if known. - const knot_dname_t *zone_name = knotd_qdata_zone_name(qdata); - - // Take the signer name as zone name if there is an RRSIG. - if (zone_name == NULL) { - const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER); - for (int i = 0; i < ans->count; i++) { - zone_name = name_from_rrsig(knot_pkt_rr(ans, i)); - if (zone_name != NULL) { - break; - } - } - } - - // Take the NS or SOA owner name if there is no RRSIG. - if (zone_name == NULL) { - const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY); - for (int i = 0; i < auth->count; i++) { - zone_name = name_from_authrr(knot_pkt_rr(auth, i)); - if (zone_name != NULL) { - break; - } - } - } - - if (rrl_query(ctx->rrl, knotd_qdata_remote_addr(qdata), &req, zone_name, mod) == KNOT_EOK) { + if (rrl_query(ctx->rrl, knotd_qdata_remote_addr(qdata), mod) == KNOT_EOK) { // Rate limiting not applied. return state; } @@ -166,13 +105,11 @@ static void ctx_free(rrl_ctx_t *ctx) int rrl_load(knotd_mod_t *mod) { - // Create RRL context. rrl_ctx_t *ctx = calloc(1, sizeof(rrl_ctx_t)); if (ctx == NULL) { return KNOT_ENOMEM; } - // Create table. uint32_t instant_limit = knotd_conf_mod(mod, MOD_INSTANT_LIMIT).single.integer; uint32_t rate_limit = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer; size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer; @@ -183,19 +120,14 @@ int rrl_load(knotd_mod_t *mod) return KNOT_ENOMEM; } - // Get slip. ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer; - - // Get whitelist. ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST); - // Set up statistics counters. int ret = knotd_mod_stats_add(mod, "slipped", 1, NULL); if (ret != KNOT_EOK) { ctx_free(ctx); return ret; } - ret = knotd_mod_stats_add(mod, "dropped", 1, NULL); if (ret != KNOT_EOK) { ctx_free(ctx); @@ -204,7 +136,7 @@ int rrl_load(knotd_mod_t *mod) knotd_mod_ctx_set(mod, ctx); - return knotd_mod_hook(mod, KNOTD_STAGE_END, ratelimit_apply); + return knotd_mod_hook(mod, KNOTD_STAGE_BEGIN, ratelimit_apply); } void rrl_unload(knotd_mod_t *mod) diff --git a/tests/modules/test_rrl.c b/tests/modules/test_rrl.c index 3a10a41b7..4612f4279 100644 --- a/tests/modules/test_rrl.c +++ b/tests/modules/test_rrl.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,11 +15,15 @@ */ #include <tap/basic.h> +#include <pthread.h> #include <sched.h> +#include <stdio.h> +#include <stdatomic.h> #include "libdnssec/crypto.h" #include "libdnssec/random.h" #include "libknot/libknot.h" +#include "contrib/openbsd/siphash.h" #include "contrib/sockaddr.h" #include "time.h" @@ -28,10 +32,6 @@ int fakeclock_gettime(clockid_t clockid, struct timespec *tp); #include "knot/modules/rrl/functions.c" #undef clock_gettime -#include <stdio.h> -#include <stdatomic.h> - - #define RRL_TABLE_SIZE (1 << 20) #define RRL_INSTANT_LIMIT (1 << 8) #define RRL_RATE_LIMIT (1 << 17) @@ -44,7 +44,6 @@ int fakeclock_gettime(clockid_t clockid, struct timespec *tp); #define HOSTS_LOG 3 // at most 6 attackers + 2 wildcard addresses for normal users #define TICK_QUERIES_LOG 13 // at most 1024 queries per host per tick - // Accessing RRL configuration of INSTANT/RATE limits for V4/V6 and specific prefix. #define LIMIT(type, Vx, prefix) (RRL_MULT(Vx, prefix) * RRL_ ## type ## _LIMIT) @@ -80,16 +79,18 @@ struct kru_avx2 { // ... }; - /* Override time in RRL module. */ struct timespec fakeclock_start; uint32_t fakeclock_tick = 0; -void fakeclock_init(void) { +void fakeclock_init(void) +{ clock_gettime(CLOCK_MONOTONIC_COARSE, &fakeclock_start); fakeclock_tick = 0; } -int fakeclock_gettime(clockid_t clockid, struct timespec *tp) { + +int fakeclock_gettime(clockid_t clockid, struct timespec *tp) +{ uint32_t inc_msec = fakeclock_tick; tp->tv_sec = fakeclock_start.tv_sec + (fakeclock_start.tv_nsec / 1000000 + inc_msec) / 1000; tp->tv_nsec = (fakeclock_start.tv_nsec + (inc_msec % 1000) * 1000000) % 1000000000; @@ -109,18 +110,14 @@ struct stage { struct host hosts[1 << HOSTS_LOG]; }; - -/*! \brief Unit runnable. */ struct runnable_data { rrl_table_t *rrl; - rrl_req_t *rq; - knot_dname_t *zone; int prime; _Atomic uint32_t *queries_acquired, *queries_done; struct stage *stages; }; -static void* rrl_runnable(void *arg) +static void *rrl_runnable(void *arg) { struct runnable_data *d = (struct runnable_data *)arg; size_t si = 0; @@ -153,7 +150,8 @@ static void* rrl_runnable(void *arg) do { fakeclock_gettime(CLOCK_MONOTONIC_COARSE, &ts_fake); clock_gettime(CLOCK_MONOTONIC_COARSE, &ts_real); - } while (!((ts_real.tv_sec > ts_fake.tv_sec) || ((ts_real.tv_sec == ts_fake.tv_sec) && (ts_real.tv_nsec >= ts_fake.tv_nsec)))); + } while (!((ts_real.tv_sec > ts_fake.tv_sec) || + ((ts_real.tv_sec == ts_fake.tv_sec) && (ts_real.tv_nsec >= ts_fake.tv_nsec)))); } #endif @@ -168,10 +166,11 @@ static void* rrl_runnable(void *arg) uint32_t hqi = (qi % (1 << TICK_QUERIES_LOG)) >> HOSTS_LOG; // host query index within tick if (hqi >= d->stages[si].hosts[hi].queries_per_tick) continue; hqi += (qi >> TICK_QUERIES_LOG) * d->stages[si].hosts[hi].queries_per_tick; // across ticks - snprintf(addr_str, sizeof(addr_str), d->stages[si].hosts[hi].addr_format, hqi % 0xff, (hqi >> 8) % 0xff, (hqi >> 16) % 0xff); + snprintf(addr_str, sizeof(addr_str), d->stages[si].hosts[hi].addr_format, + hqi % 0xff, (hqi >> 8) % 0xff, (hqi >> 16) % 0xff); sockaddr_set(&addr, d->stages[si].hosts[hi].addr_family, addr_str, 0); - if (rrl_query(d->rrl, &addr, d->rq, d->zone, NULL) == KNOT_EOK) { + if (rrl_query(d->rrl, &addr, NULL) == KNOT_EOK) { atomic_fetch_add(&d->stages[si].hosts[hi].passed, 1); } @@ -185,11 +184,11 @@ char *impl_name = ""; rrl_table_t *rrl = NULL; void count_test(char *desc, int expected_passing, double margin_fract, - int addr_family, char *addr_format, uint32_t min_value, uint32_t max_value) { + int addr_family, char *addr_format, uint32_t min_value, uint32_t max_value) +{ uint32_t max_queries = expected_passing > 0 ? 2 * expected_passing : -expected_passing; struct sockaddr_storage addr; char addr_str[40]; - rrl_req_t rq = {0,}; int cnt = -1; for (size_t i = 0; i < max_queries; i++) { @@ -197,7 +196,7 @@ void count_test(char *desc, int expected_passing, double margin_fract, i % (max_value - min_value + 1) + min_value, i / (max_value - min_value + 1) % 256); sockaddr_set(&addr, addr_family, addr_str, 0); - if (rrl_query(rrl, &addr, &rq, NULL, NULL) != KNOT_EOK) { + if (rrl_query(rrl, &addr, NULL) != KNOT_EOK) { cnt = i; break; } @@ -208,18 +207,20 @@ void count_test(char *desc, int expected_passing, double margin_fract, is_int(expected_passing, cnt, "rrl(%s): %-48s [%7d ]", impl_name, desc, expected_passing); } else { int max_diff = expected_passing * margin_fract; - ok((expected_passing - max_diff <= cnt) && (cnt <= expected_passing + max_diff), \ - "rrl(%s): %-48s [%7d <=%7d <=%7d ]", impl_name, desc, expected_passing - max_diff, cnt, expected_passing + max_diff); + ok((expected_passing - max_diff <= cnt) && (cnt <= expected_passing + max_diff), + "rrl(%s): %-48s [%7d <=%7d <=%7d ]", impl_name, desc, + expected_passing - max_diff, cnt, expected_passing + max_diff); } } -void test_rrl(void) { - +void test_rrl(void) +{ fakeclock_init(); /* create rrl table */ rrl = rrl_create(RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, 0); ok(rrl != NULL, "rrl(%s): create", impl_name); + assert(rrl); if (KRU.initialize == KRU_GENERIC.initialize) { struct kru_generic *kru = (struct kru_generic *) rrl->kru; @@ -259,7 +260,6 @@ void test_rrl(void) { count_test("IPv4 instant limit /18 not applied on /17", -1, 0, AF_INET, "128.0.64.0", 0, 0); - /* IPv6 multi-prefix tests */ static_assert(RRL_V6_PREFIXES_CNT == 5, "There are no more IPv6 limited prefixes (/128, /64, /56, /48, /32 will be tested)."); @@ -303,7 +303,6 @@ void test_rrl(void) { count_test("IPv6 rate limit /128 after 1 msec", RATEM(V6, 128), 0, AF_INET6, "8000::", 0, 0); - /* parallel tests */ struct stage stages[] = { /* first tick, last tick, hosts */ @@ -345,15 +344,12 @@ void test_rrl(void) { int primes[] = {3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61}; assert(sizeof(primes)/sizeof(*primes) >= RRL_THREADS); - rrl_req_t rq = {0,}; for (unsigned i = 0; i < RRL_THREADS; ++i) { rd[i].rrl = rrl; rd[i].queries_acquired = &queries_acquired; rd[i].queries_done = &queries_done; rd[i].prime = primes[i]; rd[i].stages = stages; - rd[i].zone = NULL; - rd[i].rq = &rq; pthread_create(thr + i, NULL, &rrl_runnable, rd + i); } for (unsigned i = 0; i < RRL_THREADS; ++i) { @@ -366,7 +362,8 @@ void test_rrl(void) { uint32_t ticks = stages[si].last_tick - stages[si].first_tick + 1; for (size_t i = 0; h[i].queries_per_tick; i++) { ok( h[i].min_passed * ticks <= h[i].passed && h[i].passed <= h[i].max_passed * ticks, - "rrl(%s): parallel stage %d, addr %-25s [%10.2f <=%12.4f <=%10.2f ]", impl_name, si, h[i].addr_format, h[i].min_passed, (double)h[i].passed / ticks, h[i].max_passed); + "rrl(%s): parallel stage %d, addr %-25s [%10.2f <=%12.4f <=%10.2f ]", impl_name, + si, h[i].addr_format, h[i].min_passed, (double)h[i].passed / ticks, h[i].max_passed); } } while (stages[++si].first_tick); |