bind9/tests/isc/stats_test.c
Alessio Podda ed0ecb62e4 Add low contention stats counter
In the current statistics counter implementation, the statistics are
backed by an array of counters, which are updated via atomic operations.
This leads to contention, especially on high core count
machines.

This commit introduces a new isc_statsmulti_t counter that keeps a
separate array per thread. These counters are then aggregated only when
statistics are queried, shifting work off the critical path.

These changes lead to a ~2% improvement in perflab.
2026-03-26 10:19:25 +01:00

200 lines
5.5 KiB
C

/*
* 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.
*/
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/async.h>
#include <isc/atomic.h>
#include <isc/lib.h>
#include <isc/loop.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/stats.h>
#include <isc/statsmulti.h>
#include <isc/time.h>
#include <isc/timer.h>
#include <isc/util.h>
#include <tests/isc.h>
/* test stats */
ISC_RUN_TEST_IMPL(isc_stats_basic) {
isc_stats_t *stats = NULL;
isc_stats_create(isc_g_mctx, &stats, 4);
assert_int_equal(isc_stats_ncounters(stats), 4);
/* Default all 0. */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
assert_int_equal(isc_stats_get_counter(stats, i), 0);
}
/* Test increment. */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
isc_stats_increment(stats, i);
assert_int_equal(isc_stats_get_counter(stats, i), 1);
isc_stats_increment(stats, i);
assert_int_equal(isc_stats_get_counter(stats, i), 2);
}
/* Test decrement. */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
isc_stats_decrement(stats, i);
assert_int_equal(isc_stats_get_counter(stats, i), 1);
isc_stats_decrement(stats, i);
assert_int_equal(isc_stats_get_counter(stats, i), 0);
}
/* Test set. */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
isc_stats_set(stats, i, i);
assert_int_equal(isc_stats_get_counter(stats, i), i);
}
/* Test update if greater. */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
isc_stats_update_if_greater(stats, i, i);
assert_int_equal(isc_stats_get_counter(stats, i), i);
isc_stats_update_if_greater(stats, i, i + 1);
assert_int_equal(isc_stats_get_counter(stats, i), i + 1);
}
/* Test resize. */
isc_stats_resize(&stats, 3);
assert_int_equal(isc_stats_ncounters(stats), 4);
isc_stats_resize(&stats, 4);
assert_int_equal(isc_stats_ncounters(stats), 4);
isc_stats_resize(&stats, 5);
assert_int_equal(isc_stats_ncounters(stats), 5);
/* Existing counters are retained */
for (int i = 0; i < isc_stats_ncounters(stats); i++) {
uint32_t expect = i + 1;
if (i == 4) {
expect = 0;
}
assert_int_equal(isc_stats_get_counter(stats, i), expect);
}
isc_stats_detach(&stats);
}
/* test statsmulti */
ISC_RUN_TEST_IMPL(isc_statsmulti_basic) {
isc_statsmulti_t *stats = NULL;
/* Create with 3 additive counters */
isc_statsmulti_create(isc_g_mctx, &stats, 3);
/* Test increment on additive counters */
for (int i = 0; i < 3; i++) {
isc_statsmulti_increment(stats, i);
assert_int_equal(isc_statsmulti_get_counter(stats, i), 1);
isc_statsmulti_increment(stats, i);
assert_int_equal(isc_statsmulti_get_counter(stats, i), 2);
}
/* Test decrement on additive counters */
for (int i = 0; i < 3; i++) {
isc_statsmulti_decrement(stats, i);
assert_int_equal(isc_statsmulti_get_counter(stats, i), 1);
isc_statsmulti_decrement(stats, i);
assert_int_equal(isc_statsmulti_get_counter(stats, i), 0);
}
/* Test clear */
isc_statsmulti_increment(stats, 0);
isc_statsmulti_increment(stats, 1);
isc_statsmulti_clear(stats);
for (int i = 0; i < 3; i++) {
assert_int_equal(isc_statsmulti_get_counter(stats, i), 0);
}
isc_statsmulti_detach(&stats);
}
/* test statsmulti with multiple threads */
static isc_statsmulti_t *mt_stats = NULL;
static atomic_uint_fast32_t mt_workers_completed = 0;
static int mt_counter_id = 0; /* Global counter ID */
#define MT_INCREMENTS_PER_THREAD 100000
static void
mt_increment_worker(void *arg ISC_ATTR_UNUSED) {
/* Do exactly 100,000 increments */
for (int i = 0; i < MT_INCREMENTS_PER_THREAD; i++) {
isc_statsmulti_increment(mt_stats, mt_counter_id);
}
/* Signal completion and check if we're the last one */
uint32_t completed = atomic_fetch_add(&mt_workers_completed, 1) + 1;
if (completed == isc_loopmgr_nloops()) {
/* Last worker shuts down the loop manager */
isc_loopmgr_shutdown();
}
}
static void
mt_setup_workers(void *arg ISC_ATTR_UNUSED) {
/* Start workers on each loop */
for (size_t i = 0; i < isc_loopmgr_nloops(); i++) {
isc_async_run(isc_loop_get(i), mt_increment_worker, NULL);
}
}
ISC_RUN_TEST_IMPL(isc_statsmulti_multithread) {
atomic_store(&mt_workers_completed, 0);
/* Create stats with 1 additive counter */
isc_statsmulti_create(isc_g_mctx, &mt_stats, 1);
isc_loop_setup(isc_loop_main(), mt_setup_workers, NULL);
isc_loopmgr_run();
/* Check results - should be exactly threads * increments per thread */
uint64_t actual_count = isc_statsmulti_get_counter(mt_stats, 0);
uint64_t expected_total = (uint64_t)isc_loopmgr_nloops() *
MT_INCREMENTS_PER_THREAD;
/* Verify no increments were lost */
assert_int_equal(actual_count, expected_total);
assert_true(actual_count > 0);
/* Verify all workers completed */
assert_int_equal(atomic_load(&mt_workers_completed),
isc_loopmgr_nloops());
/* Cleanup */
isc_statsmulti_detach(&mt_stats);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(isc_stats_basic)
ISC_TEST_ENTRY(isc_statsmulti_basic)
ISC_TEST_ENTRY_CUSTOM(isc_statsmulti_multithread, setup_loopmgr,
teardown_loopmgr)
ISC_TEST_LIST_END
ISC_TEST_MAIN