CluPilotCloud/app/Console/Commands/SampleServiceStatus.php

79 lines
3.3 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\StatusDay;
use App\Services\Status\ServiceHealth;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/**
* Writes one sample of every public component into today's row.
*
* The ninety-day bar on the status page is the one figure a reader checks
* against their own memory, so it has to come from something that was actually
* recorded at the time. There is no way to reconstruct it afterwards: the
* monitoring table holds the LAST verdict per instance, not a history, and by
* the time anybody asks about last Tuesday that verdict is about today.
*
* Runs on the scheduler beside the monitoring sync it depends on. A missed run
* costs one sample out of roughly three hundred in a day; a missed DAY leaves
* no row at all, and the page draws that as "not recorded" rather than
* inventing a colour for it.
*/
class SampleServiceStatus extends Command
{
protected $signature = 'clupilot:sample-status';
protected $description = 'Record the current state of every public status component into the daily history';
public function handle(ServiceHealth $health): int
{
// The wall clock, not UTC: the bar is labelled with days a reader
// recognises, and a sample taken at 01:30 local belongs to the day they
// would call it (R19).
$day = Carbon::now()->local()->toDateString();
foreach ($health->components() as $component) {
$state = $component['state'];
// One statement, so two schedulers racing on the same minute cannot
// read-modify-write over each other. The counters are increments,
// never assignments — a sample is a thing that happened, and a
// later run must not be able to undo it.
DB::table('status_days')->upsert(
[[
'day' => $day,
'component' => $component['key'],
'samples' => 1,
'operational' => $state === 'operational' ? 1 : 0,
'degraded' => $state === 'degraded' ? 1 : 0,
'down' => $state === 'down' ? 1 : 0,
'unknown' => $state === 'unknown' ? 1 : 0,
'created_at' => now(),
'updated_at' => now(),
]],
['day', 'component'],
[
'samples' => DB::raw('samples + 1'),
'operational' => DB::raw('operational + '.($state === 'operational' ? 1 : 0)),
'degraded' => DB::raw('degraded + '.($state === 'degraded' ? 1 : 0)),
'down' => DB::raw('down + '.($state === 'down' ? 1 : 0)),
'unknown' => DB::raw('unknown + '.($state === 'unknown' ? 1 : 0)),
'updated_at' => DB::raw('CURRENT_TIMESTAMP'),
],
);
}
// Beyond the window the page can draw, the rows are only weight. Kept a
// fortnight longer than the ninety days shown so a change of window
// does not immediately hit an empty table.
StatusDay::query()
->where('day', '<', Carbon::now()->local()->subDays(104)->toDateString())
->delete();
return self::SUCCESS;
}
}