resolved CF */ function gprint_cf_resolve(array $items, string $keyField): array { $last_graph_cf = []; $resolved = []; foreach ($items as $idx => $item) { if ($item['kind'] === 'draw') { $last_graph_cf[$item['data_source_name']][$item[$keyField]] = $item['graph_cf']; } elseif ($item['kind'] === 'gprint') { $resolved[$idx] = $last_graph_cf[$item['data_source_name']][$item[$keyField]] ?? null; } } return $resolved; } // Aggregate graph: two hosts, both traffic_in from the same data template. // Host A draws AVERAGE (cf 1), host B draws MAX (cf 3). Aggregate graphs emit // the AREA/STACK draw items grouped ahead of the GPRINT legend block, so by the // time a GPRINT reads the cache a later same-key draw has already written it. // Each GPRINT shares its AREA's task_item_id, hence the same data_template_rrd_id. function two_host_aggregate(): array { return [ // host A AREA/AVERAGE ['kind' => 'draw', 'data_source_name' => 'traffic_in', 'data_template_rrd_id' => 10, 'local_data_template_rrd_id' => 100, 'graph_cf' => 1], // host B AREA/MAX ['kind' => 'draw', 'data_source_name' => 'traffic_in', 'data_template_rrd_id' => 20, 'local_data_template_rrd_id' => 100, 'graph_cf' => 3], // host A GPRINT ['kind' => 'gprint', 'data_source_name' => 'traffic_in', 'data_template_rrd_id' => 10, 'local_data_template_rrd_id' => 100], // host B GPRINT ['kind' => 'gprint', 'data_source_name' => 'traffic_in', 'data_template_rrd_id' => 20, 'local_data_template_rrd_id' => 100], ]; } test('fixed key resolves each GPRINT to its own item CF', function () { $resolved = gprint_cf_resolve(two_host_aggregate(), 'data_template_rrd_id'); // host A GPRINT at index 2 -> AVERAGE (1); host B GPRINT at index 3 -> MAX (3) expect($resolved[2])->toBe(1); expect($resolved[3])->toBe(3); }); test('template-scoped key collides across instances of the same template', function () { $resolved = gprint_cf_resolve(two_host_aggregate(), 'local_data_template_rrd_id'); // Both hosts share local_data_template_rrd_id 100, so host B's MAX overwrites // host A's AVERAGE before either GPRINT reads, and both GPRINTs print MAX. // This is the bug the fix removes. expect($resolved[2])->toBe(3); expect($resolved[3])->toBe(3); }); test('non-templated sources do not collide under the fixed key', function () { // Manually built data sources have local_data_template_rrd_id 0 for every // item, so the template-scoped key collapses them all together. $items = [ ['kind' => 'draw', 'data_source_name' => 'in', 'data_template_rrd_id' => 5, 'local_data_template_rrd_id' => 0, 'graph_cf' => 1], ['kind' => 'draw', 'data_source_name' => 'in', 'data_template_rrd_id' => 6, 'local_data_template_rrd_id' => 0, 'graph_cf' => 2], ['kind' => 'gprint', 'data_source_name' => 'in', 'data_template_rrd_id' => 5, 'local_data_template_rrd_id' => 0], ['kind' => 'gprint', 'data_source_name' => 'in', 'data_template_rrd_id' => 6, 'local_data_template_rrd_id' => 0], ]; $fixed = gprint_cf_resolve($items, 'data_template_rrd_id'); expect($fixed[2])->toBe(1); expect($fixed[3])->toBe(2); $buggy = gprint_cf_resolve($items, 'local_data_template_rrd_id'); expect($buggy[2])->toBe(2); expect($buggy[3])->toBe(2); });