Skip to content

Commit 94e9c58

Browse files
authored
[1.x] Customise server stats (#339)
* allow server stat resolution to be customised * Fix code styling * Addresses lint issue * Fix test comparison for SQLite --------- Co-authored-by: timacdonald <timacdonald@users.noreply.github.com>
1 parent b5f89b7 commit 94e9c58

File tree

2 files changed

+115
-23
lines changed

2 files changed

+115
-23
lines changed

src/Recorders/Servers.php

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ class Servers
1515
{
1616
use Concerns\Throttling;
1717

18+
/**
19+
* Callback to detect CPU usage.
20+
*
21+
* @var null|(callable(): int)
22+
*/
23+
protected static $detectCpuUsing;
24+
25+
/**
26+
* Callback to detect memory.
27+
*
28+
* @var null|(callable(): array{total: int, used: int})
29+
*/
30+
protected static $detectMemoryUsing;
31+
1832
/**
1933
* The events to listen for.
2034
*
@@ -32,6 +46,26 @@ public function __construct(
3246
//
3347
}
3448

49+
/**
50+
* Detect CPU via the given callback.
51+
*
52+
* @param null|(callable(): int) $callback
53+
*/
54+
public static function detectCpuUsing(?callable $callback): void
55+
{
56+
self::$detectCpuUsing = $callback;
57+
}
58+
59+
/**
60+
* Detect memory via the given callback.
61+
*
62+
* @param null|(callable(): array{total: int, used: int}) $callback
63+
*/
64+
public static function detectMemoryUsing(?callable $callback): void
65+
{
66+
self::$detectMemoryUsing = $callback;
67+
}
68+
3569
/**
3670
* Record the system stats.
3771
*/
@@ -41,29 +75,8 @@ public function record(SharedBeat $event): void
4175
$server = $this->config->get('pulse.recorders.'.self::class.'.server_name');
4276
$slug = Str::slug($server);
4377

44-
$memoryTotal = match (PHP_OS_FAMILY) {
45-
'Darwin' => intval(`sysctl hw.memsize | grep -Eo '[0-9]+'` / 1024 / 1024),
46-
'Linux' => intval(`cat /proc/meminfo | grep MemTotal | grep -E -o '[0-9]+'` / 1024),
47-
'Windows' => intval(((int) trim(`wmic ComputerSystem get TotalPhysicalMemory | more +1`)) / 1024 / 1024),
48-
'BSD' => intval(`sysctl hw.physmem | grep -Eo '[0-9]+'` / 1024 / 1024),
49-
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
50-
};
51-
52-
$memoryUsed = match (PHP_OS_FAMILY) {
53-
'Darwin' => $memoryTotal - intval(intval(`vm_stat | grep 'Pages free' | grep -Eo '[0-9]+'`) * intval(`pagesize`) / 1024 / 1024), // MB
54-
'Linux' => $memoryTotal - intval(`cat /proc/meminfo | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB
55-
'Windows' => $memoryTotal - intval(((int) trim(`wmic OS get FreePhysicalMemory | more +1`)) / 1024), // MB
56-
'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
57-
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
58-
};
59-
60-
$cpu = match (PHP_OS_FAMILY) {
61-
'Darwin' => (int) `top -l 1 | grep -E "^CPU" | tail -1 | awk '{ print $3 + $5 }'`,
62-
'Linux' => (int) `top -bn1 | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
63-
'Windows' => (int) trim(`wmic cpu get loadpercentage | more +1`),
64-
'BSD' => (int) `top -b -d 2| grep 'CPU: ' | tail -1 | awk '{print$10}' | grep -Eo '[0-9]+\.[0-9]+' | awk '{ print 100 - $1 }'`,
65-
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
66-
};
78+
['total' => $memoryTotal, 'used' => $memoryUsed] = $this->memory();
79+
$cpu = $this->cpu();
6780

6881
$this->pulse->record('cpu', $slug, $cpu, $event->time)->avg()->onlyBuckets();
6982
$this->pulse->record('memory', $slug, $memoryUsed, $event->time)->avg()->onlyBuckets();
@@ -82,4 +95,55 @@ public function record(SharedBeat $event): void
8295
], flags: JSON_THROW_ON_ERROR), $event->time);
8396
});
8497
}
98+
99+
/**
100+
* CPU usage.
101+
*/
102+
protected function cpu(): int
103+
{
104+
if (self::$detectCpuUsing) {
105+
return (self::$detectCpuUsing)();
106+
}
107+
108+
return match (PHP_OS_FAMILY) {
109+
'Darwin' => (int) `top -l 1 | grep -E "^CPU" | tail -1 | awk '{ print $3 + $5 }'`,
110+
'Linux' => (int) `top -bn1 | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
111+
'Windows' => (int) trim(`wmic cpu get loadpercentage | more +1`),
112+
'BSD' => (int) `top -b -d 2| grep 'CPU: ' | tail -1 | awk '{print$10}' | grep -Eo '[0-9]+\.[0-9]+' | awk '{ print 100 - $1 }'`,
113+
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
114+
};
115+
}
116+
117+
/**
118+
* Memory usage.
119+
*
120+
* @return array{total: int, used: int}
121+
*/
122+
protected function memory(): array
123+
{
124+
if (self::$detectMemoryUsing) {
125+
return (self::$detectMemoryUsing)();
126+
}
127+
128+
$memoryTotal = match (PHP_OS_FAMILY) {
129+
'Darwin' => intval(`sysctl hw.memsize | grep -Eo '[0-9]+'` / 1024 / 1024),
130+
'Linux' => intval(`cat /proc/meminfo | grep MemTotal | grep -E -o '[0-9]+'` / 1024),
131+
'Windows' => intval(((int) trim(`wmic ComputerSystem get TotalPhysicalMemory | more +1`)) / 1024 / 1024),
132+
'BSD' => intval(`sysctl hw.physmem | grep -Eo '[0-9]+'` / 1024 / 1024),
133+
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
134+
};
135+
136+
$memoryUsed = match (PHP_OS_FAMILY) {
137+
'Darwin' => $memoryTotal - intval(intval(`vm_stat | grep 'Pages free' | grep -Eo '[0-9]+'`) * intval(`pagesize`) / 1024 / 1024), // MB
138+
'Linux' => $memoryTotal - intval(`cat /proc/meminfo | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB
139+
'Windows' => $memoryTotal - intval(((int) trim(`wmic OS get FreePhysicalMemory | more +1`)) / 1024), // MB
140+
'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
141+
default => throw new RuntimeException('The pulse:check command does not currently support '.PHP_OS_FAMILY),
142+
};
143+
144+
return [
145+
'total' => $memoryTotal,
146+
'used' => $memoryUsed,
147+
];
148+
}
85149
}

