fix(traffic): a sample without counters is not zero traffic
Defaulting missing netin/netout to zero reset the baseline, so the next real sample added the entire cumulative counter again — a customer throttled for traffic that was counted twice. Such a sample is skipped and logged now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
2415dde288
commit
7941935f57
|
|
@ -73,8 +73,19 @@ class CollectInstanceTraffic implements ShouldBeUnique, ShouldQueue
|
|||
$client = $proxmox->forHost($instance->host);
|
||||
$status = $client->vmStatus($instance->host->node ?? 'pve', $instance->vmid);
|
||||
|
||||
$netin = (int) ($status['netin'] ?? 0);
|
||||
$netout = (int) ($status['netout'] ?? 0);
|
||||
// Missing telemetry is not "zero traffic": treating it as zero resets the
|
||||
// baseline, and the next real sample then adds the whole cumulative
|
||||
// counter again — a customer throttled for traffic counted twice.
|
||||
if (! isset($status['netin'], $status['netout'])) {
|
||||
Log::warning('traffic sample without counters, skipping', [
|
||||
'instance' => $instance->uuid, 'status' => $status['status'] ?? null,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$netin = (int) $status['netin'];
|
||||
$netout = (int) $status['netout'];
|
||||
|
||||
$row = InstanceTraffic::query()->firstOrCreate(
|
||||
['instance_id' => $instance->id, 'period' => InstanceTraffic::currentPeriod()],
|
||||
|
|
|
|||
|
|
@ -218,3 +218,36 @@ it('keeps trying to release a throttle when Proxmox refuses once', function () {
|
|||
expect(InstanceTraffic::query()->where('throttled', true)->count())->toBe(0)
|
||||
->and($flaky->networkRates[1042])->toBeNull();
|
||||
});
|
||||
|
||||
it('ignores a sample that carries no counters', function () {
|
||||
[$pve, , $instance] = trafficSetup();
|
||||
|
||||
$pve->counters[1042] = ['netin' => 0, 'netout' => 4_000_000];
|
||||
(new CollectInstanceTraffic)->handle($pve);
|
||||
$pve->counters[1042] = ['netin' => 0, 'netout' => 10_000_000];
|
||||
(new CollectInstanceTraffic)->handle($pve);
|
||||
expect(InstanceTraffic::query()->first()->tx_bytes)->toBe(6_000_000);
|
||||
|
||||
// Proxmox answers without telemetry. Treating that as zero would reset the
|
||||
// baseline and count the whole counter again on the next real sample.
|
||||
$blind = new class extends FakeProxmoxClient
|
||||
{
|
||||
public function vmStatus(string $node, int $vmid): array
|
||||
{
|
||||
return ['status' => 'running'];
|
||||
}
|
||||
};
|
||||
app()->instance(ProxmoxClient::class, $blind);
|
||||
(new CollectInstanceTraffic)->handle($blind);
|
||||
|
||||
$row = InstanceTraffic::query()->first();
|
||||
expect($row->tx_bytes)->toBe(6_000_000)
|
||||
->and($row->last_netout)->toBe(10_000_000); // baseline untouched
|
||||
|
||||
// The next good sample continues from where it left off.
|
||||
app()->instance(ProxmoxClient::class, $pve);
|
||||
$pve->counters[1042] = ['netin' => 0, 'netout' => 11_000_000];
|
||||
(new CollectInstanceTraffic)->handle($pve);
|
||||
|
||||
expect(InstanceTraffic::query()->first()->tx_bytes)->toBe(7_000_000);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue