Skip to content

refactor(thold): lift the evaluation settings out of the threshold check - #800

Open
somethingwithproof wants to merge 6 commits into
Cacti:developfrom
somethingwithproof:refactor/evaluation-context
Open

refactor(thold): lift the evaluation settings out of the threshold check#800
somethingwithproof wants to merge 6 commits into
Cacti:developfrom
somethingwithproof:refactor/evaluation-context

Conversation

@somethingwithproof

Copy link
Copy Markdown
Member

Phase 2 of restructuring thold_check_threshold().

Stacked on #799 — the characterization tests there are the safety net this phase depends on, so it should merge after. Until then this PR's diff shows #799's commits too; once that lands, what remains is two commits against thold_functions.php plus one test file.

Eight write-only locals

The preamble assigned eight values that nothing ever read. Verified by listing every reference to each inside the function:

$global_alert_address  1 reference   (the assignment)
$deadnotify            1 reference   (the assignment)
$show_datasource       1 reference   (the assignment)
$baseu                 1 reference   (the assignment)
$suffix                2 references  (both assignments, no read)
$show_units            1 reference   (the assignment)
$units_suffix          1 reference   (the assignment)
$decimals              1 reference   (the assignment)

Two of them cost real work per evaluation:

  • $baseu ran SELECT base_value FROM graph_templates_graph WHERE local_graph_id = ? and discarded the result. That is one round trip per threshold per poll cycle, feeding nothing.
  • $show_datasource called thold_datasource_required() for a value nobody used.

$deadnotify is the one #751 reports.

Lifting the preamble

What remains moves to thold_evaluation_context(). It derives everything from the threshold row and the Cacti settings, decides nothing, and has no side effects, which is what lets it move without changing behaviour. thold_check_threshold() unpacks the twenty values its arms actually use.

thold_check_threshold() goes from 1432 lines to 1378.

The real gain is reachability. These rules were only exercisable by driving a whole poll; they now have direct tests:

  • an unset thold_fail_trigger falls back to the global alert_trigger
  • alerts reach the warning recipients only when the two lists differ and the operator enabled it
  • a graph is attached unless notifications are text-only or the threshold has no graph
  • each SNMP trap class follows its own setting

Verification

The 35 characterization tests pass unchanged — no assertion needed editing, which is the check that this phase was pure motion. 15 new tests cover the extracted function directly. 50 total, 100% of the changed lines covered.

composer lint and composer test both clean.

Next

Phase 3 extracts the emitter — the subject/syslog/email/command/trap/log/hook block repeated 18 times across the three arms — and is where the divergences behind #742 through #752 get collapsed onto one implementation. Having $context as one value to pass is what makes that tractable.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the thold_check_threshold() evaluator setup by extracting its configuration/derived-values preamble into a dedicated thold_evaluation_context() helper, and introduces a PHPUnit-based characterization/coverage harness to lock in existing behavior while enabling further safe refactors.

Changes:

  • Extracts evaluation settings/derived values into thold_evaluation_context() and updates thold_check_threshold() to consume the returned context.
  • Adds PHPUnit unit/characterization tests plus a Cacti stub framework and scenario/outcome helpers to exercise evaluator behavior without a live Cacti install.
  • Adds CI (Docker-based) to run lint, tests with coverage, and enforce changed-lines coverage.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
