Skip to content

Commit f4da63d

Browse files
initial commit
1 parent 4fe5b35 commit f4da63d

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Remote Server for Laravel Pulse
2+
3+
Add remote linux server to your server stats. This is meant for servers that do not run PHP, e.g. database or cache servers. Servers that run PHP should install their own instance of Laravel Pulse instead.
4+
5+
## Installation
6+
7+
Install the package using Composer:
8+
9+
```shell
10+
composer require wrklst/pulse-remote-server
11+
```
12+
13+
## Register the recorder
14+
15+
In your `pulse.php` configuration file, register the RemoteServerRecorder with the desired settings:
16+
17+
```php
18+
return [
19+
// ...
20+
21+
'recorders' => [
22+
\WrkLst\Pulse\RemoteServer\RemoteServerRecorder::class => [
23+
'server_name' => "database-server-1",
24+
'server_ssh' => "ssh forge@1.2.3.4",
25+
'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')),
26+
],
27+
]
28+
]
29+
```
30+
31+
Ensure you're running [the `pulse:check` command](https://laravel.com/docs/10.x/pulse#capturing-entries).
32+
33+
34+
And that's it!

composer.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "wrklst/pulse-remote-server",
3+
"description": "A Laravel Pulse Recorder for Remote Servers",
4+
"keywords": ["laravel", "remote", "server"],
5+
"type": "library",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Tobias Vielmetter-Diekmann",
10+
"email": "tobias@wrklst.art"
11+
}
12+
],
13+
"require": {
14+
"php": "^8.1",
15+
"illuminate/support": "*",
16+
"laravel/pulse": "^1.0.0@beta"
17+
},
18+
"autoload": {
19+
"psr-4": {
20+
"WrkLst\\Pulse\\RemoteServer\\": "src/"
21+
}
22+
}
23+
}

src/Recorders/RemoteServer.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
/*
3+
Requires SSH key authentication in place for authentication to remote server.
4+
Remote Server is assumed to be running Ubuntu Linux.
5+
This is usefull to record server performance for database/cache/queue etc only servers, that do not have pulse installed, which would also require nginx and php etc.
6+
Instead the performance measurement is taken from the app server via ssh remote commands.
7+
*/
8+
9+
namespace WrkLst\Pulse\RemoteServer\Recorders;
10+
11+
use Illuminate\Config\Repository;
12+
use Illuminate\Support\Str;
13+
use Laravel\Pulse\Events\SharedBeat;
14+
use Laravel\Pulse\Pulse;
15+
use RuntimeException;
16+
17+
/**
18+
* @internal
19+
*/
20+
class RemoteServers
21+
{
22+
/**
23+
* The events to listen for.
24+
*
25+
* @var class-string
26+
*/
27+
public string $listen = SharedBeat::class;
28+
29+
/**
30+
* Create a new recorder instance.
31+
*/
32+
public function __construct(
33+
protected Pulse $pulse,
34+
protected Repository $config
35+
) {
36+
//
37+
}
38+
39+
/**
40+
* Record the system stats.
41+
*/
42+
public function record(SharedBeat $event): void
43+
{
44+
// run every 30 seconds, comapared to than every 15 seconds (what is the default for the Pulse Servers recorder)
45+
// this is to reduce the amount of ssh commands (4 per run)
46+
if ($event->time->second % 30 !== 0) {
47+
return;
48+
}
49+
50+
$remote_ssh = $this->config->get('pulse.recorders.' . self::class . '.server_ssh');
51+
$server = $this->config->get('pulse.recorders.' . self::class . '.server_name');
52+
$slug = Str::slug($server);
53+
54+
/*
55+
This needs to be optomized to reduce the amount of ssh commands fired.
56+
E.g. running all commands with one ssh call with piping a shell script into ssh.
57+
´cat server-stats.sh | ssh 1.2.3.4´
58+
*/
59+
60+
$memoryTotal = match (PHP_OS_FAMILY) {
61+
'Darwin' => intval(`$remote_ssh 'cat /proc/meminfo' | grep MemTotal | grep -Eo '[0-9]+'` / 1024),
62+
'Linux' => intval(`$remote_ssh 'cat /proc/meminfo' | grep MemTotal | grep -E -o '[0-9]+'` / 1024),
63+
default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY),
64+
};
65+
66+
$memoryUsed = match (PHP_OS_FAMILY) {
67+
'Darwin' => $memoryTotal - intval(`$remote_ssh 'cat /proc/meminfo' | grep MemAvailable | grep -Eo '[0-9]+'` / 1024), // MB
68+
'Linux' => $memoryTotal - intval(`$remote_ssh 'cat /proc/meminfo' | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB
69+
default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY),
70+
};
71+
72+
$cpu = match (PHP_OS_FAMILY) {
73+
'Darwin' => (int) `$remote_ssh 'top -bn1' | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
74+
'Linux' => (int) `$remote_ssh 'top -bn1' | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`,
75+
default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY),
76+
};
77+
78+
$this->pulse->record('cpu', $slug, $cpu, $event->time)->avg()->onlyBuckets();
79+
$this->pulse->record('memory', $slug, $memoryUsed, $event->time)->avg()->onlyBuckets();
80+
$this->pulse->set('system', $slug, json_encode([
81+
'name' => $server,
82+
'cpu' => $cpu,
83+
'memory_used' => $memoryUsed,
84+
'memory_total' => $memoryTotal,
85+
'storage' => collect($this->config->get('pulse.recorders.' . self::class . '.directories')) // @phpstan-ignore argument.templateType argument.templateType
86+
->map(function (string $directory) use ($remote_ssh) {
87+
$storage = match (PHP_OS_FAMILY) {
88+
'Darwin' => (`$remote_ssh 'df $directory'`),
89+
'Linux' => (`$remote_ssh 'df $directory'`),
90+
default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY),
91+
};
92+
93+
/*
94+
Filesystem 1K-blocks Used Available Use% Mounted on
95+
/dev/root 507930276 31400452 476513440 7% /
96+
*/
97+
98+
$storage = explode("\n", $storage); // break in lines
99+
$storage = preg_replace('/\s+/', ' ', $storage[1]); // replace multi space with single space
100+
$storage = explode(" ", $storage); // break into segments based on sigle space
101+
102+
$storageTotal = $storage[2] + $storage[3]; // used and availble
103+
$storageUsed = $storage[2]; // used
104+
105+
return [
106+
'directory' => $directory,
107+
'total' => intval(round($storageTotal / 1024)), // MB
108+
'used' => intval(round($storageUsed / 1024)), // MB
109+
];
110+
})
111+
->all(),
112+
], flags: JSON_THROW_ON_ERROR), $event->time);
113+
}
114+
}

0 commit comments

Comments
 (0)