Skip to content

[1.x] Rework pulse:check command #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions src/Commands/CheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
namespace Laravel\Pulse\Commands;

use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;
use Illuminate\Console\Command;
use Illuminate\Contracts\Cache\LockProvider;
use Illuminate\Events\Dispatcher;
use Illuminate\Support\Env;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
use Laravel\Pulse\Events\IsolatedBeat;
use Laravel\Pulse\Events\SharedBeat;
use Laravel\Pulse\Pulse;
Expand All @@ -25,7 +26,7 @@ class CheckCommand extends Command
*
* @var string
*/
public $signature = 'pulse:check';
public $signature = 'pulse:check {--once : Take a single snapshot}';

/**
* The command's description.
Expand All @@ -42,38 +43,36 @@ public function handle(
CacheStoreResolver $cache,
Dispatcher $event,
): int {
$lastRestart = $cache->store()->get('laravel:pulse:restart');
$isVapor = (bool) Env::get('VAPOR_SSM_PATH');

$interval = CarbonInterval::seconds(5);
$instance = $isVapor ? 'vapor' : Str::random();

$lastSnapshotAt = CarbonImmutable::now()->floorSeconds((int) $interval->totalSeconds);
$lastRestart = $cache->store()->get('laravel:pulse:restart');

$lock = ($store = $cache->store()->getStore()) instanceof LockProvider
? $store->lock('laravel:pulse:check', (int) $interval->totalSeconds)
? $store->lock('laravel:pulse:check', 1)
: null;

while (true) {
$now = CarbonImmutable::now();

if ($now->subSeconds((int) $interval->totalSeconds)->lessThan($lastSnapshotAt)) {
Sleep::for(500)->milliseconds();

continue;
}

if ($lastRestart !== $cache->store()->get('laravel:pulse:restart')) {
return self::SUCCESS;
}

$lastSnapshotAt = $now->floorSeconds((int) $interval->totalSeconds);
$now = CarbonImmutable::now();

if ($lock?->get()) {
$event->dispatch(new IsolatedBeat($lastSnapshotAt, $interval));
$event->dispatch(new IsolatedBeat($now));
}

$event->dispatch(new SharedBeat($lastSnapshotAt, $interval));
$event->dispatch(new SharedBeat($now, $instance));

$pulse->ingest();

if ($isVapor || $this->option('once')) {
return self::SUCCESS;
}

Sleep::until($now->addSecond());
}
}
}
2 changes: 0 additions & 2 deletions src/Events/IsolatedBeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Laravel\Pulse\Events;

use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;

class IsolatedBeat
{
Expand All @@ -12,7 +11,6 @@ class IsolatedBeat
*/
public function __construct(
public CarbonImmutable $time,
public CarbonInterval $interval,
) {
//
}
Expand Down
3 changes: 1 addition & 2 deletions src/Events/SharedBeat.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Laravel\Pulse\Events;

use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;

