Skip to content

Commit

Permalink
[#151] Add new sitecheck page
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhilton committed Oct 16, 2023
1 parent 789b1ef commit b525079
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 3 deletions.
6 changes: 3 additions & 3 deletions classes/check/tasklatencycheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ public function get_result(): result {
$valid = $task !== false;

// Cast to int, will force non-int strings to 0, so we only need to care about negative time as invalid.
$valid &= ((int) $runtime >= 0);
$valid &= ((int) $startdelay >= 0);
$valid &= ((int) $completiondelay >= 0);
$valid &= ((int) $runtime >= 0); // 30
$valid &= ((int) $startdelay >= 0); // 6
$valid &= ((int) $completiondelay >= 0); // 45

if (!$valid) {
return new result(result::ERROR, get_string('taskconfigbad', 'tool_heartbeat', $taskclass));
Expand Down
194 changes: 194 additions & 0 deletions classes/checker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_heartbeat;

use core\check\check;
use core\check\result;
use Throwable;

/**
* Check API checker class
*
* Processes check API results and returns them in a nice format for nagios output.
*
* @package tool_heartbeat
* @author Matthew Hilton <[email protected]>
* @copyright 2023, Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class checker {
/** @var array Nagios level prefixes **/
public const NAGIOS_PREFIXES = [
0 => "OK",
1 => "WARNING",
2 => "CRITICAL",
3 => "UNKNOWN",
];

/**
* Returns an array of check API messages.
* If exceptions are thrown, they are caught and returned as result messages as well.
*
* @return array array of resultmessage objects
*/
public static function get_check_messages(): array {
// First try to get the checks, if this fails return a critical message (code is very broken).
$checks = [];

try {
$checks = \core\check\manager::get_checks('status');
} catch (Throwable $e) {
return [self::exception_to_message("Error getting checks: ", $e)];
}

// Execute each check and store their messages.
$messages = [];

foreach ($checks as $check) {
try {
$messages[] = self::process_check_and_get_result($check);
} catch (Throwable $e) {
$messages[] = self::exception_to_message("Error processing check " . $check->get_ref() . ": ", $e);
}
}

return $messages;
}

/**
* Turns the given exception into a warning resultmessage.
* @param string $prefix
* @param Throwable $e
* @return resultmessage
*/
private static function exception_to_message(string $prefix, Throwable $e): resultmessage {
$res = new resultmessage();
$res->level = resultmessage::LEVEL_WARN;
$res->title = $prefix . $e->getMessage();
$res->message = (string) $e;
return $res;
}

/**
* Processes the check and maps its result and status to a resultmessage.
* @param check $check
* @return resultmessage
*/
private static function process_check_and_get_result(check $check): resultmessage {
$res = new resultmessage();

$checkresult = $check->get_result();

// Map check result to nagios level.
$map = [
result::WARNING => resultmessage::LEVEL_WARN,
result::CRITICAL => resultmessage::LEVEL_CRITICAL,
result::OK => resultmessage::LEVEL_OK,
result::NA => resultmessage::LEVEL_OK,
result::WARNING => resultmessage::LEVEL_WARN,
result::UNKNOWN => resultmessage::LEVEL_UNKNOWN,
result::ERROR => resultmessage::LEVEL_CRITICAL,
];

// Get the level, or default to unknown.
$status = $checkresult->get_status();
$res->level = isset($map[$status]) ? $map[$status] : resultmessage::LEVEL_UNKNOWN;

$res->title = $check->get_name();

// Add the first line of summary to the title.
$summarylines = explode("\n", $checkresult->get_summary());
$res->title .= ": " . $summarylines[0] ?: '';

$res->message = $checkresult->get_summary();

return $res;
}

/**
* From an array of resultmessage, determines the highest nagios level.
* Note, it considers UNKNOWN to be less than CRITICAL or WARNING.
*
* @param array $messages array of resultmessage objects
* @return int the calculated nagios level
*/
public static function determine_nagios_level(array $messages): int {
// Find the highest level.
$levels = array_column($messages, "level");

// Add a default "OK" in case no messages were returned.
$levels[] = resultmessage::LEVEL_OK;

$hasunknown = !empty(array_filter($levels, function($l) {
return $l == resultmessage::LEVEL_UNKNOWN;
}));

// Remove unknowns.
$levels = array_filter($levels, function($l) {
return $l != resultmessage::LEVEL_UNKNOWN;
});

$highest = max($levels);

// If highest was OK but it had an unknown, return unknown.
// This stops UNKNOWN from masking WARNING or CRITICAL.
if ($highest == resultmessage::LEVEL_OK && $hasunknown) {
return resultmessage::LEVEL_UNKNOWN;
}

// Else return OK.
return $highest;
}

/**
* Creates a summary from the given messages.
* If there are no messages or only OK, OK is returned.
* If there is a single message, its details are returned.
* If there are multiple messages, the levels are aggregated and turned into a summary.
*
* @param array $messages array of resultmessage objects
* @return string
*/
public static function create_summary(array $messages): string {
// Filter out any OK messages.
$messages = array_filter($messages, function($m) {
return $m->level != resultmessage::LEVEL_OK;
});

// If no messages, return OK.
if (count($messages) == 0) {
return "OK";
}

// If only one message, use it as the top level.
if (count($messages) == 1) {
return $messages[0]->title;
}

// Otherwise count how many of each level.
$counts = array_count_values(array_column($messages, 'level'));

$countswithprefixes = [];
foreach ($counts as $level => $occurrences) {
$prefix = self::NAGIOS_PREFIXES[$level];
$countswithprefixes[] = "{$occurrences} {$prefix}";
}

return "Multiple problems detected: " . implode(", ", $countswithprefixes);
}
}

49 changes: 49 additions & 0 deletions classes/resultmessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_heartbeat;

/**
* A data-only class for holding a message about a result from a check API class.
*
* @package tool_heartbeat
* @author Matthew Hilton <[email protected]>
* @copyright 2023, Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class resultmessage {
/** @var int OK level **/
public const LEVEL_OK = 0;

/** @var int WARN level **/
public const LEVEL_WARN = 1;

/** @var int CRITICAL level **/
public const LEVEL_CRITICAL = 2;

/** @var int UNKNOWN level **/
public const LEVEL_UNKNOWN = 3;

/** @var int $level The level of this message **/
public $level = self::LEVEL_UNKNOWN;

/** @var string $title Title of the message **/
public $title = '';

/** @var string $message Details of this message **/
public $message = '';
}

86 changes: 86 additions & 0 deletions sitecheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Check API Health Check
*
* @package tool_heartbeat
* @copyright 2023 Matthew Hilton <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* This can be run either as a web api, or on the CLI. When run on the
* CLI it conforms to the Nagios plugin standard.
*
* See also:
* - http://nagios.sourceforge.net/docs/3_0/pluginapi.html
* - https://nagios-plugins.org/doc/guidelines.html#PLUGOUTPUT
*
*/

use tool_heartbeat\checker;
use tool_heartbeat\resultmessage;

// @codingStandardsIgnoreStart
define('NO_UPGRADE_CHECK', true);

// Start output buffering. This stops for e.g. debugging messages from breaking the output.
// When a nagios.php send_* function is called, they will collect the buffer
// and warn if it is not empty (but do it nicely).
ob_start();

$dirroot = __DIR__ . '/../../../';
require($dirroot.'config.php');
require_once(__DIR__.'/nagios.php');

global $PAGE;

if (isset($CFG->mnet_dispatcher_mode) and $CFG->mnet_dispatcher_mode !== 'off') {
// This is a core bug workaround, see MDL-77247 for more details.
require_once($CFG->dirroot.'/mnet/lib.php');
}

$messages = checker::get_check_messages();

// Filter out any OK ones, we only care about the others.
$messages = array_filter($messages, function($m) {
return $m->level != resultmessage::LEVEL_OK;
});

// Construct the output message.
$PAGE->set_context(\context_system::instance());

// Indent the messages.
$msg = array_map(function($message) {
global $OUTPUT;

// Indent messages.
$spacer = "&nbsp;&nbsp;";
$indentedmessage = $spacer . str_replace("\n", "\n" . $spacer, $message->message);

return $OUTPUT->render_from_template('tool_heartbeat/resultmessage', [
'prefix' => checker::NAGIOS_PREFIXES[$message->level],
'title' => $message->title,
'message' => nl2br($indentedmessage),
]);
}, $messages);

$summary = checker::create_summary($messages);
$msg = $summary . "<br />" . implode("", $msg);

$level = checker::determine_nagios_level($messages);

send($level, $msg);

40 changes: 40 additions & 0 deletions templates/resultmessage.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

{{!
This file is part of Moodle - https://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see
<http: //www.gnu.org/licenses />.
}}
{{!
@template tool_heartbeat/resultmessage
Template by JS to render output of result report getting (loading, url, error)
Classes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
"prefix": "CRTIICAL",
"title": "Something broke",
"message": "Some more details"
}
}}

* {{prefix}} {{title}}<br/>
{{{message}}}<br/>
<br />
Loading

0 comments on commit b525079

Please sign in to comment.