Skip to content

Value acquisition: counter zero pins the rate, wrap modulus off by one, percent denominator cast to int #785

Description

@somethingwithproof

Four defects in how a raw reading becomes the value a threshold is compared against.

A counter reading of exactly 0 is treated as "no previous reading" (thold_functions.php:796)

case 2: // COUNTER
    if ($thold_data['oldvalue'] != 0 && is_numeric($thold_data['oldvalue'])) {
        ... compute the delta ...
    } else {
        $currentval = 0;
    }

oldvalue of 0 is a legitimate previous reading, not an absent one. After a device reboot, or any interval where a counter genuinely sits at 0, the next poll reports a rate of 0. A thold_low on interface utilisation fires spuriously; a thold_high under-reports the first interval of traffic.

Distinguishing "no previous reading" (NULL or '') from "previous reading was 0", and testing only is_numeric(), would fix it. The DERIVE arm at :817 has the same shape.

Counter wrap is off by one, and the 64-bit case loses precision (thold_functions.php:806-810)

if ($thold_data['oldvalue'] > 4294967295) {
    $currentval = (18446744073709551615 - $thold_data['oldvalue']) + $item[...];
} else {
    $currentval = (4294967295 - $thold_data['oldvalue']) + $item[...];
}

The wrap modulus is 2^32 = 4294967296, not 4294967295, so every 32-bit wrap under-counts by exactly 1. Same for 2^64.

More seriously, 18446744073709551615 is above PHP_INT_MAX (9223372036854775807) and is parsed as a float:

$v = 18446744073709551615;  =>  1.8446744073709552E+19 (double)

so the 64-bit branch loses roughly 11 bits before the subtraction.

Cacti core's rrd.php and rrdtool itself use the correct moduli.

The percent-of denominator is cast to int (thold_functions.php:1303)

$t = (int) $rrd_reindexed[$thold['local_data_id']][$thold['percent_ds']];

if ($t > 0) {
    $currentval = ($currentval / $t) * 100;
} else {
    $currentval = 0;
}

A denominator below 1 truncates to 0 and the percentage is forced to 0; a negative denominator gives 0 rather than a negative percentage; a large float denominator is truncated. A percent threshold on a ratio with a sub-unit denominator reports 0% permanently, so a configured thold_low alerts forever.

The daemon stores a timestamp in oldvalue (thold_process.php:210-223)

if (isset($item[$thold_data['name']])) {
    $lasttime = $item[$thold_data['name']];              // a value
} else {
    $lasttime = $currenttime - $thold_data['rrd_step'];  // a timestamp
}
...
SET tcheck = 1, lastread = ?, lasttime = FROM_UNIXTIME(?), oldvalue = ?
   [$currentval, $currenttime, $lasttime, ...]

$lasttime is bound to oldvalue. When the data source has no reading this cycle, a Unix timestamp (around 1.7e9) is stored as the previous counter reading. On the next poll the delta is a large negative number, which takes the overflow branch above and fabricates a rate in the billions — a bogus high alert on the poll after any missed sample.

The non-daemon path at includes/polling.php:214-217 correctly falls back to $thold_data['oldvalue'].

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugdaemonRelated to thold daemon

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions