Completed
Push — master ( 89cde0...6774c2 )
by Vitalii
45s queued 15s
created

quantiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
Expected 1 space before "=>"; 0 found
Loading history...
Coding Style introduced by
Expected 1 space after "=>"; 0 found
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
Coding Style introduced by
Expected 1 space before "=>"; 0 found
Loading history...
Coding Style introduced by
Expected 1 space after "=>"; 0 found
Loading history...
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
Unused Code introduced by
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 ignore-call  annotation

334
    /** @scrutinizer ignore-call */ 
335
    show_hosts($batch);

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
Coding Style introduced by
DEFAULT keyword must be indented 4 spaces from SWITCH keyword
Loading history...
Coding Style introduced by
DEFAULT case must have a breaking statement
Loading history...
337
    error_page('bad action');
338
}
339
340
?>
341