1<?php
2// This file is part of BOINC.
3// http://boinc.berkeley.edu
4// Copyright (C) 2014 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 server status page.
20// Sources of data:
21// - daemons on this host: use "ps" to see if each is running
22//   (this could be made more efficient using a single "ps",
23//   or it could be cached)
24// - daemons on other hosts: get from a cached file generated periodically
25//   by ops/remote_server_status.php
26//   (we can't do this ourselves because apache can't generally ssh)
27// - apps and job counts: get from a cached file that we generate ourselves
28
29require_once("../inc/cache.inc");
30require_once("../inc/util.inc");
31require_once("../inc/xml.inc");
32require_once("../inc/boinc_db.inc");
33
34if (!defined('STATUS_PAGE_TTL')) {
35    define('STATUS_PAGE_TTL', 3600);
36}
37
38// trim a daemon command for display.
39// For now, remove the cmdline args, but show the app if any
40//
41function command_display($cmd) {
42    $x = explode(" -", $cmd);
43    $prog = $x[0];
44    $x = strpos($cmd, "-app ");
45    if ($x) {
46        $y = substr($cmd, $x);
47        $y = explode(" ", $y);
48        $app = $y[1];
49        $prog .= " ($app)";
50    }
51    return $prog;
52}
53
54function daemon_html($d) {
55    switch ($d->status) {
56    case 0:
57        $s = tra("Not Running");
58        $c = "bg-danger";
59        break;
60    case 1:
61        $s = tra("Running");
62        $c = "bg-success";
63        break;
64    default:
65        $s = tra("Disabled");
66        $c = "bg-warning";
67        break;
68    }
69    echo "<tr>
70        <td>".command_display($d->cmd)."</td>
71        <td>$d->host</td>
72        <td class=\"$c\"><nobr>$s</nobr></td>
73        </tr>
74    ";
75}
76
77function daemon_xml($d) {
78    switch ($d->status) {
79    case 0: $s = "not running"; break;
80    case 1: $s = "running"; break;
81    default: $s = "disabled";
82    }
83    echo "  <daemon>
84        <host>$d->host</host>
85        <command>".command_display($d->cmd)."</command>
86        <status>$s</status>
87    </daemon>
88";
89}
90
91function item_xml($name, $val) {
92    if (!$val) $val = 0;
93    echo "   <$name>$val</$name>\n";
94}
95
96function item_html($name, $val) {
97    $name = tra($name);
98    echo "<tr><td>$name</td><td>$val</td></tr>\n";
99    //echo "<tr><td align=right>$name</td><td align=right>$val</td></tr>\n";
100}
101
102function show_status_html($x) {
103    page_head(tra("Project status"));
104    $j = $x->jobs;
105    $daemons = $x->daemons;
106    start_table();
107    echo "<tr><td>\n";
108            echo "
109                 <h3>".tra("Server status")."</h3>
110            ";
111            start_table('table-striped');
112            table_header(tra("Program"), tra("Host"), tra("Status"));
113            foreach ($daemons->local_daemons as $d) {
114                daemon_html($d);
115            }
116            foreach ($daemons->remote_daemons as $d) {
117                daemon_html($d);
118            }
119            foreach ($daemons->disabled_daemons as $d) {
120                daemon_html($d);
121            }
122            end_table();
123
124            if ($daemons->cached_time) {
125                echo "<br>Remote daemon status as of ", time_str($daemons->cached_time);
126            }
127            if ($daemons->missing_remote_status) {
128                echo "<br>Status of remote daemons is missing\n";
129            }
130            if (function_exists('server_status_project_info')) {
131                echo "<br>";
132                server_status_project_info();
133            }
134    echo "</td><td>\n";
135            echo "<h3>".tra("Computing status")."</h3>\n";
136            echo "<h4>".tra("Work")."</h4>\n";
137            start_table('table-striped');
138            item_html("Tasks ready to send", $j->results_ready_to_send);
139            item_html("Tasks in progress", $j->results_in_progress);
140            item_html("Workunits waiting for validation", $j->wus_need_validate);
141            item_html("Workunits waiting for assimilation", $j->wus_need_assimilate);
142            item_html("Workunits waiting for file deletion", $j->wus_need_file_delete);
143            item_html("Tasks waiting for file deletion", $j->results_need_file_delete);
144            item_html("Transitioner backlog (hours)", number_format($j->transitioner_backlog, 2));
145            end_table();
146            echo "<h4>".tra("Users")."</h4>\n";
147            start_table('table-striped');
148            item_html("With credit", $j->users_with_credit);
149            item_html("With recent credit", $j->users_with_recent_credit);
150            item_html("Registered in past 24 hours", $j->users_past_24_hours);
151            end_table();
152            echo "<h4>".tra("Computers")."</h4>\n";
153            start_table('table-striped');
154            item_html("With credit", $j->hosts_with_credit);
155            item_html("With recent credit", $j->hosts_with_recent_credit);
156            item_html("Registered in past 24 hours", $j->hosts_past_24_hours);
157            item_html("Current GigaFLOPS", round($j->flops, 2));
158            end_table();
159    echo "</td></tr>\n";
160    end_table();
161    echo "<h3>".tra("Tasks by application")."</h3>\n";
162    start_table('table-striped');
163    table_header(
164        tra("Application"),
165        tra("Unsent"),
166        tra("In progress"),
167        tra("Runtime of last 100 tasks in hours: average, min, max"),
168        tra("Users in last 24 hours")
169    );
170    foreach ($j->apps as $app) {
171        $avg = round($app->info->avg, 2);
172        $min = round($app->info->min, 2);
173        $max = round($app->info->max, 2);
174        $x = $max?"$avg ($min - $max)":"---";
175        $u = $app->info->users;
176        echo "<tr>
177            <td>$app->user_friendly_name</td>
178            <td>$app->unsent</td>
179            <td>$app->in_progress</td>
180            <td>$x</td>
181            <td>$u</td>
182            </tr>
183        ";
184    }
185    end_table();
186    if ($j->db_revision) {
187        echo tra("Database schema version: "), $j->db_revision;
188    }
189    echo "<p>Task data as of ".time_str($j->cached_time);
190    page_tail();
191}
192
193function show_status_xml($x) {
194    xml_header();
195    echo "<server_status>\n<daemon_status>\n";
196
197    $daemons = $x->daemons;
198    foreach ($daemons->local_daemons as $d) {
199        daemon_xml($d);
200    }
201    foreach ($daemons->remote_daemons as $d) {
202        daemon_xml($d);
203    }
204    foreach ($daemons->disabled_daemons as $d) {
205        daemon_xml($d);
206    }
207    echo "</daemon_status>\n<database_file_states>\n";
208    $j = $x->jobs;
209    item_xml("results_ready_to_send", $j->results_ready_to_send);
210    item_xml("results_in_progress", $j->results_in_progress);
211    item_xml("workunits_waiting_for_validation", $j->wus_need_validate);
212    item_xml("workunits_waiting_for_assimilation", $j->wus_need_assimilate);
213    item_xml("workunits_waiting_for_deletion", $j->wus_need_file_delete);
214    item_xml("results_waiting_for_deletion", $j->results_need_file_delete);
215    item_xml("transitioner_backlog_hours", $j->transitioner_backlog);
216    item_xml("users_with_recent_credit", $j->users_with_recent_credit);
217    item_xml("users_with_credit", $j->users_with_credit);
218    item_xml("users_registered_in_past_24_hours", $j->users_past_24_hours);
219    item_xml("hosts_with_recent_credit", $j->hosts_with_recent_credit);
220    item_xml("hosts_with_credit", $j->hosts_with_credit);
221    item_xml("hosts_registered_in_past_24_hours", $j->hosts_past_24_hours);
222    item_xml("current_floating_point_speed", $j->flops);
223    echo "<tasks_by_app>\n";
224    foreach ($j->apps as $app) {
225        echo "<app>\n";
226        item_xml("id", $app->id);
227        item_xml("name", $app->name);
228        item_xml("unsent", $app->unsent);
229        item_xml("in_progress", $app->in_progress);
230        item_xml("avg_runtime", $app->info->avg);
231        item_xml("min_runtime", $app->info->min);
232        item_xml("max_runtime", $app->info->max);
233        item_xml("users", $app->info->users);
234        echo "</app>\n";
235    }
236    echo "</tasks_by_app>
237</database_file_states>
238</server_status>
239";
240}
241
242function local_daemon_running($cmd, $pidname, $host) {
243    if (!$pidname) {
244        $cmd = trim($cmd);
245        $x = explode(" ", $cmd);
246        $prog = $x[0];
247        $pidname = $prog . '.pid';
248    }
249    $path = "../../pid_$host/$pidname";
250    if (is_file($path)) {
251        $pid = file_get_contents($path);
252        if ($pid) {
253            $pid = trim($pid);
254            $out = Array();
255            exec("ps -ww $pid", $out);
256            foreach ($out as $y) {
257                if (strstr($y, (string)$pid)) return 1;
258            }
259        }
260    }
261    return 0;
262}
263
264// returns a data structure of the form
265// local_daemons: array of
266//   cmd, status
267// remote_daemons: array of
268//   cmd, host, status
269// disabled_daemons: array of
270//   cmd, host
271//
272function get_daemon_status() {
273    $c = simplexml_load_file("../../config.xml");
274    if (!$c) {
275        die("can't parse config file\n");
276    }
277    $daemons = $c->daemons;
278    $config = $c->config;
279    $main_host = trim((string)$config->host);
280    $master_url = trim((string)$config->master_url);
281    $u = parse_url($master_url);
282    if (!array_key_exists("host", $u)) {
283        print_r($u);
284        die("can't parse URL $master_url");
285    }
286    $master_host = $u["host"];
287    if ($config->www_host) {
288        $web_host = trim((string) $config->www_host);
289    } else {
290        $web_host = $main_host;
291    }
292    if ($config->sched_host) {
293        $sched_host = trim((string) $config->sched_host);
294    } else {
295        $sched_host = $main_host;
296    }
297    $have_remote = false;
298    $local_daemons = array();
299    $disabled_daemons = array();
300
301    // the upload and download servers are sort of daemons too
302    //
303    $url = trim((string) $config->download_url);
304    $u = parse_url($url);
305    $h = $u["host"];
306    if ($h == $master_host) {
307        $y = new StdClass;
308        $y->cmd = "Download server";
309        $y->host = $h;
310        $y->status = 1;
311        $local_daemons[] = $y;
312    } else {
313        $have_remote = true;
314    }
315    $url = trim((string) $config->upload_url);
316    $u = parse_url($url);
317    $h = $u["host"];
318    if ($h == $master_host) {
319        $y = new StdClass;
320        $y->cmd = "Upload server";
321        $y->host = $h;
322        $y->status = !file_exists("../../stop_upload");;
323        $local_daemons[] = $y;
324    } else {
325        $have_remote = true;
326    }
327
328    // Scheduler is a daemon too
329    //
330    if ($sched_host == $main_host) {
331        $y = new StdClass;
332        $y->cmd = "Scheduler";
333        $y->host = $sched_host;
334        $y->status = !file_exists("../../stop_sched");;
335        $local_daemons[] = $y;
336    } else {
337        $have_remote = true;
338    }
339
340    foreach ($daemons->daemon as $d) {
341        if ((int)$d->disabled != 0) {
342            $x = new StdClass;
343            $x->cmd = (string)$d->cmd;
344            $x->host = (string)$d->host;
345            if (!$x->host) $x->host = $main_host;
346            $x->status = -1;
347            $disabled_daemons[] = $x;
348            continue;
349        }
350        $host = $d->host?(string)$d->host:$main_host;
351        if ($host != $web_host) {
352            $have_remote = true;
353            continue;
354        }
355        $x = new StdClass;
356        $x->cmd = (string)$d->cmd;
357        $x->status = local_daemon_running($x->cmd, trim($d->pid_file), $web_host);
358        $x->host = $web_host;
359        $local_daemons[] = $x;
360
361    }
362
363    $x = new StdClass;
364    $x->local_daemons = $local_daemons;
365    $x->disabled_daemons = $disabled_daemons;
366    $x->missing_remote_status = false;
367    $x->cached_time = 0;
368    $x->remote_daemons = array();
369    if ($have_remote) {
370        $f = @file_get_contents("../cache/remote_server_status");
371        if ($f) {
372            $x->remote_daemons = unserialize($f);
373            $x->cached_time = filemtime("../cache/remote_server_status");
374        } else {
375            $x->missing_remote_status = true;
376        }
377    }
378    return $x;
379}
380
381function get_job_status() {
382    $s = unserialize(get_cached_data(STATUS_PAGE_TTL, "job_status"));
383    if ($s) {
384        return $s;
385    }
386
387    $s = new StdClass;
388    $apps = BoincApp::enum("deprecated=0");
389    foreach ($apps as $app) {
390        $info = BoincDB::get()->lookup_fields("result", "stdClass",
391            "ceil(avg(elapsed_time)/3600*100)/100 as avg,
392            ceil(min(elapsed_time)/3600*100)/100 as min,
393            ceil(max(elapsed_time)/3600*100)/100 as max,
394            count(distinct userid) as users",
395            "appid = $app->id
396            AND validate_state=1
397            AND received_time > (unix_timestamp()-86400)
398            "
399        );
400        $app->info = $info;
401        $app->unsent = BoincResult::count("appid=$app->id and server_state=2");
402        $app->in_progress = BoincResult::count("appid=$app->id and server_state=4");
403    }
404    $s->apps = $apps;
405    $s->results_ready_to_send = BoincResult::count("server_state=2");
406    $s->results_in_progress = BoincResult::count("server_state=4");
407    $s->results_need_file_delete = BoincResult::count("file_delete_state=1");
408    $s->wus_need_validate = BoincWorkunit::count("need_validate=1");
409    $s->wus_need_assimilate = BoincWorkunit::count("assimilate_state=1");
410    $s->wus_need_file_delete = BoincWorkunit::count("file_delete_state=1");
411    $x = BoincDB::get()->lookup_fields("workunit", "stdClass", "MIN(transition_time) as min", "TRUE");
412    $gap = (time() - $x->min)/3600;
413    if (($gap < 0) || ($x->min == 0)) {
414        $gap = 0;
415    }
416    $s->transitioner_backlog = $gap;
417    $s->users_with_recent_credit = BoincUser::count("expavg_credit>1");
418    $s->users_with_credit = BoincUser::count("total_credit>1");
419    $s->users_past_24_hours = BoincUser::count("create_time > (unix_timestamp() - 86400)");
420    $s->hosts_with_recent_credit = BoincHost::count("expavg_credit>1");
421    $s->hosts_with_credit = BoincHost::count("total_credit>1");
422    $s->hosts_past_24_hours = BoincHost::count("create_time > (unix_timestamp() - 86400)");
423    $s->flops = BoincUser::sum("expavg_credit")/200;
424
425    $s->db_revision = null;
426    if (file_exists("../../db_revision")) {
427        $s->db_revision = trim(file_get_contents("../../db_revision"));
428    }
429
430    $s->cached_time = time();
431    $e = set_cached_data(STATUS_PAGE_TTL, serialize($s), "job_status");
432    if ($e) echo "set_cached_data(): $e\n";
433    return $s;
434}
435
436function main() {
437    $x = new StdClass;
438    $x->daemons = get_daemon_status();
439    $x->jobs = get_job_status();
440    if (get_int('xml', true)) {
441        show_status_xml($x);
442    } else {
443        show_status_html($x);
444    }
445}
446
447main();
448?>
449