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);
|
$client = $proxmox->forHost($instance->host);
|
||||||
$status = $client->vmStatus($instance->host->node ?? 'pve', $instance->vmid);
|
$status = $client->vmStatus($instance->host->node ?? 'pve', $instance->vmid);
|
||||||
|
|
||||||
$netin = (int) ($status['netin'] ?? 0);
|
// Missing telemetry is not "zero traffic": treating it as zero resets the
|
||||||
$netout = (int) ($status['netout'] ?? 0);
|
// 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(
|
$row = InstanceTraffic::query()->firstOrCreate(
|
||||||
['instance_id' => $instance->id, 'period' => InstanceTraffic::currentPeriod()],
|
['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)
|
expect(InstanceTraffic::query()->where('throttled', true)->count())->toBe(0)
|
||||||
->and($flaky->networkRates[1042])->toBeNull();
|
->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