Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasvielmetter committed Feb 19, 2024
1 parent 4fe5b35 commit f4da63d
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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!
23 changes: 23 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -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/"
}
}
}
114 changes: 114 additions & 0 deletions src/Recorders/RemoteServer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/*
Requires SSH key authentication in place for authentication to remote server.
Remote Server is assumed to be running Ubuntu Linux.
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.
Instead the performance measurement is taken from the app server via ssh remote commands.
*/

namespace WrkLst\Pulse\RemoteServer\Recorders;

use Illuminate\Config\Repository;
use Illuminate\Support\Str;
use Laravel\Pulse\Events\SharedBeat;
use Laravel\Pulse\Pulse;
use RuntimeException;

/**
* @internal
*/
class RemoteServers
{
/**
* The events to listen for.
*
* @var class-string
*/
public string $listen = SharedBeat::class;

/**
* Create a new recorder instance.
*/
public function __construct(
protected Pulse $pulse,
protected Repository $config
) {
//
}

/**
* Record the system stats.
*/
public function record(SharedBeat $event): void
{
// run every 30 seconds, comapared to than every 15 seconds (what is the default for the Pulse Servers recorder)
// this is to reduce the amount of ssh commands (4 per run)
if ($event->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);
}
}

0 comments on commit f4da63d

Please sign in to comment.