tests/Feature/Recorders/ServersTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,31 @@
3434
expect($aggregates->pluck('key')->unique()->values()->all())->toBe(['foo']);
3535
expect($aggregates->pluck('aggregate')->unique()->values()->all())->toBe(['avg']);
3636
});
37+
38+
it('can customise CPU and memory resolution', function () {
39+
Config::set('pulse.recorders.'.Servers::class.'.server_name', 'Foo');
40+
Date::setTestNow(Date::now()->startOfMinute());
41+
42+
Servers::detectCpuUsing(fn () => 987654321);
43+
Servers::detectMemoryUsing(fn () => [
44+
'total' => 123456789,
45+
'used' => 1234,
46+
]);
47+
event(new SharedBeat(CarbonImmutable::now(), 'instance-id'));
48+
Pulse::ingest();
49+
50+
$value = Pulse::ignore(fn () => DB::table('pulse_values')->sole());
51+
52+
$payload = json_decode($value->value);
53+
expect($payload->cpu)->toBe(987654321);
54+
expect($payload->memory_used)->toBe(1234);
55+
expect($payload->memory_total)->toBe(123456789);
56+
57+
$aggregates = Pulse::ignore(fn () => DB::table('pulse_aggregates')->get());
58+
expect($aggregates->count())->toBe(8);
59+
expect($aggregates->pluck('type')->unique()->values()->all())->toBe(['cpu', 'memory']);
60+
expect($aggregates->pluck('value')->unique()->values()->all())->toEqual(['987654321.00', '1234.00']);
61+
62+
Servers::detectCpuUsing(null);
63+
Servers::detectMemoryUsing(null);
64+
});

0 commit comments

Comments
 (0)