Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Paired build benchmark

on:
workflow_dispatch:
inputs:
baseline:
description: Baseline Git revision
required: true
candidate:
description: Candidate Git revision
required: true
pairs:
description: Measured pairs after two warmup pairs (2 to 200)
default: '20'
required: true

permissions:
contents: read

jobs:
benchmark:
runs-on: ubuntu-24.04
timeout-minutes: 45
services:
database:
image: mirror.gcr.io/library/mariadb:10.11
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: '1'
ports:
- 3306:3306
options: >-
--health-cmd="mariadb-admin ping --silent"
--health-interval=5s
--health-timeout=5s
--health-retries=20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
persist-credentials: false
- name: Install shared toolchain and fixtures
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake libtool help2man gcc libmariadb-dev mariadb-client libsnmp-dev snmp snmpd libssl-dev libcmocka-dev
sudo systemctl stop snmpd
- name: Verify benchmark evidence checks
run: python3 tests/tools/test_benchmark_record.py
- name: Build both exact revisions with identical settings
env:
BASELINE: ${{ inputs.baseline }}
CANDIDATE: ${{ inputs.candidate }}
run: bash tests/tools/build_benchmark_pair.sh "$BASELINE" "$CANDIDATE" benchmark-builds
- name: Measure alternating polling cycles and process CPU
env:
PAIRS: ${{ inputs.pairs }}
run: |
python3 tests/tools/benchmark_builds.py benchmark-builds/baseline/spine benchmark-builds/candidate/spine --pairs "$PAIRS"
python3 tests/tools/verify_benchmark_record.py benchmark-results
- name: Preserve measurements and build provenance
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: paired-benchmark-${{ github.run_id }}
path: |
benchmark-results/
benchmark-builds/*-revision.txt
benchmark-builds/*-build.log
benchmark-builds/*-binary.sha256
benchmark-builds/compiler.txt
benchmark-builds/dependencies.txt
benchmark-builds/build-settings.txt
if-no-files-found: error
21 changes: 19 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ jobs:
set -euo pipefail
sudo apt-get update
sudo apt-get install -y cppcheck
# Raw checkouts have no generated configure probes. Analyze the
# supported MySQL mode API, then both MariaDB verification APIs;
# treating every declaration probe as zero erases all TLS setters.
# Project data structures are consumed across translation units;
# checking headers in isolation otherwise reports every member. A
# few externally visible helpers are deliberate unit-test seams.
Expand All @@ -187,7 +190,20 @@ jobs:
--suppress=toomanyconfigs \
--library=scripts/cppcheck/spine.cfg \
-I. -Isrc -Ivendor/uthash \
-DHAVE_DECL_MYSQL_OPT_SSL_MODE=1 \
src
for callback in 0 1; do
callback_flags=()
if [[ "$callback" == 1 ]]; then
callback_flags=(-DHAVE_MARIADB_TLS_VERIFICATION_CALLBACK=1 -DHAVE_DECL_MARIADB_OPT_TLS_VERIFICATION_CALLBACK=1)
fi
cppcheck --enable=warning,style,performance,portability --std=c11 \
--error-exitcode=1 --suppress=missingInclude --suppress=missingIncludeSystem \
--library=scripts/cppcheck/spine.cfg -I. -Isrc -Ivendor/uthash \
-DHAVE_DECL_MYSQL_OPT_SSL_ENFORCE=1 -DHAVE_DECL_MYSQL_OPT_SSL_VERIFY_SERVER_CERT=1 \
-DHAVE_DECL_MYSQL_OPT_TLS_VERSION=1 -DHAVE_DECL_MYSQL_OPT_SSL_CIPHER=1 \
"${callback_flags[@]}" src/database/tls.c
done

# GCC 15 and later default to gnu23. Build with the newest Fedora GCC to
# show that configure still selects gnu17 there.
Expand Down Expand Up @@ -222,7 +238,7 @@ jobs:
runuser -u builder -- ./configure --enable-warnings
grep "^CFLAGS = .*-std=gnu17" Makefile
runuser -u builder -- make --jobs "$(nproc)"
runuser -u builder -- make check
runuser -u builder -- make check || { cat test-suite.log; exit 1; }
'

# The Clang counterpart of newest-gcc. Pin the major version so a Fedora
Expand Down Expand Up @@ -259,7 +275,7 @@ jobs:
runuser -u builder -- ./configure CC=clang --enable-warnings
grep "^CFLAGS = .*-std=gnu17" Makefile
runuser -u builder -- make --jobs "$(nproc)"
runuser -u builder -- make check
runuser -u builder -- make check || { cat test-suite.log; exit 1; }
'

shellcheck:
Expand Down Expand Up @@ -358,6 +374,7 @@ jobs:
- name: Report coverage
run: |
set -euo pipefail
python3 tests/tools/test_json_encoder.py
# configure leaves instrumented conftest objects behind and gcovr
# cannot resolve their sources
rm -f ./*conftest*.gcno ./*conftest*.gcda
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ jobs:
else
make -j"$(nproc)" check
fi
make --no-print-directory coverage-unit-program-list > nightly-unit-programs.txt
tests/tools/check_nightly_tests.sh nightly-unit-programs.txt

- name: Load the regression fixtures
run: |
Expand Down
48 changes: 44 additions & 4 deletions .github/workflows/regressions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ permissions:
jobs:
regressions:
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
client:
- choice: mariadb
package: libmariadb-dev
- choice: mysql
package: libmysqlclient-dev
env:
SPINE_CLIENT_PACKAGE: ${{ matrix.client.package }}
SPINE_CLIENT_CHOICE: ${{ matrix.client.choice }}
services:
database:
image: mirror.gcr.io/library/mariadb:10.11
Expand All @@ -26,6 +37,19 @@ jobs:
--health-interval=5s
--health-timeout=5s
--health-retries=20
remote-database:
image: mirror.gcr.io/library/mysql:8.4
env:
MYSQL_ALLOW_EMPTY_PASSWORD: '1'
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: spine_regressions
ports:
- 3307:3306
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=5s
--health-timeout=5s
--health-retries=20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
Expand All @@ -34,23 +58,30 @@ jobs:
run: |
set -eu
sudo apt-get update
sudo apt-get install -y autoconf automake libtool help2man libmariadb-dev mariadb-client libsnmp-dev snmpd libssl-dev libcap2-bin libcmocka-dev lcov
sudo apt-get install -y autoconf automake libtool help2man "$SPINE_CLIENT_PACKAGE" mariadb-client libsnmp-dev snmpd libssl-dev libcap2-bin libcmocka-dev lcov
- name: Build and test production code
run: |
set -eu
mkdir -p config m4
autoreconf -fi
./configure
./configure --with-mysql-client="$SPINE_CLIENT_CHOICE"
make -j2 check
- name: Verify child resource and cancellation limits
run: bash tests/tools/run_script_limits.sh
- name: Verify database configuration loading
env:
SPINE_TEST_DB_HOST: 127.0.0.1
SPINE_TEST_REMOTE_DB_HOST: 127.0.0.1
run: |
set -eu
mariadb --protocol=TCP --host=127.0.0.1 --user=root spine_regressions < tests/fixtures/configuration.sql
mariadb --protocol=TCP --host=127.0.0.1 --user=root spine_regressions < tests/fixtures/poller.sql
# The fixture preserves Cacti's historical zero-date defaults.
mariadb --protocol=TCP --host=127.0.0.1 --port=3307 --user=root --init-command="SET SESSION sql_mode='NO_ENGINE_SUBSTITUTION'" spine_regressions < tests/fixtures/configuration.sql
mariadb --protocol=TCP --host=127.0.0.1 --port=3307 --user=root --init-command="SET SESSION sql_mode='NO_ENGINE_SUBSTITUTION'" spine_regressions < tests/fixtures/poller.sql
./test_spine_regressions --database
- name: Verify SNMP availability against local agent
# TLS fixtures are separate from the schema-mutating database suites.
env:
SPINE_TEST_SNMP_HOST: 127.0.0.1
SPINE_TEST_DB_HOST: 127.0.0.1
Expand All @@ -67,6 +98,15 @@ jobs:
env:
SPINE_SNMPV3_EVIDENCE_DIR: snmpv3-evidence
run: tests/tools/run_snmpv3.sh
- name: Verify database TLS trust and downgrade boundaries
run: bash tests/tools/run_database_tls.sh
- name: Preserve database TLS evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: database-tls-${{ matrix.client.choice }}-${{ github.sha }}
path: tls-evidence/
if-no-files-found: error
- name: Verify ICMP against local loopback
run: |
set -eu
Expand Down Expand Up @@ -100,6 +140,6 @@ jobs:
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: production-coverage-${{ github.sha }}
name: production-coverage-${{ matrix.client.choice }}-${{ github.sha }}
path: coverage/
if-no-files-found: error
75 changes: 66 additions & 9 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ spine_support_sources = src/app/buffer.c \
src/log/sanitize.c \
src/script/escape.c \
src/config/settings.c src/config/options.c src/config/file.c \
src/log/log.c src/poller/result.c \
src/log/log.c src/log/json.c src/poller/result.c \
src/platform/privileges.c src/poller/regex.c
spine_database_sources = src/database/sql.c \
src/database/connection.c \
src/database/connection.c src/database/tls.c src/database/lease.c \
src/database/pool.c src/database/transfer.c \
src/database/cacti_query.c src/database/persistence.c
spine_snmp_sources = src/snmp/requests.c src/snmp/response.c \
Expand All @@ -40,7 +40,7 @@ spine_process_sources = src/process/nft_popen.c src/process/fd.c

spine_shared_sources = $(spine_database_sources) $(spine_support_sources) $(spine_snmp_sources) src/platform/locks.c $(spine_poller_sources) \
$(spine_process_sources) $(spine_php_sources) $(spine_ping_sources) src/config/keywords.c src/app/signals.c
spine_runtime_sources = $(spine_shared_sources) src/app/runtime.c src/app/cli.c src/app/startup.c src/poller/workers.c
spine_runtime_sources = $(spine_shared_sources) src/platform/worker_set.c src/app/runtime.c src/app/cli.c src/app/startup.c src/poller/workers.c
spine_SOURCES = $(spine_runtime_sources) src/app/main.c

configdir = $(sysconfdir)
Expand Down Expand Up @@ -68,15 +68,48 @@ man_MANS = docs/man/spine.1
check_PROGRAMS = test_spine_regressions
dist_noinst_DATA = tests/fixtures/configuration.sql tests/fixtures/poller.sql tests/fixtures/snmpd.conf tests/fixtures/snmpv3-agent.conf tests/fixtures/snmpv3-users.conf
TESTS = $(check_PROGRAMS)
TESTS += tests/tools/test_nightly_tests.sh
EXTRA_DIST = tests/tools/check_nightly_tests.sh tests/tools/test_nightly_tests.sh
EXTRA_DIST += tests/tools/test_json_encoder.py
EXTRA_DIST += tests/tools/run_database_tls.sh
EXTRA_DIST += tests/tools/ownership-coverage-sources.txt
EXTRA_DIST += tests/tools/build_benchmark_pair.sh tests/tools/benchmark_builds.py
EXTRA_DIST += tests/tools/verify_benchmark_record.py tests/tools/test_benchmark_record.py
EXTRA_DIST += docs/benchmarks/README.md \
docs/benchmarks/results/2026-10-11-default-logging/README.md \
docs/benchmarks/results/2026-10-11-default-logging/cycles.csv \
docs/benchmarks/results/2026-10-11-default-logging/metadata.json \
docs/benchmarks/results/2026-10-11-default-logging/summary.json \
docs/benchmarks/results/2026-10-11-default-logging/compiler.txt \
docs/benchmarks/results/2026-10-11-default-logging/dependencies.txt \
docs/benchmarks/results/2026-10-11-default-logging/builds.sha256 \
docs/benchmarks/results/2026-10-11-default-logging/cpu-quota.txt \
docs/benchmarks/results/2026-10-11-default-logging/revisions.txt \
docs/benchmarks/results/2026-10-11-default-logging/build-settings.txt
EXTRA_DIST += docs/benchmarks/results/2026-10-11-runtime-ownership/README.md \
docs/benchmarks/results/2026-10-11-runtime-ownership/cycles.csv \
docs/benchmarks/results/2026-10-11-runtime-ownership/metadata.json \
docs/benchmarks/results/2026-10-11-runtime-ownership/summary.json \
docs/benchmarks/results/2026-10-11-runtime-ownership/compiler.txt \
docs/benchmarks/results/2026-10-11-runtime-ownership/dependencies.txt \
docs/benchmarks/results/2026-10-11-runtime-ownership/builds.sha256 \
docs/benchmarks/results/2026-10-11-runtime-ownership/cpu-quota.txt \
docs/benchmarks/results/2026-10-11-runtime-ownership/revisions.txt \
docs/benchmarks/results/2026-10-11-runtime-ownership/build-settings.txt
test_spine_regressions_SOURCES = $(spine_runtime_sources) tests/regression/test_spine_regressions.c tests/contracts/test_spine_contracts.c tests/contracts/test_spine_reindex_contracts.c tests/contracts/test_spine_snmpv3_contracts.c tests/contracts/test_spine_settings_contracts.c tests/contracts/test_spine_cli_contracts.c tests/contracts/test_spine_stream_contracts.c tests/contracts/test_spine_output_contracts.c tests/contracts/test_spine_privilege_contracts.c
test_spine_regressions_SOURCES += tests/contracts/test_spine_tls_contracts.c
test_spine_regressions_SOURCES += tests/contracts/test_spine_script_limits_contracts.c
EXTRA_DIST += tests/tools/run_script_limits.sh tests/fixtures/script_resource_probe.c
EXTRA_DIST += docs/operations/script-limits.md docs/operations/database-tls.md \
docs/operations/issue-boundaries.md docs/adr/0004-formatting-and-json-dependencies.md
# Link the real production translation units and globals, with a separate test
# entry point. Production and regression binaries share the runtime objects.

if HAVE_LINKER_WRAP
check_PROGRAMS += test_spine_faults
# Link the same runtime implementation with a separate fault-test entry point.
test_spine_faults_SOURCES = $(spine_runtime_sources) tests/regression/test_spine_faults.c
test_spine_faults_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=strftime -Wl,--wrap=mysql_query -Wl,--wrap=mysql_ping -Wl,--wrap=mysql_errno \
test_spine_faults_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=write -Wl,--wrap=strftime -Wl,--wrap=mysql_query -Wl,--wrap=mysql_ping -Wl,--wrap=mysql_errno \
-Wl,--wrap=mysql_real_connect -Wl,--wrap=mysql_thread_id -Wl,--wrap=mysql_store_result \
-Wl,--wrap=mysql_num_rows -Wl,--wrap=mysql_fetch_row -Wl,--wrap=mysql_free_result -Wl,--wrap=mysql_num_fields \
-Wl,--wrap=pipe -Wl,--wrap=socketpair -Wl,--wrap=posix_spawn -Wl,--wrap=posix_spawnattr_setpgroup \
Expand All @@ -90,11 +123,13 @@ endif
# Headers are not referenced by any _SOURCES, so automake will not distribute
# them unless they are listed. Without this the release tarball builds nothing.
noinst_HEADERS = src/internal/common.h src/app/signals.h src/config/keywords.h src/platform/locks.h \
src/platform/worker_set.h \
src/process/nft_popen.h src/script/server.h src/ping/ping.h src/poller/poller.h \
src/snmp/snmp.h src/app/spine.h src/platform/spine_sem.h src/internal/spine_types.h \
src/database/sql.h vendor/uthash/uthash.h src/internal/util.h
noinst_HEADERS += src/database/lease.h

EXTRA_DIST = docs/man/spine.1 etc/spine.conf.dist \
EXTRA_DIST += docs/man/spine.1 etc/spine.conf.dist \
CONTRIBUTORS.md docs/licensing.md LICENSES/LicenseRef-Xenadyne.txt LICENSES/LicenseRef-Xenadyne-Header.txt \
LICENSES/GPL-2.0-or-later.txt LICENSES/LGPL-2.1-or-later.txt LICENSES/LGPL-2.1-only.txt \
tests/README.md \
Expand Down Expand Up @@ -163,7 +198,9 @@ check_PROGRAMS += \
tests/unit/test_snmpv3_session \
tests/unit/test_ping_icmp \
tests/unit/test_ping_timing \
tests/unit/test_log_config
tests/unit/test_log_config \
tests/unit/test_worker_set \
tests/unit/test_db_lease

# A small executable which speaks the subset of the PHP Script Server protocol
# needed by test_php_runtime. Keeping it as a real child covers the shipped
Expand All @@ -178,7 +215,7 @@ endif

# test_util_strings pulls in common.h and the sources under test, so it needs
# the same libraries spine links against. The others are self-contained.
tests_unit_test_util_strings_SOURCES = tests/unit/test_util_strings.c
tests_unit_test_util_strings_SOURCES = tests/unit/test_util_strings.c src/log/json.c
tests_unit_test_util_strings_LDADD = $(CMOCKA_LIBS) $(LIBS)

tests_unit_test_build_fixes_SOURCES = tests/unit/test_build_fixes.c
Expand Down Expand Up @@ -216,7 +253,7 @@ tests_unit_test_classify_result_SOURCES = tests/unit/test_classify_result.c test
tests_unit_test_classify_result_LDADD = $(CMOCKA_LIBS) $(LIBS)

# Includes production helpers, so it needs the same libraries as Spine
tests_unit_test_util_paths_SOURCES = tests/unit/test_util_paths.c
tests_unit_test_util_paths_SOURCES = tests/unit/test_util_paths.c src/log/json.c
tests_unit_test_util_paths_LDADD = $(CMOCKA_LIBS) $(LIBS)

tests_unit_test_snmpv3_session_SOURCES = tests/unit/test_snmpv3_session.c tests/support/spine_runtime.c \
Expand All @@ -237,7 +274,7 @@ tests_unit_test_poll_host_SOURCES = tests/unit/test_poll_host.c tests/support/sp
tests_unit_test_poll_host_LDADD = $(CMOCKA_LIBS) $(LIBS)
tests_unit_test_poll_host_LDFLAGS = \
-Wl,--wrap=db_query -Wl,--wrap=db_insert -Wl,--wrap=db_escape \
-Wl,--wrap=db_get_connection -Wl,--wrap=db_release_connection \
-Wl,--wrap=db_acquire_lease -Wl,--wrap=db_release_lease \
-Wl,--wrap=db_free_result -Wl,--wrap=mysql_num_rows -Wl,--wrap=mysql_fetch_row \
-Wl,--wrap=snmp_host_init -Wl,--wrap=snmp_host_cleanup -Wl,--wrap=snmp_get_base \
-Wl,--wrap=snmp_get -Wl,--wrap=snmp_get_allow_fail -Wl,--wrap=snmp_get_multi \
Expand All @@ -254,6 +291,26 @@ tests_unit_test_log_config_SOURCES = tests/unit/test_log_config.c tests/support/
src/config/keywords.c src/app/signals.c
tests_unit_test_log_config_LDADD = $(CMOCKA_LIBS) $(LIBS)

tests_unit_test_worker_set_SOURCES = tests/unit/test_worker_set.c src/platform/worker_set.c
tests_unit_test_worker_set_LDADD = $(CMOCKA_LIBS) $(LIBS)
tests_unit_test_worker_set_CPPFLAGS = $(AM_CPPFLAGS)
if HAVE_LINKER_WRAP
tests_unit_test_worker_set_CPPFLAGS += -DSPINE_WORKER_TEST_WRAP
tests_unit_test_worker_set_LDFLAGS = -Wl,--wrap=calloc -Wl,--wrap=pthread_mutex_init \
-Wl,--wrap=pthread_mutex_destroy -Wl,--wrap=pthread_create -Wl,--wrap=pthread_join \
-Wl,--wrap=pthread_attr_getdetachstate

tests_unit_test_worker_set_LDFLAGS += -Wl,--wrap=pthread_mutex_lock
endif

tests_unit_test_db_lease_SOURCES = tests/unit/test_db_lease.c src/database/lease.c
tests_unit_test_db_lease_LDADD = $(CMOCKA_LIBS) $(LIBS)
tests_unit_test_db_lease_CPPFLAGS = $(AM_CPPFLAGS)
if HAVE_LINKER_WRAP
tests_unit_test_db_lease_CPPFLAGS += -DSPINE_LEASE_TEST_WRAP
tests_unit_test_db_lease_LDFLAGS = -Wl,--wrap=calloc -Wl,--wrap=pthread_mutex_init -Wl,--wrap=pthread_mutex_destroy
endif

tests_unit_test_php_runtime_SOURCES = tests/unit/test_php_runtime.c tests/support/spine_runtime.c \
$(spine_database_sources) $(spine_support_sources) $(spine_snmp_sources) src/platform/locks.c $(spine_poller_sources) $(spine_process_sources) $(spine_php_sources) $(spine_ping_sources) \
src/config/keywords.c src/app/signals.c
Expand Down
Loading
Loading