Skip to content
Draft
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ php artisan stat:mem

The available stat commands are:

- `stat:cpu`
- `stat:disk`
- `stat:load`
- `stat:mem`
Expand All @@ -20,7 +21,8 @@ The available stat commands are:

Forge Monitor provides alerting for several monitor types:

- `cpu_load` - CPU Load (%)
- `cpu_load` - System Load (%)
- `cpu` - CPU Usage (%)
- `disk` - Free Disk Space (%)
- `free_memory` - Free Memory (%)
- `used_memory` - Used Memory (%)
Expand Down
65 changes: 65 additions & 0 deletions app/Console/Commands/CpuStatCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class CpuStatCommand extends AbstractStatCommand
{
use InteractsWithCli;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'stat:cpu {--E|endpoint= : The endpoint to ping.}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Sample CPU usage.';

/**
* Whether the sample has been taken.
*
* @var bool
*/
protected $sampleTaken;

/**
* The stat type to look for when running the command.
*
* @var array|string
*/
protected $statType = ['cpu'];

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Don't run when no monitors are configured.
if ($this->monitors->isEmpty()) {
$this->verboseInfo("No CPU Load monitors configured...");

return;
}

$this->monitors->each(function ($monitor) {
if (!$this->sampleTaken) {
$monitor->stat()->sample();

$this->sampleTaken = true;
}
})->each(function ($monitor) {
$this->verboseInfo("Testing {$monitor->key}...");

$monitor->stat()->test();
});
}
}
22 changes: 22 additions & 0 deletions app/CpuUsage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class CpuUsage extends Model
{
/**
* The name of the table in which this Model stored.
*
* @var string
*/
protected $table = 'cpu_usage';

/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
}
2 changes: 2 additions & 0 deletions app/Monitors/Monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Monitors;

use App\Stats\CpuLoad;
use App\Stats\CpuUsage;
use App\Stats\DiskSpace;
use App\Stats\FreeMemory;
use App\Stats\LoadAvg;
Expand Down Expand Up @@ -74,6 +75,7 @@ public function __construct($key, $type, $operator, $threshold, $minutes, $token
public function stat()
{
switch ($this->type) {
case 'cpu': return new CpuUsage($this);
case 'disk': return new DiskSpace($this);
case 'cpu_load': return new LoadAvg($this);
case 'free_memory': return new FreeMemory($this);
Expand Down
92 changes: 92 additions & 0 deletions app/Stats/CpuUsage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace App\Stats;

use App\CpuUsage as CpuUsageModel;
use App\Monitors\Monitor;
use Illuminate\Support\Facades\DB;

class CpuUsage extends AbstractStat implements Stat
{
/**
* Create a new Stat instance.
*
* @param \App\Monitors\Monitor $monitor
* @return void
*/
public function __construct(Monitor $monitor)
{
$this->monitor = $monitor;
}

/**
* Sample the stat.
*
* @return void
*/
public function sample()
{
/*
|--------------------------------------------------------------------------
| /proc/stat
|--------------------------------------------------------------------------
|
| /proc/stat is actually the Linux kernel's system statistics pool, where
| the kernel writes immediate statistics about its behavior. The first
| line of the file has the format
| cpuN user nice system idle io_wait irq soft_irq steal guest guest_nice
| awk: $2 $3 $4 $5 $6 $7 $8 $9
|
| Where user, nice, and so forth are expressed in number of jiffies
| (approx 1/100th of a second) spent running processes in those
| categories.
|
| The first line of /proc/stat is an aggregation of all CPU cores, so we
| can use the one line as opposed to reading x lines and summing them
| ourselves.
|
| We calculate how much the CPU is idle from the stats
| thus we can determine how busy the CPU really is.
|
| See man 5 proc for more information.
|
*/

if (is_readable("/proc/stat")) {
$cpuIdle = (float) $this->executeCommand("grep 'cpu ' /proc/stat | awk '{idle=($5*100)/($2+$3+$4+$5+$6+$7+$8+$9)} END {print idle}'");
$cpuUsage = (float) 100 - $cpuIdle;

CpuUsageModel::create([
'used' => $cpuUsage,
'idle' => $cpuIdle,
]);
}
}

/**
* Test the stat.
*
* @return bool
*/
public function test()
{
$op = $this->getOperator();

$results = DB::select("SELECT
CASE WHEN used {$op} ? THEN 'ALERT' ELSE 'OK' END AS currentState,
IFNULL(alerts.monitor_state, 'UNKNOWN') AS lastState
FROM (
SELECT * FROM cpu_usage WHERE created_at >= DATETIME('NOW', ?) ORDER BY created_at DESC LIMIT ?
) _samples
LEFT JOIN (SELECT * FROM alerts WHERE monitor_id = ? AND monitor_type = ? ORDER BY created_at DESC LIMIT 1) alerts", [
$this->monitor->threshold,
'-'.($this->monitor->minutes + 1).' minutes',
$this->monitor->minutes + 1,
$this->monitor->key,
$this->monitor->type,
]);

return $this->testResults($results);
}

}
35 changes: 35 additions & 0 deletions database/migrations/2022_07_26_183629_create_cpu_usage_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCpuUsageTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cpu_usage', function (Blueprint $table) {
$table->id();
$table->float('used', 5, 2);
$table->float('idle', 5, 2);
$table->timestamps();

$table->index('created_at');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('cpu_usages');
}
}