diff --git a/app/HasStatus.php b/app/HasStatus.php new file mode 100644 index 0000000000000000000000000000000000000000..5b0afb928f292ddc1b6295dfe0fa336cf99dff79 --- /dev/null +++ b/app/HasStatus.php @@ -0,0 +1,12 @@ +<?php + +namespace App; + +/** + * + * @author tibo + */ +interface HasStatus +{ + public function status() : Status; +} diff --git a/app/Report.php b/app/Report.php new file mode 100644 index 0000000000000000000000000000000000000000..b6d4809f40da311e6de485bc63adf539a9208fb7 --- /dev/null +++ b/app/Report.php @@ -0,0 +1,60 @@ +<?php + +namespace App; + +/** + * Status report + * + * @author tibo + */ +class Report implements HasStatus +{ + + private $name; + private $status; + private $html = ""; + + /** + * Name is mandatory. + * Default status is "Unknown" + */ + public function __construct(string $name, ?Status $status = null, ?string $html = "") + { + $this->name = $name; + $this->status = $status; + $this->html = $html; + + if ($status == null) { + $this->status = Status::unknown(); + } else { + $this->status = $status; + } + } + + public function setStatus(Status $status) : Report + { + $this->status = $status; + return $this; + } + + public function setHTML(string $html) : Report + { + $this->html = $html; + return $this; + } + + public function name() : string + { + return $this->name; + } + + public function status() : Status + { + return $this->status; + } + + public function html() : string + { + return $this->html; + } +} diff --git a/app/Sensor.php b/app/Sensor.php index e60166e7c2d50f69fcc085c254792e0516ef57b2..abf3d2fe1f3791bf00c14a2630bea7ba1c4d7a96 100644 --- a/app/Sensor.php +++ b/app/Sensor.php @@ -2,44 +2,14 @@ namespace App; +use Illuminate\Database\Eloquent\Collection; + /** - * Base (abstract) class for sensors. + * Sensors must analyze a collection of Record, and produce a Report. * * @author tibo */ -abstract class Sensor +interface Sensor { - - private $server; - - public function __construct(?Server $server = null) - { - $this->server = $server; - } - - protected function server() : Server - { - return $this->server; - } - - /** - * Get the name of the sensor. Can be overridden by sub-classes to provide - * a more meaningful name. - * - * @return string - */ - public function name() : string - { - return (new \ReflectionClass($this))->getShortName(); - } - - /** - * Compute the status code from an array of Record. - */ - abstract public function status(array $records) : int; - - /** - * Create the HTML report describing the result of this sensor's analysis. - */ - abstract public function report(array $records) : string; + public function analyze(Collection $records, ServerInfo $serverinfo) : Report; } diff --git a/app/Sensor/CPUtemperature.php b/app/Sensor/CPUtemperature.php index d22b89c045f75d74f066e46163f2ef9b568d12b0..b53368cdc466e3bba3ea9c743111eae9be07458d 100644 --- a/app/Sensor/CPUtemperature.php +++ b/app/Sensor/CPUtemperature.php @@ -2,145 +2,71 @@ namespace App\Sensor; -use \App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Description of Update * * @author helha */ -class CPUtemperature extends Sensor +class CPUtemperature implements Sensor { - - const REGEXP = "/^(Core \d+):\s+\+(\d+\.\d+)/m"; + // Match a CPU line like + // Package id 0: +39.0°C (high = +84.0°C, crit = +100.0°C) const REGEXPCPU= "/^(Package id)+\s+(\d):\s+\+(\d+\.\d+)°C\s+\(high\s=\s\+\d+\.\d°C,\scrit\s=\s\+(\d+\.\d+)°C\)/m"; + + // Mach a core line + // Core 0: +38.0°C (high = +84.0°C, crit = +100.0°C) + const REGEXPCORE = "/^(Core \d+):\s+\+(\d+\.\d+)°C\s+\(high\s=\s\+\d+\.\d°C,\scrit\s=\s\+(\d+\.\d+)°C\)/m"; - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("CPU temperature"); + + $record = $records->last(); if (! isset($record->data["cpu-temperature"])) { - return "<p>No data available...</p>" + return $report->setHTML("<p>No data available...</p>" . "<p>Maybe <code>sensors</code> is not installed.</p>" - . "<p>You can install it with <code>sudo apt install lm-sensors</code></p>"; + . "<p>You can install it with <code>sudo apt install lm-sensors</code></p>"); } - $Cores = self::parseCPUtemperature($record->data['cpu-temperature']); - $CPUS = self::parseCPU($record->data['cpu-temperature']); - $return = "<table class='table table-sm'>"; - $return .= "<tr><th>Name</th><th>Temperature (°C)</th><th>T°crit (°C)</th></tr>"; + $cpus = $this->parse($record->data['cpu-temperature']); + $report->setHTML(view("sensor.cputemperature", ["cpus" => $cpus])); + $report->setStatus(Status::max($cpus)); - foreach ($CPUS as $CPU) { - $return .= "<tr><td>" . "<b>" ."CPU " . $CPU->number . "</td><td>" - . "<b>" . $CPU->value . "</td><td>" . "<b>" . $CPU->critvalue . "</td></tr>"; - foreach ($Cores as $Core) { - if ($Core->number == $CPU->number) { - $return .= "<tr><td>" . $Core->name . "</td><td>" - . $Core->corevalue . "</td><td>" . " " . "</td></tr>"; - } - } - } - $return .= "</table>"; - return $return; + return $report; } - public function status(array $records) : int + public function parse(string $string) { - $record = end($records); - if (! isset($record->data["cpu-temperature"])) { - return \App\Status::UNKNOWN; - } - - $all_status = []; - foreach (self::parseCPU($record->data['cpu-temperature']) as $CPU) { - /* @var $CPU Cpu */ - $status = \App\Status::OK; - if ($CPU->value > $CPU->critvalue) { - $status = \App\Status::WARNING; - } - foreach (self::parseCPUtemperature($record->data['cpu-temperature']) as $Core) { - if ($Core->number == $CPU->number) { - if ($Core->value > $CPU->critvalue) { - $status = \App\Status::WARNING; - } - } - } - $all_status[] = $status; - } - - if (count($all_status) < 1) { - return \App\Status::UNKNOWN; - } - - return max($all_status); - } - - public static function parse(string $string) //cores only - { - $values = array(); - preg_match_all(self::REGEXP, $string, $values); - $temperatures = array(); - $count = count($values[1]); - for ($i = 0; $i < $count; $i++) { - $CPUTemp = new Temperature(); - $CPUTemp->name = $values[1][$i]; - $CPUTemp->value = $values[2][$i]; - $temperatures[] = $CPUTemp; - } - return $temperatures; - } - - public static function parseCPU(string $string) //cpus only - { - $values = array(); - preg_match_all(self::REGEXPCPU, $string, $values); - $CPUS = array(); - $count = count($values[1]); - for ($i = 0; $i < $count; $i++) { - $CPU = new Cpu(); - $CPU->number = $values[2][$i]; - $CPU->value = $values[3][$i]; - $CPU->critvalue = $values[4][$i]; - $CPUS[] = $CPU; - } - return $CPUS; - } - public function parseCPUtemperature(string $string) //cores (to associate with cpus only in report() ) - { - if ($string == null) { - return []; - } - - $current_cpu = new Cpu(); - $CPUS=[]; - $Cores=[]; - $lines=explode("\n", $string); + $cpus = []; + $cpu = null; + + $lines = explode("\n", $string); foreach ($lines as $line) { - $matchesCPU = array(); - if (preg_match(self::REGEXPCPU, $line, $matchesCPU) === 1) { - $current_cpu = new Cpu(); - $current_cpu->number = $matchesCPU[2]; - $CPUS[]=$current_cpu; + $match = []; + + // this line corresponds to a CPU definition + if (preg_match(self::REGEXPCPU, $line, $match) === 1) { + $cpu = new Cpu($match[2], $match[3], $match[4]); + $cpus[] = $cpu; continue; } - $matchesCore = array(); - if (preg_match(self::REGEXP, $line, $matchesCore) === 1) { - $Core=new Temperature(); - $Core->name = $matchesCore[1]; - $Core->corevalue = $matchesCore[2]; - $Core->number = $current_cpu->number; - $Cores[] = $Core; + + // line correponds to a core definition + if (preg_match(self::REGEXPCORE, $line, $match) === 1) { + $core = new Core($match[1], $match[2], $match[3]); + // append to current CPU + $cpu->cores[] = $core; continue; } } - return $Cores; - } - public function pregMatchOne($pattern, $string) - { - $matches = array(); - if (preg_match($pattern, $string, $matches) === 1) { - return $matches[1]; - } - - return false; + return $cpus; } } diff --git a/app/Sensor/ClientVersion.php b/app/Sensor/ClientVersion.php index fdf090b2e7d92f87e96d6b78911e7230344b5e74..9137bf1da115823566d5779a61ca149e8039c90a 100644 --- a/app/Sensor/ClientVersion.php +++ b/app/Sensor/ClientVersion.php @@ -2,43 +2,51 @@ namespace App\Sensor; -use App\Sensor; use App\Jobs\FetchClientManifest; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Check if the latest version of the client is installed. * * @author tibo */ -class ClientVersion extends Sensor +class ClientVersion implements Sensor { - public function report(array $records) : string + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - return "<p>Installed version: " . $this->installedVersion($records) . "</p>" - . "<p>Latest client version: " . FetchClientManifest::version() . "</p>"; - } + $latest_version = FetchClientManifest::version(); + $installed_version = $this->installedVersion($records); + + $report = new Report("Client Version"); + $report->setHTML( + "<p>Installed version: $installed_version</p>" . + "<p>Latest client version: $latest_version</p>" + ); + + if ($latest_version == null) { + $report->setStatus(Status::unknown()); + } elseif ($installed_version === $latest_version) { + $report->setStatus(Status::ok()); + } else { + $report->setStatus(Status::warning()); + } - public function installedVersion(array $records) + return $report; + } + + public function installedVersion(Collection $records) : string { - $last_record = end($records); + $last_record = $records->last(); if ($last_record == null) { return "none"; } return $last_record->data["version"]; } - - public function status(array $records) : int - { - $latest_version = FetchClientManifest::version(); - if ($latest_version == null) { - return \App\Status::UNKNOWN; - } - - if ($this->installedVersion($records) === $latest_version) { - return \App\Status::OK; - } - - return \App\Status::WARNING; - } } diff --git a/app/Sensor/Core.php b/app/Sensor/Core.php new file mode 100644 index 0000000000000000000000000000000000000000..10640e58f709c55f65adddfd4fb5d1b29ebf2b33 --- /dev/null +++ b/app/Sensor/Core.php @@ -0,0 +1,35 @@ +<?php + +namespace App\Sensor; + +use App\Status; +use App\HasStatus; + +/** + * Description of Temperature (core) + * + * @author helha + */ + +class Core implements HasStatus +{ + public $name = ""; // eg : core 0 + public $value; // eg : 42.5 + public $critvalue; // eg : 76.0 + + public function __construct(string $name, float $value, float $critvalue) + { + $this->name = $name; + $this->value = $value; + $this->critvalue = $critvalue; + } + + public function status() : Status + { + if ($this->value >= $this->critvalue) { + return Status::warning(); + } + + return Status::ok(); + } +} diff --git a/app/Sensor/Cpu.php b/app/Sensor/Cpu.php index 111921ef7e3a5e63267b7b5241f42f55c2a449fe..fbb425f20357bbed82e64d6262f6dcd4a0dfc604 100644 --- a/app/Sensor/Cpu.php +++ b/app/Sensor/Cpu.php @@ -2,14 +2,23 @@ namespace App\Sensor; +use App\Status; + /** * Description of Cpu * * @author helha */ -class Cpu +class Cpu extends Core { - public $number = ""; //eg : 1,2,3,4,... - public $value= ""; //eg : 38.0 - public $critvalue= ""; //eg : 76.0 + + public $cores = []; + + public function status() : Status + { + return max( + Status::max($this->cores), + parent::status() + ); + } } diff --git a/app/Sensor/Date.php b/app/Sensor/Date.php index bc78b4f49589c42d92bb7afb1442fdf7054ab6d4..a77c7d9566ceabfe8202ab119d6ff9abef52d455 100644 --- a/app/Sensor/Date.php +++ b/app/Sensor/Date.php @@ -2,46 +2,37 @@ namespace App\Sensor; -use \App\Sensor; -use App\Record; +use App\Sensor; use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Description of Reboot * * @author tibo */ -class Date extends Sensor +class Date implements Sensor { - public function report(array $records) : string - { - return "<p>Time drift: " . $this->delta(end($records)) . " seconds</p>"; - } - - public function status(array $records) : int + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - if (count($records) == 0) { - return Status::UNKNOWN; - } + $report = new Report("Time drift"); + /** @var \App\Record $last_record */ + $last_record = $records->last(); - $delta = $this->delta(end($records)); - if ($delta == null) { - return Status::UNKNOWN; + if (! isset($last_record->data["date"])) { + return $report->setHTML("<p>No data available ...</p>"); } - + + $delta = $last_record->data["date"] - $last_record->time; + $report->setHTML("<p>Time drift: $delta seconds</p>"); + if (abs($delta) > 10) { - return Status::WARNING; - } - - return Status::OK; - } - - public function delta(Record $record) - { - if (! isset($record->data["date"])) { - return null; + return $report->setStatus(Status::warning()); } - return $record->data["date"] - $record->time; + return $report->setStatus(Status::ok()); } } diff --git a/app/Sensor/Disk.php b/app/Sensor/Disk.php index 2600702ac89b76cec065c7388f58ba60843e0c81..2816fc21f77a0391616cee8c2074f8f72fe8f0d9 100644 --- a/app/Sensor/Disk.php +++ b/app/Sensor/Disk.php @@ -2,17 +2,30 @@ namespace App\Sensor; +use App\Status; +use App\HasStatus; + /** * Description of Disk * * @author tibo */ -class Disk +class Disk implements HasStatus { public $port = ""; public $box = 0; public $bay = 0; public $type = ""; public $size = ""; - public $status = ""; + + /** + * + * @var \App\Status + */ + public $status; + + public function status() : Status + { + return $this->status; + } } diff --git a/app/Sensor/Disks.php b/app/Sensor/Disks.php index ccfe11577f36dd42bb91c45204b059a5b5d7db36..81786a3c58d9e8c0e6eacd4073cf69e96afaecd7 100644 --- a/app/Sensor/Disks.php +++ b/app/Sensor/Disks.php @@ -2,54 +2,39 @@ namespace App\Sensor; -use \App\Sensor; +use App\Sensor; +use App\ServerInfo; +use App\Report; +use App\Status; + +use Illuminate\Database\Eloquent\Collection; /** * Description of Update * * @author tibo */ -class Disks extends Sensor +class Disks implements Sensor { const REGEXP = "/\\n([A-z\/0-9:\\-\\.]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)%\s*([A-z\/0-9]+)/"; - - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("Partitions"); + + $record = $records->last(); if (! isset($record->data['disks'])) { - return "<p>No data available...</p>"; + return $report->setHTML("<p>No data available...</p>"); } - $partitions = self::parse($record->data["disks"]); - return view("sensor.disks", ["partitions" => $partitions]); + $partitions = $this->parse($record->data["disks"]); + $report->setHTML(view("sensor.disks", ["partitions" => $partitions])); + + return $report->setStatus(Status::max($partitions)); } - public function status(array $records) : int - { - $record = end($records); - if (! isset($record->data['disks'])) { - return \App\Status::UNKNOWN; - } - - $all_status = []; - foreach (self::parse($record->data["disks"]) as $partition) { - /* @var $partition Partition */ - $status = \App\Status::OK; - if ($partition->usedPercent() > 80) { - $status = \App\Status::WARNING; - } elseif ($partition->usedPercent() > 95) { - $status = \App\Status::ERROR; - } - $all_status[] = $status; - } - - return max($all_status); - } - - public static $skip_fs = ["none", "tmpfs", "shm", "udev", "overlay", '/dev/loop']; - - public static function parse(string $string) : array + public function parse(string $string) : array { $values = array(); preg_match_all(self::REGEXP, $string, $values); @@ -57,7 +42,7 @@ class Disks extends Sensor $count = count($values[1]); for ($i = 0; $i < $count; $i++) { $fs = $values[1][$i]; - if (self::shouldSkip($fs)) { + if ($this->shouldSkip($fs)) { continue; } @@ -71,9 +56,9 @@ class Disks extends Sensor return $partitions; } - public static function fromRecord($record) : array + public function fromRecord($record) : array { - $partitions = self::parse($record->data["disks"]); + $partitions = $this->parse($record->data["disks"]); $time = $record->time; foreach ($partitions as $partition) { $partition->time = $time; @@ -81,11 +66,13 @@ class Disks extends Sensor return $partitions; } - - public static function shouldSkip(string $fs) : bool + + const SKIP_FS = ["none", "tmpfs", "shm", "udev", "overlay", '/dev/loop']; + + public function shouldSkip(string $fs) : bool { - foreach (self::$skip_fs as $should_skip) { - if (self::startsWith($should_skip, $fs)) { + foreach (self::SKIP_FS as $should_skip) { + if ($this->startsWith($should_skip, $fs)) { return true; } } @@ -93,7 +80,7 @@ class Disks extends Sensor return false; } - public static function startsWith(string $needle, string $haystack) : bool + public function startsWith(string $needle, string $haystack) : bool { return substr($haystack, 0, strlen($needle)) === $needle; } diff --git a/app/Sensor/Heartbeat.php b/app/Sensor/Heartbeat.php index bf25216faf96706d4d368d33c7b0369ffb3e755c..3064e6b9fd4e887578fd5b28de3aa2d0427df305 100644 --- a/app/Sensor/Heartbeat.php +++ b/app/Sensor/Heartbeat.php @@ -2,51 +2,36 @@ namespace App\Sensor; -use \App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + +use Carbon\Carbon; /** * Description of Reboot * * @author tibo */ -class Heartbeat extends Sensor +class Heartbeat implements Sensor { - //put your code here - public function report(array $records) : string - { - return "<p>Last heartbeat received " - . $this->lastRecordTime(end($records))->diffForHumans() . "</p>"; - } - - /** - * - * @return \Carbon\Carbon - */ - public function lastRecordTime($record) : \Carbon\Carbon + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - if ($record === null) { - return \Carbon\Carbon::createFromTimestamp(0); - } - - return \Carbon\Carbon::createFromTimestamp($record->time); - } - - - public function status(array $records) : int - { - $record = end($records); - - if ($record === null) { - $delta = PHP_INT_MAX; - } else { - $delta = \time() - $record->time; - } - + $report = new Report("Heartbeat"); + + $record = $records->last(); + $report->setHTML("<p>Last heartbeat received " + . Carbon::createFromTimestamp($record->time)->diffForHumans() . "</p>"); + + $delta = \time() - $record->time; // > 15 minutes if ($delta > 900) { - return \App\Status::ERROR; + return $report->setStatus(Status::error()); } - - return \App\Status::OK; + + return $report->setStatus(Status::ok()); } } diff --git a/app/Sensor/Ifconfig.php b/app/Sensor/Ifconfig.php index 60b198717ab31cef4e55112f90b067e2e56c478d..65e7c8a3343f2d08148c491d8d5c8d6ee49915d6 100644 --- a/app/Sensor/Ifconfig.php +++ b/app/Sensor/Ifconfig.php @@ -2,29 +2,36 @@ namespace App\Sensor; -use \App\Sensor; -use \App\Record; +use App\Sensor; +use App\Status; +use App\Record; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Description of MemInfo * * @author tibo */ -class Ifconfig extends Sensor +class Ifconfig implements Sensor { - - public function report(array $records) : string + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); - if (! isset($record->data['ifconfig'])) { - return "<p>No data available...</p>"; + $report = new Report("Ifconfig"); + + $last_record = $records->last(); + if (! isset($last_record->data['ifconfig'])) { + return $report->setHTML("<p>No data available...</p>"); } - $interfaces = $this->parseIfconfigRecord($record); - return view("agent.ifconfig", ["interfaces" => $interfaces]); + $interfaces = $this->parseIfconfigRecord($last_record); + return $report->setStatus(Status::ok()) + ->setHTML(view("agent.ifconfig", ["interfaces" => $interfaces])); } - public function points(array $records) + public function points(Collection $records) { // Compute the time ordered list of arrays of interfaces $interfaces = []; @@ -85,11 +92,6 @@ class Ifconfig extends Sensor return array_values($dataset); } - public function status(array $records) : int - { - return \App\Status::OK; - } - const IFNAME = '/^(?|(\S+)\s+Link encap:|(\S+): flags)/m'; const IPV4 = '/^\s+inet (?>addr:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/m'; const RXTX = '/^\s+RX bytes:(\d+) .*TX bytes:(\d+)/m'; @@ -114,7 +116,6 @@ class Ifconfig extends Sensor */ public function parseIfconfig(string $string) : array { - $allowed_prefixes = ["en", "eth", "wl", "venet"]; if ($string == null) { diff --git a/app/Sensor/Inodes.php b/app/Sensor/Inodes.php index 44e3017c5816f101011e331906ea492ea6a47e81..cbc88f7d5776f48cc92463a8c006a2c3ee08cf06 100644 --- a/app/Sensor/Inodes.php +++ b/app/Sensor/Inodes.php @@ -2,55 +2,37 @@ namespace App\Sensor; +use App\Sensor; +use App\ServerInfo; +use App\Report; +use App\Status; + +use Illuminate\Database\Eloquent\Collection; + /** * Description of Update * * @author tibo */ -class Inodes extends \App\Sensor +class Inodes implements Sensor { const REGEXP = "/\\n([A-z\/0-9:\\-\\.]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)%\s*([A-z\/0-9]+)/"; - - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("Inodes"); + + $record = $records->last(); + if (! isset($record->data['inodes'])) { - return "<p>No data available...</p>"; + return $report->setHTML("<p>No data available...</p>"); } $disks = $this->parse($record->data["inodes"]); - $return = "<table class='table table-sm'>"; - $return .= "<tr><th></th><th></th><th>Usage</th></tr>"; - foreach ($disks as $disk) { - $return .= "<tr><td>" . $disk->filesystem . "</td><td>" - . $disk->mounted . "</td><td>" . $disk->usedPercent() - . "%</td></tr>"; - } - $return .= "</table>"; - return $return; - } - - public function status(array $records) : int - { - $record = end($records); - if (! isset($record->data['inodes'])) { - return \App\Status::UNKNOWN; - } - - $all_status = []; - foreach ($this->parse($record->data["inodes"]) as $disk) { - /* @var $disk InodesDisk */ - $status = \App\Status::OK; - if ($disk->usedPercent() > 80) { - $status = \App\Status::WARNING; - } elseif ($disk->usedPercent() > 95) { - $status = \App\Status::ERROR; - } - $all_status[] = $status; - } - - return max($all_status); + $report->setHTML(view("sensor.inodes", ["disks" => $disks])); + + return $report->setStatus(Status::max($disks)); } public function parse(string $string) @@ -59,9 +41,13 @@ class Inodes extends \App\Sensor preg_match_all(self::REGEXP, $string, $values); $disks = array(); $count = count($values[1]); + + $disks_sensor = new Disks(); + for ($i = 0; $i < $count; $i++) { $fs = $values[1][$i]; - if (Disks::shouldSkip($fs)) { + + if ($disks_sensor->shouldSkip($fs)) { continue; } diff --git a/app/Sensor/InodesDisk.php b/app/Sensor/InodesDisk.php index 54d708c4eff46a4e307809dccac68bbedd31abba..e22a5eed2c39ac0f5cfdf88e76756a3ec7f6606e 100644 --- a/app/Sensor/InodesDisk.php +++ b/app/Sensor/InodesDisk.php @@ -1,19 +1,16 @@ <?php -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ - namespace App\Sensor; +use App\Status; +use App\HasStatus; + /** * Description of InodesDisk * * @author tibo */ -class InodesDisk +class InodesDisk implements HasStatus { public $filesystem = ""; public $inodes = 0; @@ -24,4 +21,17 @@ class InodesDisk { return round(100.0 * $this->used / $this->inodes); } + + public function status() : Status + { + if ($this->usedPercent() > 95) { + return Status::error(); + } + + if ($this->usedPercent() > 80) { + return Status::warning(); + } + + return Status::ok(); + } } diff --git a/app/Sensor/ListeningPorts.php b/app/Sensor/ListeningPorts.php index ada94ecc0affa34221a8e8d88c9e46d32ff81bcb..09a859c576b659a4777390e7f4266bc36f4d11c3 100644 --- a/app/Sensor/ListeningPorts.php +++ b/app/Sensor/ListeningPorts.php @@ -2,24 +2,32 @@ namespace App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Parse the output of netstat to list listening ports. * * @author tibo */ -class ListeningPorts extends \App\Sensor +class ListeningPorts implements Sensor { const REGEXP = "/(tcp6|tcp|udp6|udp)\s*\d\s*\d\s*(\S*):(\d*).*LISTEN\s*(\S*)/m"; - - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("Listening Ports"); + $record = $records->last(); // "netstat-listen-tcp" "netstat-listen-udp" if (! isset($record->data["netstat-listen-udp"]) && ! isset($record->data["netstat-listen-tcp"])) { - return "<p>No data available...</p>"; + return $report->setHTML("<p>No data available...</p>"); } $ports = array_merge( @@ -33,29 +41,9 @@ class ListeningPorts extends \App\Sensor return $port1->port - $port2->port; } ); - - $return = "<table class='table table-sm'>"; - $return .= "<tr>" - . "<th>Port</th>" - . "<th>Proto</th>" - . "<th>Bind address</th>" - . "<th>Process</th>" - . "</tr>"; - foreach ($ports as $port) { - $return .= "<tr>" - . "<td>" . $port->port . "</td>" - . "<td>" . $port->proto . "</td>" - . "<td>" . $port->bind . "</td>" - . "<td>" . $port->process . "</td>" - . "</tr>"; - } - $return .= "</table>"; - return $return; - } - - public function status(array $records) : int - { - return \App\Status::OK; + + return $report->setStatus(Status::ok()) + ->setHTML(view("sensor.listeningports", ["ports" => $ports])); } /** diff --git a/app/Sensor/LoadAvg.php b/app/Sensor/LoadAvg.php index 04d2303cb70582c38216668c5ee9c56195df8fa2..286e2c6a8a51e94d3512e5d61c59b6c7bf6a3d3a 100644 --- a/app/Sensor/LoadAvg.php +++ b/app/Sensor/LoadAvg.php @@ -2,37 +2,52 @@ namespace App\Sensor; -use \App\Sensor; -use \App\Status; +use App\Record; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Description of LoadAvg * * @author tibo */ -class LoadAvg extends Sensor +class LoadAvg implements Sensor { - - /** - * - * @param array<\App\Record> $records - * @return string - */ - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); - if (! isset($record->data['loadavg'])) { - return "<p>No data available...</p>"; + $threshold = $serverinfo->cpuinfo()["threads"]; + $report = new Report("Load Average"); + + if (! isset($records->last()->data['loadavg'])) { + return $report->setHTML("<p>No data available...</p>"); + } + + $current_load = $this->parse($records->last()->data["loadavg"]); + $report->setHTML(view("agent.loadavg", ["current_load" => $current_load])); + + $max_load = $records + ->map(function (Record $record) { + $this->parse($record->data["loadavg"]); + }) + ->max(); + + if ($max_load > 2 * $threshold) { + return $report->setStatus(Status::error()); } - $current_load = $this->parse($record->data["loadavg"]); - return view( - "agent.loadavg", - ["current_load" => $current_load] - ); + if ($max_load > $threshold) { + return $report->setStatus(Status::warning()); + } + + return $report->setStatus(Status::ok()); } - public function loadPoints(array $records) + public function loadPoints(Collection $records) { $points = []; foreach ($records as $record) { @@ -44,29 +59,6 @@ class LoadAvg extends Sensor return $points; } - public function status(array $records) : int - { - $threshold = $this->server()->info()->cpuinfo()["threads"]; - - $max = 0; - foreach ($records as $record) { - $load = $this->parse($record->data["loadavg"]); - if ($load > $max) { - $max = $load; - } - } - - if ($max > 2 * $threshold) { - return Status::ERROR; - } - - if ($max > $threshold) { - return Status::WARNING; - } - - return Status::OK; - } - public function parse(string $string) : string { return current(explode(" ", $string)); diff --git a/app/Sensor/MemInfo.php b/app/Sensor/MemInfo.php index 5ea0f8e5c67149f6cb4e5e4545eca9c725f68309..093eef210a9b43fd5ae1a8d0e6b77e4aa4dcce44 100644 --- a/app/Sensor/MemInfo.php +++ b/app/Sensor/MemInfo.php @@ -2,22 +2,37 @@ namespace App\Sensor; -use \App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Description of MemInfo * * @author tibo */ -class MemInfo extends Sensor +class MemInfo implements Sensor { - - public function report(array $records) : string + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - return view("agent.meminfo", []); + $report = new Report("MemInfo"); + $report->setHTML(view("agent.meminfo")); + + foreach ($records as $record) { + $mem = $this->parseMeminfo($record->data["memory"]); + if ($mem->usedRatio() > 0.8) { + return $report->setStatus(Status::WARNING); + } + } + + return $report->setStatus(Status::ok()); } - public function usedMemoryPoints(array $records) + + public function usedMemoryPoints(Collection $records) { $used = []; foreach ($records as $record) { @@ -31,7 +46,7 @@ class MemInfo extends Sensor return $used; } - public function cachedMemoryPoints(array $records) + public function cachedMemoryPoints(Collection $records) { $points = []; foreach ($records as $record) { @@ -45,18 +60,6 @@ class MemInfo extends Sensor return $points; } - public function status(array $records) : int - { - foreach ($records as $record) { - $mem = $this->parseMeminfo($record->data["memory"]); - if ($mem->usedRatio() > 0.8) { - return \App\Status::WARNING; - } - } - - return \App\Status::OK; - } - // used = total - free - cached const MEMTOTAL = "/^MemTotal:\\s+([0-9]+) kB$/m"; const MEMFREE = "/^MemFree:\\s+([0-9]+) kB$/m"; diff --git a/app/Sensor/Netstat.php b/app/Sensor/Netstat.php index 9c7513b6330bd072388efc994e44b7e21da4e24f..89636c983ef4055cba4cbf2e8d53d9276442e270 100644 --- a/app/Sensor/Netstat.php +++ b/app/Sensor/Netstat.php @@ -2,22 +2,33 @@ namespace App\Sensor; -use \App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; /** * Parse netstat * * @author tibo */ -class Netstat extends Sensor +class Netstat implements Sensor { - - public function report(array $records) : string + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - return view("agent.netstat", []); + $report = new Report("Netstat : retransmitted TCP segments"); + $report->setHTML(view("agent.netstat")) + ->setStatus(Status::ok()); + + return $report; } - public function points(array $records) : array + + + public function points(Collection $records) : array { if (count($records) == 0) { return []; @@ -48,11 +59,6 @@ class Netstat extends Sensor return [$dataset]; } - public function status(array $records) : int - { - return \App\Status::OK; - } - const TCP_SENT = '/^ (\d+) segments sent out/m'; const TCP_RETRANSMITTED = '/^ (\d+) segments retransmitted$/m'; diff --git a/app/Sensor/Partition.php b/app/Sensor/Partition.php index 1ea0b5fe9c2a4f19db23c9fd81bd5d1238b4cfe6..19c7385e1c576d3e030c126008c6089e6fc8b641 100644 --- a/app/Sensor/Partition.php +++ b/app/Sensor/Partition.php @@ -2,12 +2,15 @@ namespace App\Sensor; +use App\Status; +use App\HasStatus; + /** * Description of Partition * * @author tibo */ -class Partition +class Partition implements HasStatus { public $filesystem = ""; public $blocks = 0; @@ -34,4 +37,15 @@ class Partition { return (int) round($this->blocks / 1E6); } + + public function status() : Status + { + if ($this->usedPercent() > 80) { + return Status::warning(); + } elseif ($this->usedPercent() > 95) { + return Status::error(); + } + + return Status::ok(); + } } diff --git a/app/Sensor/PartitionDelta.php b/app/Sensor/PartitionDelta.php deleted file mode 100644 index 3208498f095f5c7b875773be447d2fbe093570ce..0000000000000000000000000000000000000000 --- a/app/Sensor/PartitionDelta.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php - -namespace App\Sensor; - -use Carbon\Carbon; - -/** - * Represents the time evolution of a single partition. - */ -class PartitionDelta -{ - private $start; - private $end; - - public function __construct(Partition $start, Partition $end) - { - if ($start->filesystem !== $end->filesystem) { - throw new \Exception("Comparing different filesystems!"); - } - - $this->start = $start; - $this->end = $end; - } - - public function filesystem() : string - { - return $this->start->filesystem; - } - - /** - * Return difference of the number of used blocks. - * @return int - */ - private function deltaBlocks() : int - { - return $this->end->used - $this->start->used; - } - - /** - * Return time difference between 2 partitions - * @return int - */ - private function deltaT() : int - { - return $this->end->time - $this->start->time; - } - - /** - * Time in second, before this partition gets full. - * @return int - */ - public function timeUntillFull() : int - { - if ($this->deltaBlocks() <= 0) { - return PHP_INT_MAX; - } - - return ($this->end->blocks - $this->end->used) / $this->deltaBlocks() - * $this->deltaT(); - } - - public function timeUntilFullForHumans() : string - { - return Carbon::createFromTimeStamp($this->timeUntillFull())->diffForHumans(); - } -} diff --git a/app/Sensor/Perccli.php b/app/Sensor/Perccli.php index 70a3bf1f78f02e2f50ea44d1780906f65003b490..91dad9c99dff3c91da4bb465eba5485e048b891d 100644 --- a/app/Sensor/Perccli.php +++ b/app/Sensor/Perccli.php @@ -2,57 +2,35 @@ namespace App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Description of Ssacli * * @author tibo */ -class Perccli extends \App\Sensor +class Perccli implements Sensor { const REGEXP = "/(\d+:\d+)\s+\d+\s+(\w+)\s+\d+\s+(.*(GB|TB))\s+\w+\s+(\w+)/"; - public function report(array $records) : string - { - $record = end($records); - if (! isset($record->data['perccli'])) { - return "<p>No data available...</p>"; - } - - $drives = $this->parse($record->data["perccli"]); - $return = "<table class='table table-sm'>" - . "<tr>" - . "<th>Slot</th>" - . "<th>Type</th>" - . "<th>Size</th>" - . "<th>Status</th>" - . "</tr>"; - foreach ($drives as $disk) { - $return .= "<tr>" - . "<td>" . $disk->slot . "</td>" - . "<td>" . $disk->type . "</td>" - . "<td>" . $disk->size . "</td>" - . "<td>" . $disk->status . "</td>" - . "</tr>"; - } - $return .= "</table>"; - return $return; - } - - public function status(array $records) : int + public function analyze(Collection $records, ServerInfo $serverinfo): Report { + $report = new Report("DELL perccli"); + $record = end($records); if (! isset($record->data['perccli'])) { - return \App\Status::UNKNOWN; + return $report->setHTML("<p>No data available...</p>"); } - + $drives = $this->parse($record->data["perccli"]); - foreach ($drives as $disk) { - if ($disk->status != "Onln") { - return \App\Status::WARNING; - } - } - - return \App\Status::OK; + $report->setHTML(view("sensor.perccli", ["drives" => $drives])); + + return $report->setStatus(Status::max($drives)); } /** @@ -72,7 +50,7 @@ class Perccli extends \App\Sensor $drive->slot = $values[1][$i]; $drive->type = $values[5][$i]; $drive->size = $values[3][$i]; - $drive->status = $values[2][$i]; + $drive->status = ($values[2][$i] == "Onln") ? Status::ok() : Status::warning(); $drives[] = $drive; } return $drives; diff --git a/app/Sensor/PerccliDrive.php b/app/Sensor/PerccliDrive.php index 2da7836c015ce3ebb1ac22e6630dbb9612220fd5..2181fb897fccd47a05cb7eac53e95a6ae1d342ee 100644 --- a/app/Sensor/PerccliDrive.php +++ b/app/Sensor/PerccliDrive.php @@ -2,15 +2,28 @@ namespace App\Sensor; +use App\Status; +use App\HasStatus; + /** * Represents a single physical drive connected to a Dell RAID controller. * * @author tibo */ -class PerccliDrive +class PerccliDrive implements HasStatus { public $slot; + + /** + * + * @var \App\Status + */ public $status; public $size; public $type; + + public function status() : Status + { + return $this->status; + } } diff --git a/app/Sensor/Reboot.php b/app/Sensor/Reboot.php index 1053b320e2a13e95a8e490b287e9d9df52dbf5ae..d98a949af769331271297d820ab8f264b7e888db 100644 --- a/app/Sensor/Reboot.php +++ b/app/Sensor/Reboot.php @@ -2,51 +2,36 @@ namespace App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Description of Reboot * * @author tibo */ -class Reboot extends \App\Sensor +class Reboot implements \App\Sensor { - - public function report(array $records) : string - { - return "<p>Reboot required: " - . $this->statusHTML($records) - . "</p>"; - } - - public function statusHTML(array $records) - { - switch ($this->status($records)) { - case \App\Status::OK: - return "no"; - - case \App\Status::WARNING: - return "yes"; - - default: - return "?"; - } - } - - public function status(array $records) : int + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("Reboot required"); + + $record = $records->last(); + if (! isset($record->data['reboot'])) { - return \App\Status::UNKNOWN; + return $report->setStatus(Status::unknown()) + ->setHTML("<p>No data available!</p>"); } if ($record->data["reboot"]) { - return \App\Status::WARNING; + return $report->setStatus(Status::warning()) + ->setHTML("<p>Reboot required: yes</p>"); } - return \App\Status::OK; - } - - public function name(): string - { - return "Reboot required"; + return $report->setStatus(Status::ok()) + ->setHtml("<p>Reboot required: no</p>"); } } diff --git a/app/Sensor/Ssacli.php b/app/Sensor/Ssacli.php index 3c0ea91f82b0e4daabf6b504355c348541745f8d..2760a77f65983ba4dd408902b19de54a1f32661e 100644 --- a/app/Sensor/Ssacli.php +++ b/app/Sensor/Ssacli.php @@ -2,61 +2,35 @@ namespace App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Description of Ssacli * * @author tibo */ -class Ssacli extends \App\Sensor +class Ssacli implements Sensor { const REGEXP = "/\s*physicaldrive .*\(port (.*):box (\d*):bay (\d*), (.*), (.*), (\w*)\)/"; - public function report(array $records) : string + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("HP ssacli"); + + $record = $records->last(); if (! isset($record->data['ssacli'])) { - return "<p>No data available...</p>"; + return $report->setHTML("<p>No data available...</p>"); } - + $disks = $this->parse($record->data["ssacli"]); - $return = "<table class='table table-sm'>" - . "<tr>" - . "<th>Port</th>" - . "<th>Box</th>" - . "<th>Bay</th>" - . "<th>Type</th>" - . "<th>Size</th>" - . "<th>Status</th>" - . "</tr>"; - foreach ($disks as $disk) { - $return .= "<tr>" - . "<td>" . $disk->port . "</td>" - . "<td>" . $disk->box . "</td>" - . "<td>" . $disk->bay . "</td>" - . "<td>" . $disk->type . "</td>" - . "<td>" . $disk->size . "</td>" - . "<td>" . $disk->status . "</td>" - . "</tr>"; - } - $return .= "</table>"; - return $return; - } - - public function status(array $records) : int - { - $record = end($records); - if (! isset($record->data['ssacli'])) { - return \App\Status::UNKNOWN; - } - - $disks = $this->parse($record->data["ssacli"]); - foreach ($disks as $disk) { - if ($disk->status != "OK") { - return \App\Status::WARNING; - } - } - - return \App\Status::OK; + $report->setHTML(view("sensor.ssacli", ["disks" => $disks])); + + return $report->setStatus(Status::max($disks)); } /** @@ -77,7 +51,7 @@ class Ssacli extends \App\Sensor $disk->bay = $values[3][$i]; $disk->type = $values[4][$i]; $disk->size = $values[5][$i]; - $disk->status = $values[6][$i]; + $disk->status = ($values[6][$i] == "OK") ? Status::ok() : Status::warning(); $disks[] = $disk; } return $disks; diff --git a/app/Sensor/Temper.php b/app/Sensor/Temper.php index 0e93e3db8f092c36f1f18bbd50cb55c798d7cce6..04c9550538d67997740c4cccbedb422a96028ccd 100644 --- a/app/Sensor/Temper.php +++ b/app/Sensor/Temper.php @@ -9,16 +9,25 @@ namespace App\Sensor; */ class Temper { - public $part1= "0a"; //eg : 0a - public $part2= "6c"; //eg : 6c - public $temp=array();//eg : [26,68] + // allows to extract device response + // 80 80 09 47 4e 20 00 00 + const REGEX = "/^80\s80\s([0-9a-fA-F]{2}\s[0-9a-fA-F]{2})/m"; - public function conversion() + public function convert(string $string) : float { - $hexatemp=$this->part1.$this->part2; //eg : 0a6c - $decitemp=hexdec($hexatemp); //eg : 2668 - $this->temp[1]=substr($decitemp, 0, -2); //eg : 26 - $this->temp[2]=substr($decitemp, -2); //eg : 68 - return $this->temp; + // extract 2 hex values from device response + // 09 47 + $values = []; + preg_match(self::REGEX, $string, $values); + + // remove intermediate white space + // 0947 + $hexatemp = preg_replace("/\s+/", "", $values[1]); + + // convert to decimal + // 2375 + $decitemp = hexdec($hexatemp); + + return $decitemp / 100.0; } } diff --git a/app/Sensor/Temperature.php b/app/Sensor/Temperature.php deleted file mode 100644 index 76278e18e84fa84979682ed20ddceb44a87be5f5..0000000000000000000000000000000000000000 --- a/app/Sensor/Temperature.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -namespace App\Sensor; - -/** - * Description of Temperature (core) - * - * @author helha - */ -class Temperature extends Cpu -{ - public $name = ""; //eg : core 0 - public $corevalue= ""; //eg : T° value -} diff --git a/app/Sensor/USBtemperature.php b/app/Sensor/USBtemperature.php index acf6a2f7df2947680af3c1e53b89962f13657c88..3cbcc98bcdd6d2493c9bc6ac125296c098f6630d 100644 --- a/app/Sensor/USBtemperature.php +++ b/app/Sensor/USBtemperature.php @@ -2,52 +2,37 @@ namespace App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Description of USBTemperature * * @author helha */ -class USBtemperature extends \App\Sensor +class USBtemperature implements Sensor { - //get device responce (8 bytes) : - const REGEXP = "/^(80 80)\s*([A-z\/0-9]+) \s*([A-z\/0-9]+)/m"; - public function report(array $records) : string + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("USB Temperature"); + + $record = $records->last(); if (! isset($record->data["TEMPer"])) { - return "<p>No data available...</p>" - . "<p>Maybe <code>TEMPer</code> is not installed.</p>" - . "<p>You can install it following the tutorial on the Gitlab repository</p>"; + return $report->setHTML("<p>No data available...</p>" + . "<p>Maybe <code>TEMPer</code> is not installed? " + . "You can install it following the tutorial on the Gitlab repository</p>"); } - $temper = self::parse($record->data['TEMPer']); - $return= "<p>Ambient temperature (USB TEMPer) : " . $temper->temp[1] . "." . $temper->temp[2] . " °C " . "</p>"; - return $return; - } - - public function status(array $records) : int - { - $record = end($records); - if (! isset($record->data["TEMPer"])) { - return \App\Status::UNKNOWN; - } - $status = \App\Status::OK; - $USBTemp = self::parse($record->data['TEMPer']); - if ((int)($USBTemp->temp[1]) > 60) { - $status = \App\Status::WARNING; - } - return $status; - } - - public static function parse(string $string) - { - $values = array(); - preg_match_all(self::REGEXP, $string, $values); //get 8 bytes response from TEMPerUSB device - $USBTemp = new Temper(); - $USBTemp->part1 = implode($values[2]); - $USBTemp->part2 = implode($values[3]); - $USBTemp->conversion(); //1st element = integer part, 2th = decimal part - $temper=$USBTemp; - return $temper; + + $temper = new Temper(); + $value = $temper->convert($record->data['TEMPer']); + $report->setHTML("<p>Ambient temperature (USB TEMPer) : $value °C " . "</p>"); + + $report->setStatus(Status::ok()); + return $report; } } diff --git a/app/Sensor/Updates.php b/app/Sensor/Updates.php index 79523ec771c794eecdcc3dd9b51a6c73c3c0cb27..f74d3e8a666f921842d29349da03a35f0e8ffa2d 100644 --- a/app/Sensor/Updates.php +++ b/app/Sensor/Updates.php @@ -2,43 +2,44 @@ namespace App\Sensor; +use App\Sensor; +use App\Status; +use App\ServerInfo; +use App\Report; + +use Illuminate\Database\Eloquent\Collection; + /** * Check if (security) updates are available. * * @author tibo */ -class Updates extends \App\Sensor +class Updates implements Sensor { const REGEXP = "/(\d+)\spackages? can be updated\.\n(\d+)\supdates? (is a|are) security updates?./"; - - public function report(array $records) : string - { - $record = end($records); - if (! isset($record->data['updates'])) { - return "<p>No data available...</p>"; - } - - return "<p>" . nl2br($record->data["updates"]) . "</p>"; - } - - public function status(array $records) : int + + public function analyze(Collection $records, ServerInfo $serverinfo): Report { - $record = end($records); + $report = new Report("Updates available"); + + $record = $records->last(); if (! isset($record->data['updates'])) { - return \App\Status::UNKNOWN; + return $report->setHTML("<p>No data available...</p>"); } + $report->setHTML("<p>" . nl2br($record->data["updates"]) . "</p>"); + $status = $this->parse($record->data["updates"]); if ($status == null) { - return \App\Status::UNKNOWN; + return $report->setStatus(Status::unknown()); } if ($status["security"] != 0) { - return \App\Status::WARNING; + return $report->setStatus(Status::warning()); } - return \App\Status::OK; + return $report->setStatus(Status::ok()); } public function parse($string) diff --git a/app/SensorWrapper.php b/app/SensorWrapper.php deleted file mode 100644 index 5238e9bb782d8a11d8aeab08e0f286fab2c6b778..0000000000000000000000000000000000000000 --- a/app/SensorWrapper.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php - -namespace App; - -use Illuminate\Support\Facades\Log; - -/** - * A wrapper around a sensor, used to catch possible exceptions. - */ -class SensorWrapper -{ - private $sensor; - private $records; - - private $report; - private $status; - - public function __construct(Sensor $sensor, $records) - { - $this->sensor = $sensor; - $this->records = $records; - } - - public function id() : string - { - return \get_class($this->sensor); - } - - public function name(): string - { - return $this->sensor->name(); - } - - public function report(): string - { - if (is_null($this->report)) { - try { - $this->report = $this->sensor->report($this->records); - } catch (\Exception $ex) { - Log::error('Sensor failed : ' . $ex->getTraceAsString()); - $this->report = "<p>Sensor " . $this->name() . " failed :-(</p>"; - } - } - - return $this->report; - } - - public function status(): Status - { - if (is_null($this->status)) { - try { - $this->status = new Status($this->sensor->status($this->records)); - } catch (\Exception $ex) { - Log::error('Sensor failed : ' . $ex->getTraceAsString()); - $this->status = new Status(Status::UNKNOWN); - } - } - - return $this->status; - } -} diff --git a/app/Server.php b/app/Server.php index b052fd42d3edf9a6a874df36e2172f8479e20c19..508400bb9b612a270fe5bd3c9d29aaf114fc0d3f 100644 --- a/app/Server.php +++ b/app/Server.php @@ -3,6 +3,7 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Log; /** @@ -56,7 +57,6 @@ class Server extends Model \App\Sensor\Date::class, \App\Sensor\ClientVersion::class, \App\Sensor\Heartbeat::class, - // \App\Sensor\DiskEvolution::class, \App\Sensor\CPUtemperature::class, \App\Sensor\USBtemperature::class ]; @@ -89,11 +89,14 @@ class Server extends Model /** * Get the last day of data. */ - public function lastRecords1Day() : array + public function lastRecords1Day() : Collection { if ($this->records_1day == null) { $start = time() - 24 * 3600; - $this->records_1day = $this->records()->where("time", ">", $start)->get()->all(); + $this->records_1day = $this->records() + ->where("time", ">", $start) + ->orderBy("time") + ->get(); } return $this->records_1day; @@ -119,44 +122,43 @@ class Server extends Model */ public function status() : Status { - return Status::max($this->statusArray()); - } - - public function statusArray() - { - $status_array = []; - foreach ($this->getSensors() as $sensor) { - $sensor_name = $sensor->id(); - try { - $status_array[$sensor_name] = $sensor->status(); - } catch (\Exception $ex) { - $status_array[$sensor_name] = Status::UNKNOWN; - Log::error("Sensor $sensor_name failed : " . $ex->getTraceAsString()); - } - } - return $status_array; + return Status::max($this->reports()); } public function getSensorsNOK() { $sensorsNOK = []; - foreach ($this->getSensors() as $sensor) { + foreach ($this->reports() as $sensor) { if ($sensor->status()->code() > 0) { $sensorsNOK[] = $sensor; } } return $sensorsNOK; } + + private $reports = null; - public function getSensors() + public function reports() { - $records = $this->lastRecords1Day(); - - $sensors = []; - foreach (self::$sensors as $sensor) { - $sensors[] = new SensorWrapper(new $sensor($this), $records); + if (is_null($this->reports)) { + $records = $this->lastRecords1Day(); + $serverinfo = $this->info(); + + $this->reports = []; + foreach (self::$sensors as $sensor) { + try { + $report = (new $sensor)->analyze($records, $serverinfo); + $this->reports[] = $report; + } catch (\Exception $ex) { + Log::error('Sensor failed : ' . $ex->getTraceAsString()); + $report = new Report($sensor, Status::unknown()); + $report->setHTML("<p>Agent crashed...</p>"); + $this->reports[] = $report; + } + } } - return $sensors; + + return $this->reports; } public function changes() diff --git a/app/Status.php b/app/Status.php index 428871602171999fcb7fb54cbb2cf01073fe3970..e83c5a1afafd61429ffb3d05bffd795fa8c8d722 100644 --- a/app/Status.php +++ b/app/Status.php @@ -19,7 +19,7 @@ class Status $this->code = $code; } - public function name() : string + public function __toString() : string { switch ($this->code) { case 0: @@ -52,18 +52,6 @@ class Status } } - public static function max(array $statuses) : Status - { - $max = new Status(self::UNKNOWN); - foreach ($statuses as $status) { - if ($status->code() > $max->code()) { - $max = $status; - } - } - - return $max; - } - public function color() : string { switch ($this->code) { @@ -77,4 +65,39 @@ class Status return 'secondary'; } } + + /** + * + * @param array<HasStatus> $items + * @return Status + */ + public static function max(array $items) : Status + { + return max(array_map( + function (HasStatus $item) { + return $item->status(); + }, + $items + )); + } + + public static function ok() : Status + { + return new Status(0); + } + + public static function warning() : Status + { + return new Status(10); + } + + public static function error() : Status + { + return new Status(20); + } + + public static function unknown() : Status + { + return new Status(-1); + } } diff --git a/resources/views/sensor/cputemperature.blade.php b/resources/views/sensor/cputemperature.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..317c7a63c464c4618c21301f934e2b16c38fef28 --- /dev/null +++ b/resources/views/sensor/cputemperature.blade.php @@ -0,0 +1,24 @@ +<table class='table table-sm'> + <tr> + <th>Name</th> + <th class="text-right">Temperature (°C)</th> + <th class="text-right">T°crit (°C)</th> + </tr> + +@foreach ($cpus as $CPU) + <tr> + <td><b>CPU {{ $CPU->name }}</b></td> + <td class="text-right"><b> {{ $CPU->value }}</b></td> + <td class="text-right"><b>{{ $CPU->critvalue }}</b></td> + </tr> + + @foreach ($CPU->cores as $core) + <tr> + <td>{{ $core->name }}</td> + <td class="text-right">{{ $core->value }}</td> + <td class="text-right">{{ $CPU->critvalue }}</td> + </tr> + @endforeach +@endforeach + +</table> \ No newline at end of file diff --git a/resources/views/sensor/inodes.blade.php b/resources/views/sensor/inodes.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..378bcc59a3e8a7f2f1509dca6e6c35d003ba46fe --- /dev/null +++ b/resources/views/sensor/inodes.blade.php @@ -0,0 +1,15 @@ +<table class='table table-sm'> +<tr> + <th></th> + <th></th> + <th>Usage</th> +</tr> + +@foreach ($disks as $disk) +<tr> + <td>{{ $disk->filesystem }}</td> + <td>{{ $disk->mounted }}</td> + <td>{{ $disk->usedPercent() }}%</td> +</tr> +@endforeach +</table> \ No newline at end of file diff --git a/resources/views/sensor/listeningports.blade.php b/resources/views/sensor/listeningports.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..d13b589779a871fb9663a084382cf04fe60c9a15 --- /dev/null +++ b/resources/views/sensor/listeningports.blade.php @@ -0,0 +1,17 @@ +<table class='table table-sm'> +<tr> + <th>Port</th> + <th>Proto</th> + <th>Bind address</th> + <th>Process</th> +</tr> + +@foreach ($ports as $port) +<tr> + <td>{{ $port->port }}</td> + <td>{{ $port->proto }}</td> + <td>{{ $port->bind }}</td> + <td>{{ $port->process }}</td> +</tr> +@endforeach +</table> \ No newline at end of file diff --git a/resources/views/sensor/perccli.blade.php b/resources/views/sensor/perccli.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..745e4375d573ce5114b89f7be7aba44d77971ceb --- /dev/null +++ b/resources/views/sensor/perccli.blade.php @@ -0,0 +1,18 @@ +<table class='table table-sm'> +<tr> + <th>Slot</th> + <th>Type</th> + <th>Size</th> + <th>Status</th> +</tr> + +@foreach ($drives as $disk) +<tr> + <td>{{ $disk->slot }}</td> + <td>{{ $disk->type }}</td> + <td>{{ $disk->size }}</td> + <td>{{ $disk->status }}</td> +</tr> +@endforeach + +</table> \ No newline at end of file diff --git a/resources/views/sensor/ssacli.blade.php b/resources/views/sensor/ssacli.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..4da321f159667efa3f8bf69e23305768b7f5709a --- /dev/null +++ b/resources/views/sensor/ssacli.blade.php @@ -0,0 +1,22 @@ +<table class='table table-sm'> +<tr> + <th>Port</th> + <th>Box</th> + <th>Bay</th> + <th>Type</th> + <th>Size</th> + <th>Status</th> +</tr> + +@foreach ($disks as $disk) +<tr> + <td>{{ $disk->port }}</td> + <td>{{ $disk->box }}</td> + <td>{{ $disk->bay }}</td> + <td>{{ $disk->type }}</td> + <td>{{ $disk->size }}</td> + <td>{{ $disk->status }}</td> +</tr> +@endforeach + +</table> \ No newline at end of file diff --git a/resources/views/server/show.blade.php b/resources/views/server/show.blade.php index 1f24646383128967b3ed573babbe2e8e11d6b5d6..8b9421858a2d4efa52bfa3f6158859b5ebf1fe52 100644 --- a/resources/views/server/show.blade.php +++ b/resources/views/server/show.blade.php @@ -75,7 +75,7 @@ window.monitorServerToken = "{{ $server->read_token }}"; </h1> @if ($server->hasData()) - @foreach ($server->getSensors() as $sensor) + @foreach ($server->reports() as $sensor) <div class="card"> <div class="card-header"> {{ $sensor->name() }} @@ -85,7 +85,7 @@ window.monitorServerToken = "{{ $server->read_token }}"; </div> </div> <div class="card-body"> - {!! $sensor->report() !!} + {!! $sensor->html() !!} </div> </div> @endforeach diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 622c6be8599269c199c5e638d57adfd8817e0c2b..a7463d3f2bfcf13195394e5eb8438a886514e291 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -6,6 +6,8 @@ use App\User; use App\Organization; use App\Server; use App\Record; +use App\Status; + use Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -40,6 +42,20 @@ class ExampleTest extends TestCase $user->organizations()->first()->name ); } + + /** + * @group status + */ + public function testStatusComparison() + { + $warning = Status::warning(); + $error = Status::error(); + + $this->assertGreaterThan($warning, $error); + $this->assertTrue($error > $warning); + + $this->assertEquals(Status::error(), max($warning, $error)); + } /** diff --git a/tests/Unit/SensorsTest.php b/tests/Unit/SensorsTest.php index 6bfb28114d6cc301bd2bf86d4bacff16ef239139..6012745b63e301be0ccef429cc1a4b691ec8e4ed 100644 --- a/tests/Unit/SensorsTest.php +++ b/tests/Unit/SensorsTest.php @@ -3,12 +3,14 @@ namespace Tests\Unit; use App\Server; +use App\Status; + use App\Sensor\Disks; use App\Sensor\CPUtemperature; -use App\Sensor\USBtemperature; use App\Sensor\Ifconfig; use App\Sensor\Updates; use App\Sensor\Netstat; +use App\Sensor\Temper; use Tests\TestCase; @@ -26,7 +28,7 @@ class SensorsTest extends TestCase public function testIfconfig() { $string = file_get_contents(__DIR__ . "/ifconfig"); - $sensor = new Ifconfig(new Server()); + $sensor = new Ifconfig(); $interfaces = $sensor->parseIfconfig($string); $this->assertEquals(2, count($interfaces)); $this->assertEquals("enp0s31f6", $interfaces[0]->name); @@ -44,7 +46,7 @@ class SensorsTest extends TestCase public function testIfconfig1804() { $string = file_get_contents(__DIR__ . "/ifconfig1804"); - $sensor = new Ifconfig(new Server()); + $sensor = new Ifconfig(); $interfaces = $sensor->parseIfconfig($string); $this->assertEquals(2, count($interfaces)); $this->assertEquals("eno1", $interfaces[0]->name); @@ -60,7 +62,7 @@ class SensorsTest extends TestCase public function testDisksSensor() { $string = file_get_contents(__DIR__ . "/df"); - $sensor = new Disks(new Server()); + $sensor = new Disks(); $disks = $sensor->parse($string); $this->assertEquals(2, count($disks)); $this->assertEquals("/dev/sda1", $disks[0]->filesystem); @@ -70,7 +72,7 @@ class SensorsTest extends TestCase public function testNetstatListening() { $string = file_get_contents(__DIR__ . "/netstat-tcp"); - $sensor = new \App\Sensor\ListeningPorts(new Server()); + $sensor = new \App\Sensor\ListeningPorts(); $ports = $sensor->parse($string); $this->assertEquals(16, count($ports)); $this->assertEquals("31933/cloud-backup-", $ports[4]->process); @@ -81,7 +83,7 @@ class SensorsTest extends TestCase public function testSsacli() { $string = file_get_contents(__DIR__ . "/ssacli"); - $sensor = new \App\Sensor\Ssacli(new Server()); + $sensor = new \App\Sensor\Ssacli(); $disks = $sensor->parse($string); $this->assertEquals("OK", $disks[0]->status); } @@ -89,16 +91,17 @@ class SensorsTest extends TestCase public function testPerccli() { $string = file_get_contents(__DIR__ . "/perccli"); - $sensor = new \App\Sensor\Perccli(new Server()); + $sensor = new \App\Sensor\Perccli(); $disks = $sensor->parse($string); - $this->assertEquals("Onln", $disks[0]->status); + + $this->assertEquals(Status::ok(), $disks[0]->status); $this->assertEquals("SSD", $disks[0]->type); $this->assertEquals("446.625 GB", $disks[0]->size); } public function testUpdates() { - $sensor = new Updates(new Server()); + $sensor = new Updates(); $string1 = "6 packages can be updated. 2 updates are security updates."; @@ -131,11 +134,12 @@ class SensorsTest extends TestCase public function testCPUtemp() { $string = file_get_contents(__DIR__ . "/sensors"); - $sensor = new CPUtemperature(new Server()); - $CPUTEMPS = $sensor->parse($string); - $this->assertEquals(4, count($CPUTEMPS)); - $this->assertEquals("Core 3", $CPUTEMPS[3]->name); - $this->assertEquals("34.0", $CPUTEMPS[3]->value); + $sensor = new CPUtemperature(); + + $cpus = $sensor->parse($string); + $this->assertEquals(4, count($cpus[0]->cores)); + $this->assertEquals("Core 3", $cpus[0]->cores[3]->name); + $this->assertEquals("34.0", $cpus[0]->cores[3]->value); } /** * @group USBtemp @@ -143,23 +147,9 @@ class SensorsTest extends TestCase public function testTEMPer() { $string = file_get_contents(__DIR__ . "/TEMPer"); - $TEMPer = new USBtemperature(new Server()); - $USBTemp = $TEMPer->parse($string); - $this->assertEquals("09", $USBTemp->part1); - $this->assertEquals("47", $USBTemp->part2); - $this->assertEquals("23", $USBTemp->temp[1]); - $this->assertEquals("75", $USBTemp->temp[2]); - } - /** - * @group multicpu - */ - public function testmultiCPUtemp() - { - $string = file_get_contents(__DIR__ . "/sensors"); - $sensor = new CPUtemperature(new Server()); - $CPUTEMPS = $sensor->parseCPUtemperature($string); - $this->assertEquals(4, count($CPUTEMPS)); - $this->assertEquals("Core 3", $CPUTEMPS[3]->name); - $this->assertEquals("34.0", $CPUTEMPS[3]->corevalue); + + $temper = new Temper(); + $t = $temper->convert($string); + $this->assertEquals(23.75, $t); } }