class SharedBeat
{
Expand All @@ -12,7 +11,7 @@ class SharedBeat
*/
public function __construct(
public CarbonImmutable $time,
public CarbonInterval $interval,
public string $instance,
) {
//
}
Expand Down
42 changes: 42 additions & 0 deletions src/Recorders/Concerns/Throttling.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Laravel\Pulse\Recorders\Concerns;

use Carbon\CarbonImmutable;
use DateInterval;
use Illuminate\Support\Facades\App;
use Illuminate\Support\InteractsWithTime;
use Laravel\Pulse\Events\IsolatedBeat;
use Laravel\Pulse\Events\SharedBeat;
use Laravel\Pulse\Support\CacheStoreResolver;

trait Throttling
{
use InteractsWithTime;

/**
* Determine if the recorder is ready to record another snapshot.
*/
protected function throttle(DateInterval|int $interval, SharedBeat|IsolatedBeat $event, callable $callback, ?string $key = null): void
{
$key ??= static::class;

if ($event instanceof SharedBeat) {
$key = $event->instance.":{$key}";
}

$cache = App::make(CacheStoreResolver::class);

$key = 'laravel:pulse:throttle:'.$key;

$lastRunAt = $cache->store()->get($key);

if ($lastRunAt !== null && CarbonImmutable::createFromTimestamp($lastRunAt)->addSeconds($this->secondsUntil($interval))->isFuture()) {
return;
}

$callback($event);

$cache->store()->put($key, $event->time->getTimestamp(), $interval);
}
}
84 changes: 42 additions & 42 deletions src/Recorders/Servers.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
class Servers
{
use Concerns\Throttling;

/**
* The events to listen for.
*
Expand All @@ -35,51 +37,49 @@ public function __construct(
*/
public function record(SharedBeat $event): void
{
if ($event->time->second % 15 !== 0) {
return;
}

$server = $this->config->get('pulse.recorders.'.self::class.'.server_name');
$slug = Str::slug($server);
$this->throttle(15, $event, function ($event) {
$server = $this->config->get('pulse.recorders.'.self::class.'.server_name');
$slug = Str::slug($server);

$memoryTotal = match (PHP_OS_FAMILY) {
'Darwin' => intval(`sysctl hw.memsize | grep -Eo '[0-9]+'` / 1024 / 1024),
'Linux' => intval(`cat /proc/meminfo | grep MemTotal | grep -E -o '[0-9]+'` / 1024),
'Windows' => intval(((int) trim(`wmic ComputerSystem get TotalPhysicalMemory | more +1`)) / 1024 / 1024),
'BSD' => intval(`sysctl hw.physmem | grep -Eo '[0-9]+'` / 1024 / 1024),
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};
$memoryTotal = match (PHP_OS_FAMILY) {
'Darwin' => intval(`sysctl hw.memsize | grep -Eo '[0-9]+'` / 1024 / 1024),
'Linux' => intval(`cat /proc/meminfo | grep MemTotal | grep -E -o '[0-9]+'` / 1024),
'Windows' => intval(((int) trim(`wmic ComputerSystem get TotalPhysicalMemory | more +1`)) / 1024 / 1024),
'BSD' => intval(`sysctl hw.physmem | grep -Eo '[0-9]+'` / 1024 / 1024),
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};

$memoryUsed = match (PHP_OS_FAMILY) {
'Darwin' => $memoryTotal - intval(intval(`vm_stat | grep 'Pages free' | grep -Eo '[0-9]+'`) * intval(`pagesize`) / 1024 / 1024), // MB
'Linux' => $memoryTotal - intval(`cat /proc/meminfo | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB
'Windows' => $memoryTotal - intval(((int) trim(`wmic OS get FreePhysicalMemory | more +1`)) / 1024), // MB
'BSD' => intval(intval(`( sysctl vm.stats.vm.v_cache_count | grep -Eo '[0-9]+' ; sysctl vm.stats.vm.v_inactive_count | grep -Eo '[0-9]+' ; sysctl vm.stats.vm.v_active_count | grep -Eo '[0-9]+' ) | awk '{s+=$1} END {print s}'`) * intval(`pagesize`) / 1024 / 1024), // MB
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};
$memoryUsed = match (PHP_OS_FAMILY) {
'Darwin' => $memoryTotal - intval(intval(`vm_stat | grep 'Pages free' | grep -Eo '[0-9]+'`) * intval(`pagesize`) / 1024 / 1024), // MB
'Linux' => $memoryTotal - intval(`cat /proc/meminfo | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB
'Windows' => $memoryTotal - intval(((int) trim(`wmic OS get FreePhysicalMemory | more +1`)) / 1024), // MB
'BSD' => intval(intval(`( sysctl vm.stats.vm.v_cache_count | grep -Eo '[0-9]+' ; sysctl vm.stats.vm.v_inactive_count | grep -Eo '[0-9]+' ; sysctl vm.stats.vm.v_active_count | grep -Eo '[0-9]+' ) | awk '{s+=$1} END {print s}'`) * intval(`pagesize`) / 1024 / 1024), // MB
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};

$cpu = match (PHP_OS_FAMILY) {
'Darwin' => (int) `top -l 1 | grep -E "^CPU" | tail -1 | awk '{ print $3 + $5 }'`,
'Linux' => (int) `top -bn1 | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
'Windows' => (int) trim(`wmic cpu get loadpercentage | more +1`),
'BSD' => (int) `top -b -d 2| grep 'CPU: ' | tail -1 | awk '{print$10}' | grep -Eo '[0-9]+\.[0-9]+' | awk '{ print 100 - $1 }'`,
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};
$cpu = match (PHP_OS_FAMILY) {
'Darwin' => (int) `top -l 1 | grep -E "^CPU" | tail -1 | awk '{ print $3 + $5 }'`,
'Linux' => (int) `top -bn1 | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
'Windows' => (int) trim(`wmic cpu get loadpercentage | more +1`),
'BSD' => (int) `top -b -d 2| grep 'CPU: ' | tail -1 | awk '{print$10}' | grep -Eo '[0-9]+\.[0-9]+' | awk '{ print 100 - $1 }'`,
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
};

$this->pulse->record('cpu', $slug, $cpu, $event->time)->avg()->onlyBuckets();
$this->pulse->record('memory', $slug, $memoryUsed, $event->time)->avg()->onlyBuckets();
$this->pulse->set('system', $slug, json_encode([
'name' => $server,
'cpu' => $cpu,
'memory_used' => $memoryUsed,
'memory_total' => $memoryTotal,
'storage' => collect($this->config->get('pulse.recorders.'.self::class.'.directories')) // @phpstan-ignore argument.templateType argument.templateType
->map(fn (string $directory) => [
'directory' => $directory,
'total' => $total = intval(round(disk_total_space($directory) / 1024 / 1024)), // MB
'used' => intval(round($total - (disk_free_space($directory) / 1024 / 1024))), // MB
])
->all(),
], flags: JSON_THROW_ON_ERROR), $event->time);
$this->pulse->record('cpu', $slug, $cpu, $event->time)->avg()->onlyBuckets();
$this->pulse->record('memory', $slug, $memoryUsed, $event->time)->avg()->onlyBuckets();
$this->pulse->set('system', $slug, json_encode([
'name' => $server,
'cpu' => $cpu,
'memory_used' => $memoryUsed,
'memory_total' => $memoryTotal,
'storage' => collect($this->config->get('pulse.recorders.'.self::class.'.directories')) // @phpstan-ignore argument.templateType argument.templateType
->map(fn (string $directory) => [
'directory' => $directory,
'total' => $total = intval(round(disk_total_space($directory) / 1024 / 1024)), // MB
'used' => intval(round($total - (disk_free_space($directory) / 1024 / 1024))), // MB
])
->all(),
], flags: JSON_THROW_ON_ERROR), $event->time);
});
}
}
Loading