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