Skip to content

Commit b8a8868

Browse files
committed
Issue #224: Add task load metric
1 parent 7979169 commit b8a8868

16 files changed

Lines changed: 893 additions & 16 deletions

classes/check/metriccheck.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,31 @@
3232
*/
3333
class metriccheck extends check {
3434
/** @var base $metric to be checked*/
35-
private $metric;
35+
protected $metric;
36+
37+
/**
38+
* Factory method for making a metric check.
39+
*
40+
* @param base $metric
41+
* @return mixed|self
42+
*/
43+
public static function get_check(base $metric) {
44+
// Derive the metric-specific check class from the metric's short name,
45+
// e.g. metric\task_load_metric -> check\task_load_metric_performance_check.
46+
$parts = explode('\\', $metric::class);
47+
$classname = __NAMESPACE__ . '\\' . end($parts) . '_performance_check';
48+
if (class_exists($classname)) {
49+
return new $classname($metric);
50+
}
51+
return new self($metric);
52+
}
3653

3754
/**
3855
* Constructor
3956
*
4057
* @param base $metric
4158
*/
42-
public function __construct($metric) {
59+
public function __construct(base $metric) {
4360
$this->metric = $metric;
4461
}
4562

@@ -75,6 +92,7 @@ public function get_action_link(): ?\action_link {
7592

7693
/**
7794
* Return result
95+
*
7896
* @return result
7997
*/
8098
public function get_result(): result {
@@ -86,12 +104,11 @@ public function get_result(): result {
86104
}
87105

88106
$options = manager::get_frequency_labels();
89-
$frequency = get_config('tool_cloudmetrics', $this->metric->get_name() . '_frequency') ?
90-
get_config('tool_cloudmetrics', $this->metric->get_name() . '_frequency') : $this->metric->get_frequency_default();
91-
$description = get_string('collector_frequency', 'tool_cloudmetrics', $options[$frequency]);
107+
$frequency = $this->metric->get_frequency();
108+
$details = get_string('collector_frequency', 'tool_cloudmetrics', $options[$frequency]);
92109

93110
// Return only the value of the metric, so it can be easily parsed externally.
94111
$value = get_config('tool_cloudmetrics', $this->metric->get_name() . '_last_value');
95-
return new result(result::OK, $value, $description);
112+
return new result(result::OK, $value, $details);
96113
}
97114
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace tool_cloudmetrics\check;
18+
19+
use core\check\result;
20+
21+
/**
22+
* Performance check for the task load metric.
23+
*
24+
* @package tool_cloudmetrics
25+
* @author Jason den Dulk <jasondendulk@catalyst-au.net>
26+
* @copyright 2026 Catalyst IT
27+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28+
*/
29+
class task_load_metric_performance_check extends metriccheck {
30+
/**
31+
* Return result
32+
*
33+
* @return result
34+
*/
35+
public function get_result(): result {
36+
$result = parent::get_result();
37+
return new task_load_metric_performance_check_result(
38+
$this->metric,
39+
$result->get_status(),
40+
$result->get_summary(),
41+
$result->get_details()
42+
);
43+
}
44+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace tool_cloudmetrics\check;
18+
19+
use core\output\action_link;
20+
use tool_cloudmetrics\metric\base;
21+
use core\check\result;
22+
use core_admin\reportbuilder\local\entities\task_log;
23+
use tool_cloudmetrics\local\task_load\task_load_estimator;
24+
25+
/**
26+
* Performance check result for task load estimator metric.
27+
*
28+
* @package tool_cloudmetrics
29+
* @author Jason den Dulk <jasondendulk@catalyst-au.net>
30+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31+
* @copyright Catalyst IT
32+
*/
33+
class task_load_metric_performance_check_result extends result {
34+
/** @var base The metric that was checked */
35+
private readonly base $metric;
36+
37+
/**
38+
* Construct the result object.
39+
* @param base $metric The metric that was checked
40+
* @param string $status
41+
* @param string $summary
42+
* @param string $details
43+
*/
44+
public function __construct(
45+
base $metric,
46+
string $status,
47+
string $summary,
48+
string $details = '',
49+
) {
50+
$this->metric = $metric;
51+
parent::__construct($status, $summary, $details);
52+
}
53+
54+
/**
55+
* Get additional details about the check.
56+
*
57+
* @return string HTML markup describing the check in more detail
58+
*/
59+
public function get_details(): string {
60+
$details = \html_writer::tag('p', parent::get_details());
61+
$table = new \html_table();
62+
$table->head = [
63+
get_string('task_name', 'tool_cloudmetrics'),
64+
get_string('estimatedtaskload_s', 'tool_cloudmetrics'),
65+
];
66+
foreach ($this->get_data() as $row) {
67+
$table->data[] = [
68+
self::format_classname(ltrim($row->task_name, '\\')),
69+
round($row->estimated_load, 1),
70+
];
71+
}
72+
73+
$details .= \html_writer::table($table);
74+
return $details;
75+
}
76+
77+
/**
78+
* Formats a task name with its classname
79+
*
80+
* @param string $classname
81+
* @return string html formatted name
82+
*/
83+
public static function format_classname(string $classname): string {
84+
// TODO: 5.3 adds this function to core_admin\reportbuilder\local\entities\task_log.
85+
$output = '';
86+
if (class_exists($classname)) {
87+
$task = new $classname();
88+
if ($task instanceof \core\task\task_base) {
89+
$output = $task->get_name();
90+
}
91+
}
92+
93+
$output .= \html_writer::tag('div', "\\{$classname}", [
94+
'class' => 'small text-muted',
95+
]);
96+
return $output;
97+
}
98+
99+
/**
100+
* Fetch the task load estimates as rows.
101+
*
102+
* Estimates are generated for the time window supplied by the metric.
103+
*
104+
* @return \stdClass[] the report rows, keyed by column name
105+
*/
106+
private function get_data(): array {
107+
$estimator = task_load_estimator::create();
108+
$estimates = $estimator->generate_load_estimates($this->metric->get_time_window());
109+
110+
$rows = [];
111+
foreach ($estimates as $estimate) {
112+
$rows[] = (object) [
113+
'task_name' => $estimate['classname'],
114+
'estimated_load' => $estimate['totalduration'],
115+
];
116+
}
117+
return $rows;
118+
}
119+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace tool_cloudmetrics\local\task_load;
18+
19+
use core\task\manager as taskmanager;
20+
21+
/**
22+
* Base class for task load estimators.
23+
*
24+
* @package tool_cloudmetrics
25+
* @author Jason den Dulk <jasondendulk@catalyst-au.net>
26+
* @copyright 2026 Catalyst IT
27+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28+
*/
29+
abstract class task_load_estimator {
30+
/** @var float Default value for the average time, in seconds. */
31+
public const DEFAULT_AVG_TIME = 0.5;
32+
33+
/**
34+
* Returns the object to calculate the task load estimates.
35+
* Which implementation is used depends on the availability of task statistics feature (MDL-85173).
36+
*
37+
* @return task_load_estimator
38+
*/
39+
public static function create(): task_load_estimator {
40+
if (class_exists('\core\task\stat_processor')) {
41+
return new task_load_from_stats();
42+
} else {
43+
return new task_load_from_log();
44+
}
45+
}
46+
47+
/**
48+
* Generate task load estimates for the near future. The load is taken from the current
49+
* time determined with a DI clock.
50+
*
51+
* @param int $window The number of seconds into the future to look.
52+
* @return array Returns an array of task load estimates (in seconds), indexed by classname.
53+
*/
54+
abstract public function generate_load_estimates(int $window): array;
55+
56+
/**
57+
* Main function for generating the load estimates array.
58+
*
59+
* @param array $scheduled
60+
* @param array $adhoc
61+
* @param int $start
62+
* @param int $finish
63+
* @return array
64+
*/
65+
protected function generate_load_estimates_inner(array $scheduled, array $adhoc, int $start, int $finish): array {
66+
// Loads will contain the estimates.
67+
$loads = [];
68+
69+
foreach ($scheduled as $record) {
70+
$task = taskmanager::scheduled_task_from_record($record);
71+
72+
// If the task has already started, then we add the remaining time.
73+
if ($record->timestarted > 0) {
74+
$started = (int) $record->timestarted;
75+
$elapsed = max(0, $start - $started);
76+
$avgduration = max(0.0, $record->mean - $elapsed);
77+
} else if ($record->nextruntime + $record->mean > $finish) {
78+
$avgduration = ($finish - $record->nextruntime);
79+
} else {
80+
$avgduration = $record->mean;
81+
}
82+
$classname = taskmanager::get_canonical_class_name($task::class);
83+
$loads[$classname] = [
84+
'type' => 'scheduled',
85+
'classname' => $classname,
86+
'totalduration' => $avgduration,
87+
'avgduration' => $avgduration,
88+
];
89+
}
90+
91+
foreach ($adhoc as $record) {
92+
$classname = taskmanager::get_canonical_class_name($record->classname);
93+
if (!isset($loads[$classname])) {
94+
$loads[$classname] = [
95+
'type' => 'adhoc',
96+
'classname' => $classname,
97+
'count' => 0,
98+
'totalduration' => 0,
99+
'avgduration' => 0,
100+
];
101+
}
102+
if ($record->timestarted > 0) {
103+
// Task has already started. Add remaining time.
104+
$started = (int) $record->timestarted;
105+
$elapsed = max(0, $start - $started);
106+
$remaining = max(0.0, $record->mean - $elapsed);
107+
$loads[$classname]['totalduration'] += $remaining;
108+
} else if ($record->nextruntime + $record->mean > $finish) {
109+
// Task will finish after window ends. Add time left in window.
110+
$loads[$classname]['totalduration'] += ($finish - $record->nextruntime);
111+
} else {
112+
// Task will run wholly within the window, so add the full mean.
113+
$loads[$classname]['totalduration'] += $record->mean;
114+
}
115+
++$loads[$classname]['count'];
116+
$loads[$classname]['avgduration'] = $loads[$classname]['totalduration'] / $loads[$classname]['count'];
117+
}
118+
119+
// Order by type then by estimated_seconds descending.
120+
usort($loads, static function ($a, $b) {
121+
$q = strcmp($a['type'], $b['type']);
122+
if ($q !== 0) {
123+
return $q;
124+
}
125+
return $b['totalduration'] <=> $a['totalduration'];
126+
});
127+
128+
return $loads;
129+
}
130+
}

0 commit comments

Comments
 (0)