Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exemption functionality #171

Merged
merged 2 commits into from
Mar 12, 2024
Merged
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
120 changes: 71 additions & 49 deletions classes/check/failingtaskcheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ class failingtaskcheck extends check {
/** @var int $errorthreshold Threshold in minutes after which should error about tasks failing **/
public $errorthreshold = 600;

/** @var \stdClass $task Record of task that is failing **/
private $task;

/**
* Constructor
*/
public function __construct() {
public function __construct($task = null) {
$this->id = 'cronfailingtasks';
$this->name = get_string('checkfailingtaskcheck', 'tool_heartbeat');
$this->task = $task;

$this->actionlink = new \action_link(
new \moodle_url('/admin/tasklogs.php'),
Expand All @@ -56,72 +60,90 @@ public function __construct() {
public function get_result() : result {
global $DB;

$taskoutputs = [];

// Instead of using task API here, we read directly from the database.
// This stops errors originating from broken tasks.
$scheduledtasks = $DB->get_records_sql("SELECT * FROM {task_scheduled} WHERE faildelay > 0 AND disabled = 0");

foreach ($scheduledtasks as $task) {
$taskoutputs[] = "SCHEDULED TASK: {$task->classname} Delay: {$task->faildelay}\n";
}

// Instead of using task API here, we read directly from the database.
// This stops errors originating from broken tasks, and allows the DB to de-duplicate them.
$adhoctasks = $DB->get_records_sql(" SELECT classname, COUNT(*) count, MAX(faildelay) faildelay, SUM(faildelay) cfaildelay
FROM {task_adhoc}
WHERE faildelay > 0
GROUP BY classname
ORDER BY cfaildelay DESC");

foreach ($adhoctasks as $record) {
// Only add duplicate message if there are more than 1.
$duplicatemsg = $record->count > 1 ? " ({$record->count} duplicates!!!)" : '';
$taskoutputs[] = "ADHOC TASK: {$record->classname} Delay: {$record->faildelay} {$duplicatemsg}\n";
// Return OK if no task errors.
if (!isset($this->task)) {
$count = $DB->count_records_sql("SELECT COUNT(*) FROM {task_scheduled} WHERE faildelay = 0 AND disabled = 0");
return new result(result::OK, get_string('checkfailingtaskok', 'tool_heartbeat', $count), '');
}

// Find the largest faildelay out of both adhoc and scheduled tasks.
$alldelays = array_merge(array_column($adhoctasks, 'faildelay'), array_column($scheduledtasks, 'faildelay'));
$maxdelaymins = !empty($alldelays) ? max($alldelays) / 60 : 0;

// Define a simple function to work out what the message should be based on the task outputs.
// Returns the [$summary, $details].
$taskoutputfn = function($faildelaymins) use ($taskoutputs) {
$count = count($taskoutputs);

if ($count == 1) {
// Only a single task is failing, so put it at the top level.
return [$taskoutputs[0], ''];
}

if ($count > 1) {
// More than 1, add a message at the start that indicates how many.
return ["{$count} Moodle tasks reported errors, maximum faildelay > {$faildelaymins} mins", implode("", $taskoutputs)];
}

// There are 0 tasks are failing, default to nothing.
return ['', ''];
};
$maxdelaymins = !empty($this->task->faildelay) ? $this->task->faildelay / 60 : 0;

// Default to ok.
$status = result::OK;
$delay = 0;

// Check if warn - if so then upgrade to warn.
if ($maxdelaymins > $this->warnthreshold) {
$status = result::WARNING;
$delay = $this->warnthreshold;
}

// Check if error - if so then upgrade to error.
if ($maxdelaymins > $this->errorthreshold) {
$status = result::ERROR;
$delay = $this->errorthreshold;
}

list($summary, $details) = $taskoutputfn($delay);
return new result($status, $this->task->message, '');
}

/**
* Get the short check name
*
* @return string
*/
public function get_name(): string {
$name = parent::get_name();
if (!isset($this->task)) {
return $name;
}
return get_string('checkfailingtaskchecktask', 'tool_heartbeat', $this->task->classname);
}

/**
* Get the check reference.
* If this check is on a specific task, use the task classname.
*
* @return string must be globally unique
*/
public function get_ref(): string {
if (!isset($this->task)) {
return parent::get_ref();
}
// Format nicely to use as a query param.
return trim(str_replace('\\', '_', $this->task->classname), '_');
}

/**
* Gets an array of all failing tasks, stored as \stdClass.
*
* @return array of failing tasks
*/
public static function get_failing_tasks(): array {
GLOBAL $DB;
$tasks = [];

// Instead of using task API here, we read directly from the database.
// This stops errors originating from broken tasks.
$scheduledtasks = $DB->get_records_sql("SELECT * FROM {task_scheduled} WHERE faildelay > 0 AND disabled = 0");

foreach ($scheduledtasks as $task) {
$task->message = "SCHEDULED TASK: {$task->classname} Delay: {$task->faildelay}\n";
$tasks[] = new \tool_heartbeat\check\failingtaskcheck($task);
}

return new result($status, nl2br($summary), nl2br($details));
// Instead of using task API here, we read directly from the database.
// This stops errors originating from broken tasks, and allows the DB to de-duplicate them.
$adhoctasks = $DB->get_records_sql(" SELECT classname, COUNT(*) count, MAX(faildelay) faildelay, SUM(faildelay) cfaildelay
FROM {task_adhoc}
WHERE faildelay > 0
GROUP BY classname
ORDER BY cfaildelay DESC");

foreach ($adhoctasks as $record) {
// Only add duplicate message if there are more than 1.
$duplicatemsg = $record->count > 1 ? " ({$record->count} duplicates!!!)" : '';
$record->message = "ADHOC TASK: {$record->classname} Delay: {$record->faildelay} {$duplicatemsg}\n";
$tasks[] = new \tool_heartbeat\check\failingtaskcheck($record);
}
return $tasks;
}
}
35 changes: 30 additions & 5 deletions classes/checker.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ public static function get_check_messages(): array {
}

// Remove any supressed checks from the list.
$checks = array_filter($checks, function($check) {
return !in_array(get_class($check), self::supressed_checks());
});
$checks = self::remove_supressed_checks($checks);

// Execute each check and store their messages.
$messages = [];
Expand Down Expand Up @@ -132,7 +130,7 @@ private static function exception_to_message(string $prefix, Throwable $e): resu
private static function process_check_and_get_result(check $check): resultmessage {
$res = new resultmessage();

$checkresult = $check->get_result();
$checkresult = self::get_overridden_result($check);

// Map check result to nagios level.
$map = [
Expand Down Expand Up @@ -297,5 +295,32 @@ private static function supressed_checks(): array {
\tool_task\check\adhocqueue::class,
];
}
}

/**
* Removes supressed checks from an array
* @param array $checks
* @return array of checks without supressed checks
*/
public static function remove_supressed_checks(array $checks): array {
// Remove any supressed checks from the list.
return array_filter($checks, function($check) {
return !in_array(get_class($check), self::supressed_checks());
});
}

/**
* Gets a check result while applying specified overrides.
* @param check $check
* @return result with overrides
*/
public static function get_overridden_result(check $check): result {
$ref = $check->get_ref();
$result = $check->get_result();

$override = \tool_heartbeat\object\override::get_active_override($ref);
if (isset($override)) {
return new result($override->get('override'), $result->get_summary(), $result->get_details());
}
return $result;
}
}
76 changes: 76 additions & 0 deletions classes/form/override_form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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/>.
/**
* Override form
*
* @package tool_heartbeat
* @copyright 2023 Owen Herbert <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
*/

namespace tool_heartbeat\form;

use core\check\result;
use core\form\persistent;

/**
* Override form
*
* @package tool_heartbeat
* @copyright 2023 Owen Herbert <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class override_form extends persistent {

/** @var string Persistent class name. */
protected static $persistentclass = 'tool_heartbeat\\object\\override';

/**
* Define the form.
*/
public function definition() {
$mform = $this->_form;

// Ref.
$mform->addElement('static', 'ref', get_string('check'));
$mform->setConstant('ref', $this->_customdata['ref']);

// Override.
$mform->addElement('select', 'override', get_string('override', 'tool_heartbeat'), [
result::NA => get_string('statusna'),
result::OK => get_string('statusok'),
result::INFO => get_string('statusinfo'),
result::UNKNOWN => get_string('statusunknown'),
result::WARNING => get_string('statuswarning'),
result::CRITICAL => get_string('statuscritical'),
result::ERROR => get_string('statuserror'),
]);

// Note.
$mform->addElement('textarea', 'note', get_string('notes', 'core_notes'), ['rows' => 3]);
$mform->addRule('note', get_string('noterequired', 'tool_heartbeat'), 'required', null, 'client');

// URL.
$mform->addElement('text', 'url', get_string('url'), ['size' => 80]);
$mform->setType('url', PARAM_URL);

// Override until.
$mform->addElement('date_selector', 'expires_at', get_string('expiresat', 'tool_heartbeat'));

$this->add_action_buttons();
}
}
Loading
Loading