diff --git a/README.md b/README.md new file mode 100644 index 0000000..a826e30 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Remote Server for Laravel Pulse + +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. + +## Installation + +Install the package using Composer: + +```shell +composer require wrklst/pulse-remote-server +``` + +## Register the recorder + +In your `pulse.php` configuration file, register the RemoteServerRecorder with the desired settings: + +```php +return [ + // ... + + 'recorders' => [ + \WrkLst\Pulse\RemoteServer\RemoteServerRecorder::class => [ + 'server_name' => "database-server-1", + 'server_ssh' => "ssh forge@1.2.3.4", + 'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')), + ], + ] +] +``` + +Ensure you're running [the `pulse:check` command](https://laravel.com/docs/10.x/pulse#capturing-entries). + + +And that's it! diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..63bf61d --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "wrklst/pulse-remote-server", + "description": "A Laravel Pulse Recorder for Remote Servers", + "keywords": ["laravel", "remote", "server"], + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Tobias Vielmetter-Diekmann", + "email": "tobias@wrklst.art" + } + ], + "require": { + "php": "^8.1", + "illuminate/support": "*", + "laravel/pulse": "^1.0.0@beta" + }, + "autoload": { + "psr-4": { + "WrkLst\\Pulse\\RemoteServer\\": "src/" + } + } +} diff --git a/src/Recorders/RemoteServer.php b/src/Recorders/RemoteServer.php new file mode 100644 index 0000000..a68b7b3 --- /dev/null +++ b/src/Recorders/RemoteServer.php @@ -0,0 +1,114 @@ +time->second % 30 !== 0) { + return; + } + + $remote_ssh = $this->config->get('pulse.recorders.' . self::class . '.server_ssh'); + $server = $this->config->get('pulse.recorders.' . self::class . '.server_name'); + $slug = Str::slug($server); + + /* + This needs to be optomized to reduce the amount of ssh commands fired. + E.g. running all commands with one ssh call with piping a shell script into ssh. + ´cat server-stats.sh | ssh 1.2.3.4´ + */ + + $memoryTotal = match (PHP_OS_FAMILY) { + 'Darwin' => intval(`$remote_ssh 'cat /proc/meminfo' | grep MemTotal | grep -Eo '[0-9]+'` / 1024), + 'Linux' => intval(`$remote_ssh 'cat /proc/meminfo' | grep MemTotal | grep -E -o '[0-9]+'` / 1024), + default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY), + }; + + $memoryUsed = match (PHP_OS_FAMILY) { + 'Darwin' => $memoryTotal - intval(`$remote_ssh 'cat /proc/meminfo' | grep MemAvailable | grep -Eo '[0-9]+'` / 1024), // MB + 'Linux' => $memoryTotal - intval(`$remote_ssh 'cat /proc/meminfo' | grep MemAvailable | grep -E -o '[0-9]+'` / 1024), // MB + default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY), + }; + + $cpu = match (PHP_OS_FAMILY) { + 'Darwin' => (int) `$remote_ssh 'top -bn1' | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`, + 'Linux' => (int) `$remote_ssh 'top -bn1' | grep -E '^(%Cpu|CPU)' | awk '{ print $2 + $4 }'`, + 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(function (string $directory) use ($remote_ssh) { + $storage = match (PHP_OS_FAMILY) { + 'Darwin' => (`$remote_ssh 'df $directory'`), + 'Linux' => (`$remote_ssh 'df $directory'`), + default => throw new RuntimeException('The pulse:check command does not currently support ' . PHP_OS_FAMILY), + }; + + /* + Filesystem 1K-blocks Used Available Use% Mounted on + /dev/root 507930276 31400452 476513440 7% / + */ + + $storage = explode("\n", $storage); // break in lines + $storage = preg_replace('/\s+/', ' ', $storage[1]); // replace multi space with single space + $storage = explode(" ", $storage); // break into segments based on sigle space + + $storageTotal = $storage[2] + $storage[3]; // used and availble + $storageUsed = $storage[2]; // used + + return [ + 'directory' => $directory, + 'total' => intval(round($storageTotal / 1024)), // MB + 'used' => intval(round($storageUsed / 1024)), // MB + ]; + }) + ->all(), + ], flags: JSON_THROW_ON_ERROR), $event->time); + } +} \ No newline at end of file