BOINC /
boinc
| 1 | <?php |
||||||
| 2 | // This file is part of BOINC. |
||||||
| 3 | // https://boinc.berkeley.edu |
||||||
| 4 | // Copyright (C) 2025 University of California |
||||||
| 5 | // |
||||||
| 6 | // BOINC is free software; you can redistribute it and/or modify it |
||||||
| 7 | // under the terms of the GNU Lesser General Public License |
||||||
| 8 | // as published by the Free Software Foundation, |
||||||
| 9 | // either version 3 of the License, or (at your option) any later version. |
||||||
| 10 | // |
||||||
| 11 | // BOINC is distributed in the hope that it will be useful, |
||||||
| 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
| 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||||
| 14 | // See the GNU Lesser General Public License for more details. |
||||||
| 15 | // |
||||||
| 16 | // You should have received a copy of the GNU Lesser General Public License |
||||||
| 17 | // along with BOINC. If not, see <http://www.gnu.org/licenses/>. |
||||||
| 18 | |||||||
| 19 | // show various stats of batches: |
||||||
| 20 | // err_host |
||||||
| 21 | // list of hosts with the most errors |
||||||
| 22 | // err_code |
||||||
| 23 | // list of exit codes with most errors |
||||||
| 24 | // flops_graph |
||||||
| 25 | // histogram of average CPU hours |
||||||
| 26 | |||||||
| 27 | require_once('../inc/util.inc'); |
||||||
| 28 | require_once('../inc/result.inc'); |
||||||
| 29 | |||||||
| 30 | function err_host($batch) { |
||||||
| 31 | $results = BoincResult::enum_fields( |
||||||
| 32 | 'hostid', |
||||||
| 33 | sprintf('batch=%d and outcome=%d', |
||||||
| 34 | $batch->id, |
||||||
| 35 | RESULT_OUTCOME_CLIENT_ERROR |
||||||
| 36 | ) |
||||||
| 37 | ); |
||||||
| 38 | $x = []; |
||||||
| 39 | foreach ($results as $r) { |
||||||
| 40 | $id = $r->hostid; |
||||||
| 41 | if (array_key_exists($id, $x)) { |
||||||
| 42 | $x[$id] += 1; |
||||||
| 43 | } else { |
||||||
| 44 | $x[$id] = 1; |
||||||
| 45 | } |
||||||
| 46 | } |
||||||
| 47 | if (!$x) error_page('That batch had no error results'); |
||||||
| 48 | page_head('Errors by host'); |
||||||
| 49 | text_start(); |
||||||
| 50 | arsort($x); |
||||||
| 51 | start_table(); |
||||||
| 52 | table_header('Host', 'OS', '# errors'); |
||||||
| 53 | $n = 0; |
||||||
| 54 | foreach ($x as $id=>$count) { |
||||||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||||||
| 55 | $host = BoincHost::lookup_id($id); |
||||||
| 56 | table_row( |
||||||
| 57 | "<a href=show_host_detail.php?hostid=$id>$host->domain_name</a>", |
||||||
| 58 | $host->os_name, |
||||||
| 59 | sprintf( |
||||||
| 60 | '<a href=submit_stats.php?action=host_list_errors&host_id=%d&batch_id=%d>%d</a>', |
||||||
| 61 | $id, $batch->id, $count |
||||||
| 62 | ) |
||||||
| 63 | ); |
||||||
| 64 | if (++$n == 20) break; |
||||||
| 65 | } |
||||||
| 66 | text_end(); |
||||||
| 67 | end_table(); |
||||||
| 68 | page_tail(); |
||||||
| 69 | } |
||||||
| 70 | |||||||
| 71 | function err_code($batch) { |
||||||
| 72 | $results = BoincResult::enum_fields( |
||||||
| 73 | 'exit_status', |
||||||
| 74 | sprintf('batch=%d and outcome=%d', |
||||||
| 75 | $batch->id, |
||||||
| 76 | RESULT_OUTCOME_CLIENT_ERROR |
||||||
| 77 | ) |
||||||
| 78 | ); |
||||||
| 79 | $x = []; |
||||||
| 80 | foreach ($results as $r) { |
||||||
| 81 | $id = $r->exit_status; |
||||||
| 82 | if (array_key_exists($id, $x)) { |
||||||
| 83 | $x[$id] += 1; |
||||||
| 84 | } else { |
||||||
| 85 | $x[$id] = 1; |
||||||
| 86 | } |
||||||
| 87 | } |
||||||
| 88 | if (!$x) error_page('That batch had no error results'); |
||||||
| 89 | page_head('Errors by exit code'); |
||||||
| 90 | text_start(); |
||||||
| 91 | arsort($x); |
||||||
| 92 | start_table(); |
||||||
| 93 | table_header('Code', '# errors'); |
||||||
| 94 | $n = 0; |
||||||
| 95 | foreach ($x as $id=>$count) { |
||||||
|
0 ignored issues
–
show
|
|||||||
| 96 | table_row( |
||||||
| 97 | exit_status_string($id), |
||||||
| 98 | sprintf( |
||||||
| 99 | '<a href=submit_stats.php?action=code_list&code=%d&batch_id=%d>%d</a>', |
||||||
| 100 | $id, $batch->id, $count |
||||||
| 101 | ) |
||||||
| 102 | ); |
||||||
| 103 | if (++$n == 20) break; |
||||||
| 104 | } |
||||||
| 105 | text_end(); |
||||||
| 106 | end_table(); |
||||||
| 107 | page_tail(); |
||||||
| 108 | } |
||||||
| 109 | |||||||
| 110 | // list of error results from a host |
||||||
| 111 | // |
||||||
| 112 | function host_list_errors($batch_id, $host_id) { |
||||||
| 113 | page_head("Errors for batch $batch_id, host $host_id"); |
||||||
| 114 | $results = BoincResult::enum( |
||||||
| 115 | sprintf('batch=%d and hostid=%d and outcome=%d', |
||||||
| 116 | $batch_id, $host_id, RESULT_OUTCOME_CLIENT_ERROR |
||||||
| 117 | ) |
||||||
| 118 | ); |
||||||
| 119 | |||||||
| 120 | start_table(); |
||||||
| 121 | table_header('Job instance', 'Exit status'); |
||||||
| 122 | foreach ($results as $r) { |
||||||
| 123 | table_row( |
||||||
| 124 | "<a href=result.php?resultid=$r->id>$r->name</a>", |
||||||
| 125 | exit_status_string($r->exit_status) |
||||||
| 126 | ); |
||||||
| 127 | } |
||||||
| 128 | end_table(); |
||||||
| 129 | page_tail(); |
||||||
| 130 | } |
||||||
| 131 | |||||||
| 132 | // list of error results with given code |
||||||
| 133 | // |
||||||
| 134 | function code_list($batch_id, $code) { |
||||||
| 135 | page_head("Errors for batch $batch_id, exit code $code"); |
||||||
| 136 | $results = BoincResult::enum( |
||||||
| 137 | sprintf('batch=%d and exit_status=%d and outcome=%d', |
||||||
| 138 | $batch_id, $code, RESULT_OUTCOME_CLIENT_ERROR |
||||||
| 139 | ) |
||||||
| 140 | ); |
||||||
| 141 | |||||||
| 142 | text_start(); |
||||||
| 143 | start_table(); |
||||||
| 144 | table_header('Job instance', 'Host'); |
||||||
| 145 | foreach ($results as $r) { |
||||||
| 146 | $host = BoincHost::lookup_id($r->hostid); |
||||||
| 147 | table_row( |
||||||
| 148 | "<a href=result.php?resultid=$r->id>$r->name</a>", |
||||||
| 149 | sprintf('<a href=show_host_detail.php?hostid=%d>%d (%s)</a>', |
||||||
| 150 | $host->id, |
||||||
| 151 | $host->id, |
||||||
| 152 | $host->os_name |
||||||
| 153 | ) |
||||||
| 154 | ); |
||||||
| 155 | } |
||||||
| 156 | end_table(); |
||||||
| 157 | text_end(); |
||||||
| 158 | page_tail(); |
||||||
| 159 | } |
||||||
| 160 | |||||||
| 161 | function graph($data, $id, $xlabel, $ylabel) { |
||||||
| 162 | echo " |
||||||
| 163 | <script type=\"text/javascript\"> |
||||||
| 164 | google.charts.load('current', {'packages':['corechart']}); |
||||||
| 165 | google.charts.setOnLoadCallback(drawChart); |
||||||
| 166 | |||||||
| 167 | function drawChart() { |
||||||
| 168 | var data = google.visualization.arrayToDataTable([ |
||||||
| 169 | "; |
||||||
| 170 | // huh? should work, see https://developers.google.com/chart/interactive/docs/reference#google.visualization.arraytodatatable |
||||||
| 171 | //echo "[{label: '$xlabel', type:'number'},{label:'$ylabel', type='number'}],\n"; |
||||||
| 172 | //echo "['$xlabel', '$ylabel'],\n"; |
||||||
| 173 | foreach ($data as [$x, $y]) { |
||||||
| 174 | echo "[$x, $y],\n"; |
||||||
| 175 | } |
||||||
| 176 | echo " |
||||||
| 177 | ], true); |
||||||
| 178 | |||||||
| 179 | var options = { |
||||||
| 180 | title: '', |
||||||
| 181 | legend: 'none', |
||||||
| 182 | hAxis: {title: '$xlabel'}, |
||||||
| 183 | vAxis: {title: '$ylabel'} |
||||||
| 184 | }; |
||||||
| 185 | |||||||
| 186 | var chart = new google.visualization.LineChart(document.getElementById('$id')); |
||||||
| 187 | |||||||
| 188 | chart.draw(data, options); |
||||||
| 189 | } |
||||||
| 190 | </script> |
||||||
| 191 | "; |
||||||
| 192 | echo sprintf( |
||||||
| 193 | '<div id="%s" style="width: 900px; height: 500px"></div>', $id |
||||||
| 194 | ); |
||||||
| 195 | } |
||||||
| 196 | |||||||
| 197 | // compute 25/50/75 quantiles of values |
||||||
| 198 | // |
||||||
| 199 | function quantiles($vals) { |
||||||
| 200 | sort($vals); |
||||||
| 201 | $n = count($vals); |
||||||
| 202 | return [$vals[intval($n*.25)], $vals[intval($n*.5)], $vals[intval($n*.75)]]; |
||||||
| 203 | } |
||||||
| 204 | |||||||
| 205 | // if $flops is true, show normalized runtime; else turnaround time |
||||||
| 206 | // |
||||||
| 207 | function batch_graph($batch, $flops) { |
||||||
| 208 | if ($flops) { |
||||||
| 209 | echo "<p>Runtimes of completed jobs, normalized to an average (4.3 GFLOPS) computer.<p>"; |
||||||
| 210 | // 'flops' is CPU benchmark times elapsed (i.e. run) time. |
||||||
| 211 | // as of 10/2025, cpu_time is way off for BUDA jobs |
||||||
| 212 | // |
||||||
| 213 | $results = BoincResult::enum_fields( |
||||||
| 214 | 'flops_estimate*elapsed_time/(4.3e9*3600) as val', |
||||||
| 215 | sprintf('batch=%d and outcome=%d', |
||||||
| 216 | $batch->id, RESULT_OUTCOME_SUCCESS |
||||||
| 217 | ) |
||||||
| 218 | ); |
||||||
| 219 | } else { |
||||||
| 220 | page_head("Batch $batch->id turnaround times"); |
||||||
| 221 | echo "<p>Turnaround times of completed jobs.<p>"; |
||||||
| 222 | $results = BoincResult::enum_fields( |
||||||
| 223 | '(received_time-sent_time)/3600 as val', |
||||||
| 224 | sprintf('batch=%d and outcome=%d', |
||||||
| 225 | $batch->id, RESULT_OUTCOME_SUCCESS |
||||||
| 226 | ) |
||||||
| 227 | ); |
||||||
| 228 | } |
||||||
| 229 | |||||||
| 230 | $x = []; |
||||||
| 231 | $min = 1e99; |
||||||
| 232 | $max = 0; |
||||||
| 233 | foreach ($results as $r) { |
||||||
| 234 | $f = $r->val; |
||||||
| 235 | if ($f > $max) $max = $f; |
||||||
| 236 | if ($f < $min) $min = $f; |
||||||
| 237 | $x[] = $f; |
||||||
| 238 | } |
||||||
| 239 | $n = 100; |
||||||
| 240 | $count = []; |
||||||
| 241 | for ($i=0; $i<$n; $i++) { |
||||||
| 242 | $count[$i] = 0; |
||||||
| 243 | } |
||||||
| 244 | $range = $max - $min; |
||||||
| 245 | foreach ($x as $f) { |
||||||
| 246 | $d = intval($n*($f-$min)/$range); |
||||||
| 247 | if ($d >= $n) $d = $n-1; |
||||||
| 248 | $count[$d] += 1; |
||||||
| 249 | } |
||||||
| 250 | $data = []; |
||||||
| 251 | for ($i=0; $i<$n; $i++) { |
||||||
| 252 | $data[] = [$min+($i*$range)/$n, $count[$i]]; |
||||||
| 253 | } |
||||||
| 254 | if ($flops) { |
||||||
| 255 | graph($data, 'runtime', 'Job runtime (hours)', 'job count'); |
||||||
| 256 | } else { |
||||||
| 257 | graph($data, 'turnaround', 'Turnaround time (hours)', 'job count'); |
||||||
| 258 | } |
||||||
| 259 | [$x25, $x5, $x75] = quantiles($x); |
||||||
| 260 | echo sprintf('quantiles: %s %s %s', |
||||||
| 261 | number_format($x25, 2), |
||||||
| 262 | number_format($x5, 2), |
||||||
| 263 | number_format($x75, 2) |
||||||
| 264 | ); |
||||||
| 265 | } |
||||||
| 266 | |||||||
| 267 | function batch_graphs($batch) { |
||||||
| 268 | page_head("Batch $batch->id job times"); |
||||||
| 269 | echo " |
||||||
| 270 | <script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script> |
||||||
| 271 | "; |
||||||
| 272 | echo '<hr>'; |
||||||
| 273 | batch_graph($batch, true); |
||||||
| 274 | echo '<hr>'; |
||||||
| 275 | batch_graph($batch, false); |
||||||
| 276 | page_tail(); |
||||||
| 277 | } |
||||||
| 278 | |||||||
| 279 | // show hosts that did jobs for this batch |
||||||
| 280 | function show_hosts($batch) { |
||||||
| 281 | $results = BoincResult::enum_fields( |
||||||
| 282 | 'hostid', |
||||||
| 283 | sprintf('batch=%d and outcome=%d', |
||||||
| 284 | $batch->id, |
||||||
| 285 | RESULT_OUTCOME_SUCCESS |
||||||
| 286 | ) |
||||||
| 287 | ); |
||||||
| 288 | $x = []; |
||||||
| 289 | foreach ($results as $r) { |
||||||
| 290 | $id = $r->hostid; |
||||||
| 291 | if (array_key_exists($id, $x)) { |
||||||
| 292 | $x[$id] += 1; |
||||||
| 293 | } else { |
||||||
| 294 | $x[$id] = 1; |
||||||
| 295 | } |
||||||
| 296 | } |
||||||
| 297 | arsort($x); |
||||||
| 298 | page_head("Batch $batch->id: completed jobs grouped by host"); |
||||||
| 299 | text_start(); |
||||||
| 300 | start_table(); |
||||||
| 301 | table_header('Host', 'OS', '# jobs'); |
||||||
| 302 | foreach ($x as $id => $count) { |
||||||
| 303 | $host = BoincHost::lookup_id($id); |
||||||
| 304 | table_row( |
||||||
| 305 | "<a href=show_host_detail.php?hostid=$id>$id</a>", |
||||||
| 306 | $host->os_name, |
||||||
| 307 | $count |
||||||
| 308 | ); |
||||||
| 309 | } |
||||||
| 310 | end_table(); |
||||||
| 311 | text_end(); |
||||||
| 312 | page_tail(); |
||||||
| 313 | } |
||||||
| 314 | |||||||
| 315 | $batch = BoincBatch::lookup_id(get_int('batch_id')); |
||||||
| 316 | if (!$batch) error_page('no batch'); |
||||||
| 317 | switch(get_str('action')) { |
||||||
| 318 | case 'err_host': |
||||||
| 319 | err_host($batch); |
||||||
| 320 | break; |
||||||
| 321 | case 'err_code': |
||||||
| 322 | err_code($batch); |
||||||
| 323 | break; |
||||||
| 324 | case 'host_list_errors': |
||||||
| 325 | host_list_errors($batch->id, get_int('host_id')); |
||||||
| 326 | break; |
||||||
| 327 | case 'code_list': |
||||||
| 328 | code_list($batch->id, get_int('code')); |
||||||
| 329 | break; |
||||||
| 330 | case 'graphs': |
||||||
| 331 | batch_graphs($batch); |
||||||
| 332 | break; |
||||||
| 333 | case 'show_hosts': |
||||||
| 334 | show_hosts($batch); |
||||||
|
0 ignored issues
–
show
The call to
show_hosts() has too many arguments starting with $batch.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. Loading history...
|
|||||||
| 335 | break; |
||||||
| 336 | default: |
||||||
|
0 ignored issues
–
show
|
|||||||
| 337 | error_page('bad action'); |
||||||
| 338 | } |
||||||
| 339 | |||||||
| 340 | ?> |
||||||
| 341 |