thold_functions.php Adds thold_evaluation_context() and replaces inline preamble in thold_check_threshold() with context unpacking.
tests/Unit/ThresholdHiLowCharacterizationTest.php Characterization tests for the hi/low arm behavior over a single poll.
tests/Unit/ThresholdBaselineCharacterizationTest.php Characterization tests for the baseline arm behavior over a single poll.
tests/Unit/ThresholdTimeBasedCharacterizationTest.php Characterization tests for the time-based arm behavior over a single poll.
tests/Unit/TholdEvaluationContextTest.php Direct unit tests for thold_evaluation_context() behavior (triggers, recipients, graph attachment, trap settings).
tests/TestCase.php PHPUnit base class providing shared reset/setup and plugin loading helpers.
tests/Helpers/CactiStubs.php Programmable/recording stub layer for Cacti global functions used by the plugin.
tests/Helpers/ThresholdScenario.php Builder/driver for running one threshold through one poll of thold_check_threshold().
tests/Helpers/ThresholdOutcome.php Assertion helper that interprets recorded side effects (mail/log/traps/persisted columns).
tests/fixtures/optional-core-functions.php Fixture for optional core functions to exercise function_exists() branches.
tests/fixtures/cacti-lib/variables.php Minimal file fixture to satisfy runtime includes in plugin code during tests.
tests/bootstrap-unit.php Test bootstrap providing Cacti function shims and global environment expected by the plugin.
tests/bin/patch-coverage.php Computes coverage specifically for added/modified lines versus a base ref.
tests/docker/Dockerfile Dockerized PHPUnit runner pinned to PHP 8.1 with pcov for coverage.
tests/docker/docker-compose.yml Local developer entrypoint to run the same Docker-based test flow as CI.
phpunit.xml PHPUnit configuration targeting unit tests and scoping coverage to thold_functions.php.
composer.json Adds dev dependencies (PHPUnit, phplint) and scripts for lint/test/coverage/docker.
.gitignore Ignores vendor, coverage output, and PHPUnit cache artifacts.
.github/workflows/php-unit-tests.yml GitHub Actions workflow to run lint, tests with coverage, and changed-lines coverage gate in Docker.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +39 to +45
if (!function_exists('get_total_row_data')) {
function get_total_row_data($user_id, $sql, $sql_params = [], $class = '', $timeout = 86400) {
CactiStub::record('get_total_row_data', $sql, $sql_params);

return CactiStub::nextReturn('get_total_row_data', 0);
}
}
Comment thread tests/bootstrap-unit.php
Comment on lines +125 to +129
if (!function_exists('db_qstr')) {
function db_qstr($string) {
return "'" . str_replace("'", "''", (string) $string) . "'";
}
}
thold_check_threshold() has no return value: everything it decides is a side
effect through seven global Cacti functions. ThresholdScenario builds the
fixture those need and runs one poll; ThresholdOutcome reads back what was
emitted, so a test asserts on behaviour rather than on the SQL text.

No production file is touched. Several assertions record behaviour that is
wrong rather than intended, each with a comment saying so, so that fixing it
later is a deliberate edit here.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Records where this arm has drifted from the hi/low one, notably that a
restoral writes the log row and clears the state but sends no mail, so an
operator sees the alert and never the all-clear.

Cacti's cell fetchers return false rather than '' when a query matches no row.
The stub now does the same: on PHP 8 the difference is a TypeError in the
re-alert arithmetic, so the old default invented a failure production does not
have.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Adopts the conventions from Cacti core: tests/bootstrap-unit.php, tests/Helpers
for the stubs, a phpunit.xml carrying error_reporting -1 and
CACTI_TEST_BOOTSTRAP, and composer lint / test / test:coverage scripts so CI
runs the same commands a developer does. The dev toolchain is Cacti's, pinned
to the same platform php 8.1.0.

Cacti core runs Pest and this suite does not, because pest ^2 does not resolve
on PHP 8.1 -- the platform Cacti's own composer.json pins. Releases up to
v2.36.0 conflict with phpunit 10.5.62 and later, every earlier 10.x release is
blocked by advisory PKSA-z3gr-8qht-p93v, and v2.36.1, which does resolve,
requires PHP 8.2. The stack installs on 8.2 and above; 8.1 is the floor this
plugin's CI matrix targets. The tests are written in the plain PHPUnit class
style that Cacti's tests/Pest.php explicitly supports, so they run unchanged
under Pest wherever it is installable.
This arm compares the reading against statistics rrdtool reports for a
reference window, so the scenario supplies those rather than static bounds.
Reaching them means answering the three rrdtool calls thold makes -- file
existence, an info block describing the data sources and consolidation
functions, and a graph command whose printed values are decoded by position --
which the helper now does.

Completes the three arms, so Phase 2 can start moving code.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Each was assigned and never read. One of them, $baseu, ran a query against
graph_templates_graph and discarded the result, so every threshold on every
poll cycle paid for a round trip that fed nothing. $show_datasource likewise
called thold_datasource_required() for a value nobody used.

The others are $global_alert_address, $deadnotify (issue Cacti#751), $suffix,
$show_units, $units_suffix and $decimals.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
The preamble computed the settings the three threshold arms read, so reaching
any of that logic meant driving a whole poll. It moves to
thold_evaluation_context(), which derives everything from the threshold row
and the Cacti settings and decides nothing, and the check unpacks what it
needs.

Pure motion. The characterization tests from the previous commits pass
unchanged, which is what they were written for, and the rules that were buried
in the preamble now have direct tests: trigger fallback, whether alerts also
reach the warning recipients, and when a graph is attached.

thold_check_threshold() goes from 1432 lines to 1378.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof
somethingwithproof force-pushed the refactor/evaluation-context branch from 0169913 to 10ee9c6 Compare August 17, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants