| blib/lib/Helios/Panoptes.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 22 | 24 | 91.6 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 8 | 8 | 100.0 |
| pod | n/a | ||
| total | 30 | 32 | 93.7 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Helios::Panoptes; | ||||||
| 2 | |||||||
| 3 | 1 | 1 | 23408 | use 5.008000; | |||
| 1 | 3 | ||||||
| 1 | 41 | ||||||
| 4 | 1 | 1 | 5 | use strict; | |||
| 1 | 2 | ||||||
| 1 | 36 | ||||||
| 5 | 1 | 1 | 5 | use warnings; | |||
| 1 | 6 | ||||||
| 1 | 38 | ||||||
| 6 | 1 | 1 | 5 | use base qw(CGI::Application); | |||
| 1 | 1 | ||||||
| 1 | 1349 | ||||||
| 7 | 1 | 1 | 22068 | use Data::Dumper; | |||
| 1 | 16916 | ||||||
| 1 | 88 | ||||||
| 8 | |||||||
| 9 | 1 | 1 | 1143 | use CGI::Application::Plugin::DBH qw(dbh_config dbh); | |||
| 1 | 1982 | ||||||
| 1 | 77 | ||||||
| 10 | 1 | 1 | 1023 | use Error qw(:try); | |||
| 1 | 4138 | ||||||
| 1 | 6 | ||||||
| 11 | |||||||
| 12 | 1 | 1 | 702 | use Helios::Service; | |||
| 0 | |||||||
| 0 | |||||||
| 13 | |||||||
| 14 | our $VERSION = '1.51_3070'; | ||||||
| 15 | our $CONF_PARAMS; | ||||||
| 16 | |||||||
| 17 | =head1 NAME | ||||||
| 18 | |||||||
| 19 | Helios::Panoptes - CGI::Application providing web admin interface to Helios distributed job | ||||||
| 20 | processing system | ||||||
| 21 | |||||||
| 22 | =head1 DESCRIPTION | ||||||
| 23 | |||||||
| 24 | Helios::Panoptes is the web interface to the Helios distributed job processing system. It | ||||||
| 25 | provides a central point of control for all of the services and jobs in a Helios collective. This | ||||||
| 26 | web interface can be used to track jobs through the system and manage workloads on a per service | ||||||
| 27 | and per host basis. Available workers may be increased or decreased as necessary, tuned to | ||||||
| 28 | match available resources. Job processing can be held, or Helios daemons can be HALTed, from this | ||||||
| 29 | interface. | ||||||
| 30 | |||||||
| 31 | (Why I |
||||||
| 32 | |||||||
| 33 | =head1 CGI::Application SETUP METHODS | ||||||
| 34 | |||||||
| 35 | =head2 setup() | ||||||
| 36 | |||||||
| 37 | The setup() method defines the available run modes, which include: | ||||||
| 38 | |||||||
| 39 | =over 4 | ||||||
| 40 | |||||||
| 41 | =item ctrl_panel | ||||||
| 42 | |||||||
| 43 | The Ctrl Panel is the web interface to the central Helios configuration parameter repository (the | ||||||
| 44 | helios_params_tb table in the Helios database). | ||||||
| 45 | |||||||
| 46 | =item ctrl_panel_mod | ||||||
| 47 | |||||||
| 48 | Used by the Ctrl Panel and Worker Admin run modes to change configuration parameters in | ||||||
| 49 | helios_params_tb. | ||||||
| 50 | |||||||
| 51 | =item job_queue_view | ||||||
| 52 | |||||||
| 53 | The Job Queue view provides views of waiting, running, and completed jobs in the Helios collective. | ||||||
| 54 | |||||||
| 55 | =item collective | ||||||
| 56 | |||||||
| 57 | The Collective Admin provides a simple, dashboard-style view of the current services running in | ||||||
| 58 | the Helios collective, broken down by host. The service class version is displayed, as well | ||||||
| 59 | as the daemons' uptime. Job processing can be held and unheld here, and the daemons' run modes | ||||||
| 60 | can be shifted between Normal and Overdrive mode. The maximum workers for each service can be | ||||||
| 61 | managed, and services can be shut down here via the HALT button. | ||||||
| 62 | |||||||
| 63 | =item job_submit | ||||||
| 64 | |||||||
| 65 | The Submit Job view provides a simple interface to submit a test job to the Helios collective for | ||||||
| 66 | debugging purposes. | ||||||
| 67 | |||||||
| 68 | =back | ||||||
| 69 | |||||||
| 70 | =cut | ||||||
| 71 | |||||||
| 72 | sub setup { | ||||||
| 73 | my $self = shift; | ||||||
| 74 | $self->start_mode('job_queue_view'); | ||||||
| 75 | $self->mode_param('rm'); | ||||||
| 76 | $self->run_modes( | ||||||
| 77 | ctrl_panel => 'ctrl_panel', | ||||||
| 78 | ctrl_panel_mod => 'ctrl_panel_mod', | ||||||
| 79 | job_queue_view => 'job_queue_view', | ||||||
| 80 | job_detail => 'job_detail', | ||||||
| 81 | job_submit => 'job_submit', | ||||||
| 82 | collective => 'collective', | ||||||
| 83 | ); | ||||||
| 84 | |||||||
| 85 | my $inifile; | ||||||
| 86 | if (defined($ENV{HELIOS_INI}) ) { | ||||||
| 87 | $inifile = $ENV{HELIOS_INI}; | ||||||
| 88 | } else { | ||||||
| 89 | $inifile = './helios.ini'; | ||||||
| 90 | } | ||||||
| 91 | $self->{service} = Helios::Service->new(); | ||||||
| 92 | $self->{service}->prep(); | ||||||
| 93 | my $config = $self->{service}->getConfig(); | ||||||
| 94 | $CONF_PARAMS = $config; | ||||||
| 95 | |||||||
| 96 | # connect to db | ||||||
| 97 | $self->dbh_config($config->{dsn},$config->{user},$config->{password}); | ||||||
| 98 | } | ||||||
| 99 | |||||||
| 100 | =head2 teardown() | ||||||
| 101 | |||||||
| 102 | The only thing that currently happens in teardown() is the database is disconnected. | ||||||
| 103 | |||||||
| 104 | =cut | ||||||
| 105 | |||||||
| 106 | sub teardown { | ||||||
| 107 | my $self = shift; | ||||||
| 108 | |||||||
| 109 | $self->dbh->disconnect(); | ||||||
| 110 | } | ||||||
| 111 | |||||||
| 112 | =head1 RUN MODE METHODS | ||||||
| 113 | |||||||
| 114 | These methods define code that back the particular application pages. | ||||||
| 115 | |||||||
| 116 | =head2 ctrl_panel() | ||||||
| 117 | |||||||
| 118 | This method controls the rendering of the Ctrl Panel view, used to display Helios configuration | ||||||
| 119 | parameters. The view also allows the user to change config parameters, although the actual config | ||||||
| 120 | modifications are handled by the ctrl_panel_mod() run mode. | ||||||
| 121 | |||||||
| 122 | =cut | ||||||
| 123 | |||||||
| 124 | sub ctrl_panel { | ||||||
| 125 | my $self = shift; | ||||||
| 126 | my $dbh = $self->dbh(); | ||||||
| 127 | my $q = $self->query(); | ||||||
| 128 | |||||||
| 129 | my $output; | ||||||
| 130 | |||||||
| 131 | my $sql = < | ||||||
| 132 | SELECT worker_class, | ||||||
| 133 | host, | ||||||
| 134 | param, | ||||||
| 135 | value | ||||||
| 136 | FROM helios_params_tb | ||||||
| 137 | ORDER BY worker_class, host, param | ||||||
| 138 | PNLSQL | ||||||
| 139 | |||||||
| 140 | my $sth = $dbh->prepare($sql); | ||||||
| 141 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 142 | |||||||
| 143 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 144 | |||||||
| 145 | my $classes; | ||||||
| 146 | my $hosts; | ||||||
| 147 | my $params = []; | ||||||
| 148 | my $last_host; | ||||||
| 149 | my $last_class; | ||||||
| 150 | my $current_host; | ||||||
| 151 | my $current_class; | ||||||
| 152 | my $first_result = 1; | ||||||
| 153 | while (my $result = $sth->fetchrow_hashref() ) { | ||||||
| 154 | if ($first_result) { | ||||||
| 155 | $last_class = $result->{worker_class}; | ||||||
| 156 | $last_host = $result->{host}; | ||||||
| 157 | $first_result = 0; | ||||||
| 158 | } | ||||||
| 159 | if ($result->{worker_class} ne $last_class) { | ||||||
| 160 | $current_host->{PARAMS} = $params; | ||||||
| 161 | $current_host->{HOST} = $last_host; | ||||||
| 162 | $current_host->{WORKER_CLASS} = $last_class; | ||||||
| 163 | push(@$hosts, $current_host); | ||||||
| 164 | undef $params; | ||||||
| 165 | undef $current_host; | ||||||
| 166 | $last_host = $result->{host}; | ||||||
| 167 | |||||||
| 168 | $current_class->{HOSTS} = $hosts; | ||||||
| 169 | $current_class->{WORKER_CLASS} = $last_class; | ||||||
| 170 | push(@$classes, $current_class); | ||||||
| 171 | undef $hosts; | ||||||
| 172 | undef $current_class; | ||||||
| 173 | $last_class = $result->{worker_class}; | ||||||
| 174 | } | ||||||
| 175 | if ($result->{host} ne $last_host) { | ||||||
| 176 | $current_host->{PARAMS} = $params; | ||||||
| 177 | $current_host->{HOST} = $last_host; | ||||||
| 178 | $current_host->{WORKER_CLASS} = $last_class; | ||||||
| 179 | push(@$hosts, $current_host); | ||||||
| 180 | undef $params; | ||||||
| 181 | undef $current_host; | ||||||
| 182 | $last_host = $result->{host}; | ||||||
| 183 | } | ||||||
| 184 | |||||||
| 185 | push(@$params, $result); | ||||||
| 186 | } | ||||||
| 187 | |||||||
| 188 | $current_host->{PARAMS} = $params; | ||||||
| 189 | $current_host->{HOST} = $last_host; | ||||||
| 190 | $current_host->{WORKER_CLASS} = $last_class; | ||||||
| 191 | push(@$hosts, $current_host); | ||||||
| 192 | |||||||
| 193 | $current_class->{HOSTS} = $hosts; | ||||||
| 194 | $current_class->{WORKER_CLASS} = $last_class; | ||||||
| 195 | push(@$classes, $current_class); | ||||||
| 196 | |||||||
| 197 | my $tmpl = $self->load_tmpl(undef, die_on_bad_params => 0); | ||||||
| 198 | $tmpl->param(TITLE => "Helios - Control Panel"); | ||||||
| 199 | |||||||
| 200 | # only fill in parameters if we actually have some | ||||||
| 201 | if ( $classes->[0]->{HOSTS}->[0]->{HOST} ) { | ||||||
| 202 | $tmpl->param(CLASSES => $classes); | ||||||
| 203 | } | ||||||
| 204 | return $tmpl->output(); | ||||||
| 205 | } | ||||||
| 206 | |||||||
| 207 | |||||||
| 208 | =head2 ctrl_panel_mod() | ||||||
| 209 | |||||||
| 210 | Run mode used to modify Helios config parameters. Used by ctrl_panel() and collective(). | ||||||
| 211 | |||||||
| 212 | The ctrl_panel_mod run mode uses the following parameters: | ||||||
| 213 | |||||||
| 214 | =over 4 | ||||||
| 215 | |||||||
| 216 | =item worker_class | ||||||
| 217 | |||||||
| 218 | The worker (service) class of the changed parameter | ||||||
| 219 | |||||||
| 220 | =item host | ||||||
| 221 | |||||||
| 222 | The host of the changed parameter (* for all hosts) | ||||||
| 223 | |||||||
| 224 | =item param | ||||||
| 225 | |||||||
| 226 | THe name of the parameter | ||||||
| 227 | |||||||
| 228 | =item value | ||||||
| 229 | |||||||
| 230 | The value the parameter should be changed to | ||||||
| 231 | |||||||
| 232 | =item action | ||||||
| 233 | |||||||
| 234 | The action (add, modify, delete) to perform. A delete action will delete the param for the worker | ||||||
| 235 | class and host in question (obviously), add will add it, and modify will replace any existing | ||||||
| 236 | values of the parameter with the new value. | ||||||
| 237 | |||||||
| 238 | =back | ||||||
| 239 | |||||||
| 240 | =cut | ||||||
| 241 | |||||||
| 242 | sub ctrl_panel_mod { | ||||||
| 243 | my $self = shift; | ||||||
| 244 | my $dbh = $self->dbh(); | ||||||
| 245 | my $q = $self->query(); | ||||||
| 246 | my $return_to = $q->param('return_to'); | ||||||
| 247 | |||||||
| 248 | my $sql; | ||||||
| 249 | |||||||
| 250 | my $worker_class = $q->param('worker_class'); | ||||||
| 251 | my $host = $q->param('host'); | ||||||
| 252 | my $param = $q->param('param'); | ||||||
| 253 | my $value = $q->param('value'); | ||||||
| 254 | my $action = $q->param('action'); | ||||||
| 255 | |||||||
| 256 | unless ($worker_class && $host && $param && $action) { | ||||||
| 257 | throw Error::Simple("Worker class ($worker_class), host ($host), param ($param), and action ($action) required"); | ||||||
| 258 | } | ||||||
| 259 | |||||||
| 260 | $self->modParam($action, $worker_class, $host, $param, $value); | ||||||
| 261 | |||||||
| 262 | if (defined($return_to)) { | ||||||
| 263 | if ( defined($q->param('groupby')) ) { | ||||||
| 264 | print $q->redirect("./panoptes.pl?rm=$return_to&groupby=".$q->param('groupby')); | ||||||
| 265 | } | ||||||
| 266 | print $q->redirect("./panoptes.pl?rm=$return_to"); | ||||||
| 267 | } else { | ||||||
| 268 | print $q->redirect('./panoptes.pl?rm=ctrl_panel'); | ||||||
| 269 | } | ||||||
| 270 | return 1; | ||||||
| 271 | } | ||||||
| 272 | |||||||
| 273 | |||||||
| 274 | =head2 job_queue_view() | ||||||
| 275 | |||||||
| 276 | The job_queue_view() run mode handles the display of the lists of running, waiting, and completed | ||||||
| 277 | jobs. Note that although all Job Queue lists are dispatched to here, lists of completed jobs are | ||||||
| 278 | actually redirected to _job_queue_view_completed(). | ||||||
| 279 | |||||||
| 280 | =cut | ||||||
| 281 | |||||||
| 282 | sub job_queue_view { | ||||||
| 283 | my $self = shift; | ||||||
| 284 | my $q = $self->query(); | ||||||
| 285 | my $job_status = $q->param('status'); | ||||||
| 286 | my $job_detail = $q->param('job_detail'); | ||||||
| 287 | # "job_queue_view" is actually a basket of views, all sharing the same template | ||||||
| 288 | if ( defined($job_status) && $job_status eq 'done' && $job_detail) { return $self->_job_queue_view_done($q); } | ||||||
| 289 | if ( defined($job_status) && $job_status eq 'done' && !$job_detail) { return $self->_job_queue_count_done($q); } | ||||||
| 290 | if ( !$job_detail ) { return $self->_job_queue_count($q); } | ||||||
| 291 | |||||||
| 292 | my $dbh = $self->dbh(); | ||||||
| 293 | my $now = time(); | ||||||
| 294 | my $funcmap = $self->loadFuncMap(); | ||||||
| 295 | my $output; | ||||||
| 296 | my $sql; | ||||||
| 297 | my @where_clauses; | ||||||
| 298 | |||||||
| 299 | |||||||
| 300 | # defaults | ||||||
| 301 | my $time_horizon = 3600; | ||||||
| 302 | $job_status = 'run'; | ||||||
| 303 | |||||||
| 304 | $sql = < | ||||||
| 305 | SELECT funcid, | ||||||
| 306 | jobid, | ||||||
| 307 | arg, | ||||||
| 308 | uniqkey, | ||||||
| 309 | insert_time, | ||||||
| 310 | run_after, | ||||||
| 311 | grabbed_until, | ||||||
| 312 | priority, | ||||||
| 313 | coalesce | ||||||
| 314 | FROM | ||||||
| 315 | job j | ||||||
| 316 | ACTIVEJOBSQL | ||||||
| 317 | |||||||
| 318 | # form values | ||||||
| 319 | if ( defined($q->param('time')) ) { $time_horizon = $q->param('time'); } | ||||||
| 320 | if ( defined($q->param('status')) ) { $job_status = $q->param('status'); } | ||||||
| 321 | |||||||
| 322 | SWITCH: { | ||||||
| 323 | if ($job_status eq 'run') { | ||||||
| 324 | push(@where_clauses,"grabbed_until != 0"); | ||||||
| 325 | $time_horizon = 'all'; | ||||||
| 326 | last SWITCH; | ||||||
| 327 | } | ||||||
| 328 | if ($job_status eq 'wait') { | ||||||
| 329 | push(@where_clauses,"run_after < $now"); | ||||||
| 330 | push(@where_clauses,"grabbed_until < $now"); | ||||||
| 331 | last SWITCH; | ||||||
| 332 | } | ||||||
| 333 | #[] default | ||||||
| 334 | $time_horizon = 'all'; | ||||||
| 335 | } | ||||||
| 336 | |||||||
| 337 | # time horizon filter | ||||||
| 338 | if ( defined($time_horizon) && ($time_horizon ne '') && ($time_horizon ne 'all') ) { | ||||||
| 339 | push(@where_clauses, "run_after > ".($now - $time_horizon) ); | ||||||
| 340 | } | ||||||
| 341 | |||||||
| 342 | # complete WHERE | ||||||
| 343 | if (scalar(@where_clauses)) { | ||||||
| 344 | $sql .= " WHERE ". join(' AND ',@where_clauses); | ||||||
| 345 | } | ||||||
| 346 | |||||||
| 347 | # ORDER BY | ||||||
| 348 | $sql .= " ORDER BY funcid asc, run_after desc"; | ||||||
| 349 | |||||||
| 350 | #t print $q->header(); | ||||||
| 351 | #t print $sql; | ||||||
| 352 | |||||||
| 353 | my $sth = $dbh->prepare($sql); | ||||||
| 354 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 355 | |||||||
| 356 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 357 | |||||||
| 358 | my @job_types; | ||||||
| 359 | my $job_count = 0; | ||||||
| 360 | my @dbresult; | ||||||
| 361 | my $current_job_class; | ||||||
| 362 | my $first_result = 1; | ||||||
| 363 | my $last_class = undef; | ||||||
| 364 | while ( my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 365 | if ($first_result) { | ||||||
| 366 | $last_class = $result->[0]; | ||||||
| 367 | $first_result = 0; | ||||||
| 368 | } | ||||||
| 369 | if ($result->[0] ne $last_class) { | ||||||
| 370 | push(@job_types, $current_job_class); | ||||||
| 371 | undef $current_job_class; | ||||||
| 372 | $last_class = $result->[0]; | ||||||
| 373 | $job_count = 0; | ||||||
| 374 | } | ||||||
| 375 | |||||||
| 376 | my $date_parts = $self->splitEpochDate($result->[5]); | ||||||
| 377 | my $grabbed_until = $self->splitEpochDate($result->[6]); | ||||||
| 378 | $current_job_class->{JOB_CLASS} = $funcmap->{$result->[0]}; | ||||||
| 379 | $current_job_class->{JOB_COUNT} = ++$job_count; | ||||||
| 380 | push(@{ $current_job_class->{JOBS} }, | ||||||
| 381 | { JOBID => $result->[1], | ||||||
| 382 | # ARG => $result->[2], | ||||||
| 383 | UNIQKEY => $result->[3], | ||||||
| 384 | INSERT_TIME => $result->[4], | ||||||
| 385 | RUN_AFTER => $date_parts->{YYYY}.'-'.$date_parts->{MM}.'-'.$date_parts->{DD}.' '.$date_parts->{HH24}.':'.$date_parts->{MI}.':'.$date_parts->{SS}, | ||||||
| 386 | GRABBED_UNTIL => $grabbed_until->{YYYY}.'-'.$grabbed_until->{MM}.'-'.$grabbed_until->{DD}.' '.$grabbed_until->{HH24}.':'.$grabbed_until->{MI}.':'.$grabbed_until->{SS}, | ||||||
| 387 | PRIORITY => $result->[7], | ||||||
| 388 | COALESCE => $result->[8] | ||||||
| 389 | }); | ||||||
| 390 | } | ||||||
| 391 | push(@job_types, $current_job_class); | ||||||
| 392 | |||||||
| 393 | my $tmpl = $self->load_tmpl(undef, die_on_bad_params => 0); | ||||||
| 394 | $tmpl->param(TITLE => "Helios - Job Queue"); | ||||||
| 395 | $tmpl->param("STATUS_".$job_status, 1); | ||||||
| 396 | $tmpl->param("TIME_".$time_horizon, 1); | ||||||
| 397 | $tmpl->param("JOB_DETAIL_CHECKED" => 1); | ||||||
| 398 | $tmpl->param(JOB_CLASSES => \@job_types); | ||||||
| 399 | return $tmpl->output(); | ||||||
| 400 | } | ||||||
| 401 | |||||||
| 402 | |||||||
| 403 | =head2 _job_queue_view_done() | ||||||
| 404 | |||||||
| 405 | This method is called from job_queue_view() to deal with displaying completed jobs, which pulls | ||||||
| 406 | completed job data from helios_job_history_tb instead of the job table. | ||||||
| 407 | |||||||
| 408 | =cut | ||||||
| 409 | |||||||
| 410 | sub _job_queue_view_done { | ||||||
| 411 | my $self = shift; | ||||||
| 412 | my $q = shift; | ||||||
| 413 | my $dbh = $self->dbh(); | ||||||
| 414 | my $now = time(); | ||||||
| 415 | my $funcmap = $self->loadFuncMap(); | ||||||
| 416 | my $job_status; | ||||||
| 417 | my $output; | ||||||
| 418 | my $sql; | ||||||
| 419 | my @where_clauses; | ||||||
| 420 | |||||||
| 421 | # defaults | ||||||
| 422 | my $time_horizon = 3600; | ||||||
| 423 | |||||||
| 424 | $sql = ' | ||||||
| 425 | select * | ||||||
| 426 | from (select | ||||||
| 427 | if (@jid = jobid, | ||||||
| 428 | if (@time = complete_time, | ||||||
| 429 | @rnk := @rnk + least(0, @inc := @inc + 1), | ||||||
| 430 | @rnk := @rnk + greatest(@inc, @inc := 1) | ||||||
| 431 | + least(0, @time := complete_time) | ||||||
| 432 | ), | ||||||
| 433 | @rnk := 1 + least(0, @jid := jobid) | ||||||
| 434 | + least(0, @time := complete_time) | ||||||
| 435 | + least(0, @inc := 1) | ||||||
| 436 | ) rank, | ||||||
| 437 | jobid, | ||||||
| 438 | funcid, | ||||||
| 439 | run_after, | ||||||
| 440 | grabbed_until, | ||||||
| 441 | exitstatus, | ||||||
| 442 | complete_time | ||||||
| 443 | from helios_job_history_tb, | ||||||
| 444 | (select (@jid := 0)) as x | ||||||
| 445 | where complete_time >= ? | ||||||
| 446 | order by jobid, complete_time desc | ||||||
| 447 | ) as y | ||||||
| 448 | where rank < 2 | ||||||
| 449 | order by funcid asc, complete_time desc | ||||||
| 450 | '; | ||||||
| 451 | |||||||
| 452 | # form values | ||||||
| 453 | if ( defined($q->param('time')) ) { $time_horizon = $q->param('time'); } | ||||||
| 454 | if ( defined($q->param('time')) && ($q->param('time') eq '') ) { $time_horizon = 3600; } | ||||||
| 455 | if ( defined($q->param('status')) ) { $job_status = $q->param('status'); } | ||||||
| 456 | |||||||
| 457 | #t print $q->header(); | ||||||
| 458 | #t print $sql," \n"; |
||||||
| 459 | #t print $now - $time_horizon," \n"; |
||||||
| 460 | |||||||
| 461 | my $sth = $dbh->prepare($sql); | ||||||
| 462 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 463 | |||||||
| 464 | $sth->execute($now - $time_horizon) or throw Error::Simple($dbh->errstr()); | ||||||
| 465 | |||||||
| 466 | my @job_types; | ||||||
| 467 | my $job_count = 0; | ||||||
| 468 | my $job_count_failed = 0; | ||||||
| 469 | my @dbresult; | ||||||
| 470 | my $current_job_class; | ||||||
| 471 | my $first_result = 1; | ||||||
| 472 | my $last_class = undef; | ||||||
| 473 | my $last_jobid = 0; | ||||||
| 474 | while ( my $result = $sth->fetchrow_hashref() ) { | ||||||
| 475 | # print join("|",($result->{rank},$result->{funcid},$result->{jobid},$result->{complete_time},$result->{run_after},$result->{exitstatus},$result->{run_after},$result->{grabbed_until}))," \n"; #p |
||||||
| 476 | if ($first_result) { | ||||||
| 477 | $last_class = $result->{funcid}; | ||||||
| 478 | $first_result = 0; | ||||||
| 479 | } | ||||||
| 480 | if ($result->{funcid} ne $last_class) { | ||||||
| 481 | push(@job_types, $current_job_class); | ||||||
| 482 | undef $current_job_class; | ||||||
| 483 | $last_class = $result->{funcid}; | ||||||
| 484 | $job_count = 0; | ||||||
| 485 | $job_count_failed = 0; | ||||||
| 486 | } | ||||||
| 487 | |||||||
| 488 | my $date_parts = $self->splitEpochDate($result->{run_after}); | ||||||
| 489 | my $complete_time = $self->splitEpochDate($result->{complete_time}); | ||||||
| 490 | $current_job_class->{JOB_CLASS} = $funcmap->{$result->{funcid}}; | ||||||
| 491 | $current_job_class->{JOB_COUNT} = ++$job_count; | ||||||
| 492 | if ( $result->{exitstatus} != 0 ) { $current_job_class->{JOB_COUNT_FAILED} = ++$job_count_failed; } | ||||||
| 493 | # if this jobid is the same as the last, that means it was a failure that was retried | ||||||
| 494 | # dump it, because we've already added the final completion of the job (whether success or fail) | ||||||
| 495 | # because we sorted "jobid, complete_time desc" | ||||||
| 496 | push(@{ $current_job_class->{JOBS} }, | ||||||
| 497 | { JOBID => $result->{jobid}, | ||||||
| 498 | # ARG => $result->[2], | ||||||
| 499 | RUN_AFTER => $date_parts->{YYYY}.'-'.$date_parts->{MM}.'-'.$date_parts->{DD}.' '.$date_parts->{HH24}.':'.$date_parts->{MI}.':'.$date_parts->{SS}, | ||||||
| 500 | GRABBED_UNTIL => $result->{grabbed_until}, | ||||||
| 501 | COMPLETE_TIME => $complete_time->{YYYY}.'-'.$complete_time->{MM}.'-'.$complete_time->{DD}.' '.$complete_time->{HH24}.':'.$complete_time->{MI}.':'.$complete_time->{SS}, | ||||||
| 502 | EXITSTATUS => $result->{exitstatus} | ||||||
| 503 | }); | ||||||
| 504 | $last_jobid = $result->{jobid}; | ||||||
| 505 | } | ||||||
| 506 | push(@job_types, $current_job_class); | ||||||
| 507 | @job_types = sort { $a->{JOB_CLASS} cmp $b->{JOB_CLASS} } @job_types; | ||||||
| 508 | my $tmpl = $self->load_tmpl('job_queue_view.html', die_on_bad_params => 0); | ||||||
| 509 | $tmpl->param(TITLE => 'Helios - Job Queue'); | ||||||
| 510 | $tmpl->param("STATUS_".$job_status, 1); | ||||||
| 511 | $tmpl->param("TIME_".$time_horizon, 1); | ||||||
| 512 | $tmpl->param("JOB_DETAIL_CHECKED" => 1); | ||||||
| 513 | $tmpl->param(JOB_CLASSES => \@job_types); | ||||||
| 514 | return $tmpl->output(); | ||||||
| 515 | } | ||||||
| 516 | |||||||
| 517 | |||||||
| 518 | =head2 job_queue_count() | ||||||
| 519 | |||||||
| 520 | This method will handle a job queue view that displays only counts. | ||||||
| 521 | |||||||
| 522 | =cut | ||||||
| 523 | |||||||
| 524 | sub _job_queue_count { | ||||||
| 525 | my $self = shift; | ||||||
| 526 | my $q = $self->query(); | ||||||
| 527 | my $job_status = $q->param('status'); | ||||||
| 528 | |||||||
| 529 | my $dbh = $self->dbh(); | ||||||
| 530 | my $now = time(); | ||||||
| 531 | my $funcmap = $self->loadFuncMap(); | ||||||
| 532 | my $output; | ||||||
| 533 | my $sql; | ||||||
| 534 | my @where_clauses; | ||||||
| 535 | |||||||
| 536 | # defaults | ||||||
| 537 | my $time_horizon = 3600; | ||||||
| 538 | $job_status = 'run'; | ||||||
| 539 | |||||||
| 540 | # form values | ||||||
| 541 | if ( defined($q->param('time')) ) { $time_horizon = $q->param('time'); } | ||||||
| 542 | if ( defined($q->param('status')) ) { $job_status = $q->param('status'); } | ||||||
| 543 | |||||||
| 544 | $sql = "SELECT funcid, count(*) FROM job "; | ||||||
| 545 | |||||||
| 546 | SWITCH: { | ||||||
| 547 | if ($job_status eq 'run') { | ||||||
| 548 | push(@where_clauses,"grabbed_until != 0"); | ||||||
| 549 | $time_horizon = 'all'; | ||||||
| 550 | last SWITCH; | ||||||
| 551 | } | ||||||
| 552 | if ($job_status eq 'wait') { | ||||||
| 553 | push(@where_clauses,"run_after < $now"); | ||||||
| 554 | push(@where_clauses,"grabbed_until < $now"); | ||||||
| 555 | last SWITCH; | ||||||
| 556 | } | ||||||
| 557 | # default | ||||||
| 558 | $time_horizon = 'all'; | ||||||
| 559 | } | ||||||
| 560 | |||||||
| 561 | # time horizon filter | ||||||
| 562 | if ( defined($time_horizon) && ($time_horizon ne '') && ($time_horizon ne 'all') ) { | ||||||
| 563 | push(@where_clauses, "run_after > ".($now - $time_horizon) ); | ||||||
| 564 | } | ||||||
| 565 | |||||||
| 566 | # complete WHERE | ||||||
| 567 | if (scalar(@where_clauses)) { | ||||||
| 568 | $sql .= " WHERE ". join(' AND ',@where_clauses); | ||||||
| 569 | } | ||||||
| 570 | |||||||
| 571 | # GROUP BY | ||||||
| 572 | $sql .= " GROUP BY funcid"; | ||||||
| 573 | |||||||
| 574 | # ORDER BY | ||||||
| 575 | $sql .= " ORDER BY funcid asc"; | ||||||
| 576 | |||||||
| 577 | my $sth = $dbh->prepare($sql); | ||||||
| 578 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 579 | |||||||
| 580 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 581 | |||||||
| 582 | my @job_types; | ||||||
| 583 | my $job_count = 0; | ||||||
| 584 | my @dbresult; | ||||||
| 585 | my $current_job_class; | ||||||
| 586 | my $first_result = 1; | ||||||
| 587 | my $last_class = undef; | ||||||
| 588 | while ( my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 589 | if ($first_result) { | ||||||
| 590 | $last_class = $result->[0]; | ||||||
| 591 | $first_result = 0; | ||||||
| 592 | } | ||||||
| 593 | if ($result->[0] ne $last_class) { | ||||||
| 594 | push(@job_types, $current_job_class); | ||||||
| 595 | undef $current_job_class; | ||||||
| 596 | $last_class = $result->[0]; | ||||||
| 597 | $job_count = 0; | ||||||
| 598 | } | ||||||
| 599 | |||||||
| 600 | $current_job_class->{JOB_CLASS} = $funcmap->{$result->[0]}; | ||||||
| 601 | $current_job_class->{JOB_COUNT} = $result->[1]; | ||||||
| 602 | } | ||||||
| 603 | push(@job_types, $current_job_class); | ||||||
| 604 | |||||||
| 605 | my $tmpl = $self->load_tmpl('job_queue_view.html', die_on_bad_params => 0); | ||||||
| 606 | $tmpl->param(TITLE => "Helios - Job Queue"); | ||||||
| 607 | $tmpl->param("STATUS_".$job_status, 1); | ||||||
| 608 | $tmpl->param("TIME_".$time_horizon, 1); | ||||||
| 609 | $tmpl->param("JOB_DETAIL_CHECKED" => 0); | ||||||
| 610 | $tmpl->param(JOB_CLASSES => \@job_types); | ||||||
| 611 | return $tmpl->output(); | ||||||
| 612 | } | ||||||
| 613 | |||||||
| 614 | |||||||
| 615 | sub _job_queue_count_done { | ||||||
| 616 | my $self = shift; | ||||||
| 617 | my $q = shift; | ||||||
| 618 | my $dbh = $self->dbh(); | ||||||
| 619 | my $now = time(); | ||||||
| 620 | my $funcmap = $self->loadFuncMap(); | ||||||
| 621 | my $job_status; | ||||||
| 622 | my $output; | ||||||
| 623 | my $sql; | ||||||
| 624 | my @where_clauses; | ||||||
| 625 | |||||||
| 626 | # defaults | ||||||
| 627 | my $time_horizon = 3600; | ||||||
| 628 | |||||||
| 629 | $sql = ' | ||||||
| 630 | select funcid, if(exitstatus,1,0) as exitstatus, count(*) as count | ||||||
| 631 | from (select | ||||||
| 632 | if (@jid = jobid, | ||||||
| 633 | if (@time = complete_time, | ||||||
| 634 | @rnk := @rnk + least(0, @inc := @inc + 1), | ||||||
| 635 | @rnk := @rnk + greatest(@inc, @inc := 1) | ||||||
| 636 | + least(0, @time := complete_time) | ||||||
| 637 | ), | ||||||
| 638 | @rnk := 1 + least(0, @jid := jobid) | ||||||
| 639 | + least(0, @time := complete_time) | ||||||
| 640 | + least(0, @inc := 1) | ||||||
| 641 | ) rank, | ||||||
| 642 | jobid, | ||||||
| 643 | funcid, | ||||||
| 644 | run_after, | ||||||
| 645 | exitstatus, | ||||||
| 646 | complete_time | ||||||
| 647 | from helios_job_history_tb, | ||||||
| 648 | (select (@jid := 0)) as x | ||||||
| 649 | where complete_time >= ? | ||||||
| 650 | order by jobid, complete_time desc | ||||||
| 651 | ) as y | ||||||
| 652 | where rank < 2 | ||||||
| 653 | GROUP BY funcid, if(exitstatus,1,0) | ||||||
| 654 | '; | ||||||
| 655 | |||||||
| 656 | # form values | ||||||
| 657 | if ( defined($q->param('time')) ) { $time_horizon = $q->param('time'); } | ||||||
| 658 | if ( defined($q->param('time')) && ($q->param('time') eq '') ) { $time_horizon = 3600; } | ||||||
| 659 | if ( defined($q->param('status')) ) { $job_status = $q->param('status'); } | ||||||
| 660 | |||||||
| 661 | #t print $q->header(); | ||||||
| 662 | #t print $sql," \n"; |
||||||
| 663 | #t print $now - $time_horizon," \n"; |
||||||
| 664 | |||||||
| 665 | my $sth = $dbh->prepare($sql); | ||||||
| 666 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 667 | |||||||
| 668 | $sth->execute($now - $time_horizon) or throw Error::Simple($dbh->errstr()); | ||||||
| 669 | |||||||
| 670 | my @job_types; | ||||||
| 671 | my $current_funcid; | ||||||
| 672 | my $current_success_jobs = 0; | ||||||
| 673 | my $current_failed_jobs = 0; | ||||||
| 674 | my $last_funcid; | ||||||
| 675 | my $first_result = 1; | ||||||
| 676 | |||||||
| 677 | while ( my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 678 | # print join("|",@$result)," \n"; #p |
||||||
| 679 | if ($first_result) { | ||||||
| 680 | $last_funcid = $result->[0]; | ||||||
| 681 | $current_funcid = $result->[0]; | ||||||
| 682 | $first_result = 0; | ||||||
| 683 | } | ||||||
| 684 | |||||||
| 685 | if ($current_funcid ne $result->[0] ) { | ||||||
| 686 | # flush | ||||||
| 687 | push(@job_types, { JOB_CLASS => $funcmap->{$current_funcid}, | ||||||
| 688 | JOB_COUNT => $current_success_jobs + $current_failed_jobs, | ||||||
| 689 | JOB_COUNT_FAILED => $current_failed_jobs | ||||||
| 690 | } | ||||||
| 691 | ); | ||||||
| 692 | $last_funcid = $result->[0]; | ||||||
| 693 | $current_success_jobs = 0; | ||||||
| 694 | $current_failed_jobs = 0; | ||||||
| 695 | } | ||||||
| 696 | $current_funcid = $result->[0]; | ||||||
| 697 | if ($result->[1] == 0) { | ||||||
| 698 | $current_success_jobs = $result->[2]; | ||||||
| 699 | } else { | ||||||
| 700 | $current_failed_jobs = $result->[2]; | ||||||
| 701 | } | ||||||
| 702 | } | ||||||
| 703 | push(@job_types, { JOB_CLASS => $funcmap->{$current_funcid}, | ||||||
| 704 | JOB_COUNT => $current_success_jobs + $current_failed_jobs, | ||||||
| 705 | JOB_COUNT_FAILED => $current_failed_jobs | ||||||
| 706 | } | ||||||
| 707 | ); | ||||||
| 708 | @job_types = sort { $a->{JOB_CLASS} cmp $b->{JOB_CLASS} } @job_types; | ||||||
| 709 | |||||||
| 710 | my $tmpl = $self->load_tmpl('job_queue_view.html', die_on_bad_params => 0); | ||||||
| 711 | $tmpl->param(TITLE => 'Helios - Job Queue'); | ||||||
| 712 | $tmpl->param("STATUS_".$job_status, 1); | ||||||
| 713 | $tmpl->param("TIME_".$time_horizon, 1); | ||||||
| 714 | $tmpl->param("JOB_DETAIL_CHECKED" => 0); | ||||||
| 715 | $tmpl->param(JOB_CLASSES => \@job_types); | ||||||
| 716 | return $tmpl->output(); | ||||||
| 717 | } | ||||||
| 718 | |||||||
| 719 | |||||||
| 720 | |||||||
| 721 | =head2 job_submit() | ||||||
| 722 | |||||||
| 723 | The job_submit() run mode allows for manual submission of a job to the Helios collective via | ||||||
| 724 | Panoptes. This run mode is useful mainly for debugging purposes. | ||||||
| 725 | |||||||
| 726 | This run mode uses the Helios web job submission interface (submitJob.pl) and requires the | ||||||
| 727 | job_submit_url option being set in the [global] section of helios.ini so it can submit the job to | ||||||
| 728 | the appropriate URL. eg: | ||||||
| 729 | |||||||
| 730 | [global] | ||||||
| 731 | job_submit_url=http://localhost/cgi-bin/submitJob.pl | ||||||
| 732 | |||||||
| 733 | This run mode is really just displaying a form with some of the details filled in. Once you | ||||||
| 734 | submit the form, the response you receive is actually the response returned from submitJob.pl. If | ||||||
| 735 | job submission was successful, this is normally a file of type text/xml with a |
||||||
| 736 | (normally containing 0) and a |
||||||
| 737 | there was an error during submission, the response will be an HTTP error, and submitJob.pl will | ||||||
| 738 | log a message in the Helios log. | ||||||
| 739 | |||||||
| 740 | =cut | ||||||
| 741 | |||||||
| 742 | sub job_submit { | ||||||
| 743 | my $self = shift; | ||||||
| 744 | my $q = $self->query(); | ||||||
| 745 | |||||||
| 746 | my $classmap = $self->loadClassMap(); | ||||||
| 747 | |||||||
| 748 | my @classes; | ||||||
| 749 | foreach (sort keys %$classmap) { | ||||||
| 750 | push(@classes, { job_type => $_, job_class => $classmap->{$_} }); | ||||||
| 751 | } | ||||||
| 752 | |||||||
| 753 | |||||||
| 754 | my $tmpl = $self->load_tmpl('job_submit.html', die_on_bad_params => 0); | ||||||
| 755 | $tmpl->param(TITLE => 'Helios - Job Submit'); | ||||||
| 756 | $tmpl->param(CLASSES => \@classes); | ||||||
| 757 | $tmpl->param(JOB_SUBMIT_URL => $CONF_PARAMS->{job_submit_url}); | ||||||
| 758 | return $tmpl->output(); | ||||||
| 759 | } | ||||||
| 760 | |||||||
| 761 | |||||||
| 762 | =head2 collective() | ||||||
| 763 | |||||||
| 764 | The collective() run mode provides the Collective display, a list of what Helios service daemons | ||||||
| 765 | are running in the collective, with some limited convenience controls for admins that don't want | ||||||
| 766 | to deal with the Ctrl Panel. | ||||||
| 767 | |||||||
| 768 | The collective() method actually reads the groupby CGI parameter and dispatches to | ||||||
| 769 | _collective_host() or _collective_service(), depending on which the user specified (the default | ||||||
| 770 | is service). | ||||||
| 771 | |||||||
| 772 | =cut | ||||||
| 773 | |||||||
| 774 | sub collective { | ||||||
| 775 | my $self = shift; | ||||||
| 776 | my $q = $self->query(); | ||||||
| 777 | if ($q->param('groupby') eq 'service') { | ||||||
| 778 | return $self->_collective_service(); | ||||||
| 779 | } else { | ||||||
| 780 | return $self->_collective_host(); | ||||||
| 781 | } | ||||||
| 782 | } | ||||||
| 783 | |||||||
| 784 | |||||||
| 785 | =head2 _collective_host() | ||||||
| 786 | |||||||
| 787 | The _collective_host() method provides the Collective display grouped by host. | ||||||
| 788 | |||||||
| 789 | =cut | ||||||
| 790 | |||||||
| 791 | sub _collective_host { | ||||||
| 792 | my $self = shift; | ||||||
| 793 | my $dbh = $self->dbh(); | ||||||
| 794 | my $q = $self->query(); | ||||||
| 795 | my $config = $self->loadParams('host'); | ||||||
| 796 | |||||||
| 797 | my $register_threshold = time() - 360; | ||||||
| 798 | |||||||
| 799 | my $sql = < | ||||||
| 800 | SELECT host, | ||||||
| 801 | worker_class, | ||||||
| 802 | worker_version, | ||||||
| 803 | process_id, | ||||||
| 804 | register_time, | ||||||
| 805 | start_time | ||||||
| 806 | FROM helios_worker_registry_tb | ||||||
| 807 | WHERE register_time > ? | ||||||
| 808 | ORDER BY host, worker_class | ||||||
| 809 | STATUSSQL | ||||||
| 810 | |||||||
| 811 | my $sth = $dbh->prepare($sql); | ||||||
| 812 | unless ($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 813 | |||||||
| 814 | $sth->execute($register_threshold) or throw Error::Simple($dbh->errstr()); | ||||||
| 815 | |||||||
| 816 | my @collective; | ||||||
| 817 | my @dbresult; | ||||||
| 818 | my $current_host; | ||||||
| 819 | my $first_result = 1; | ||||||
| 820 | my $last_host = undef; | ||||||
| 821 | while ( my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 822 | if ($first_result) { | ||||||
| 823 | $last_host = $result->[0]; | ||||||
| 824 | $first_result = 0; | ||||||
| 825 | } | ||||||
| 826 | if ($result->[0] ne $last_host) { | ||||||
| 827 | push(@collective, $current_host); | ||||||
| 828 | undef $current_host; | ||||||
| 829 | $last_host = $result->[0]; | ||||||
| 830 | } | ||||||
| 831 | |||||||
| 832 | my $date_parts = $self->splitEpochDate($result->[4]); | ||||||
| 833 | $current_host->{HOST} = $result->[0]; | ||||||
| 834 | |||||||
| 835 | # calc uptime | ||||||
| 836 | my $uptime_string = ''; | ||||||
| 837 | { | ||||||
| 838 | use integer; | ||||||
| 839 | my $uptime = time() - $result->[5]; | ||||||
| 840 | my $uptime_days = $uptime/86400; | ||||||
| 841 | my $uptime_hours = ($uptime % 86400)/3600; | ||||||
| 842 | my $uptime_mins = (($uptime % 86400) % 3600)/60; | ||||||
| 843 | if ($uptime_days != 0) { $uptime_string .= $uptime_days.'d '; } | ||||||
| 844 | if ($uptime_hours != 0) { $uptime_string .= $uptime_hours.'h '; } | ||||||
| 845 | if ($uptime_mins != 0) { $uptime_string .= $uptime_mins.'m '; } | ||||||
| 846 | } | ||||||
| 847 | |||||||
| 848 | # max_workers | ||||||
| 849 | my $max_workers = 1; | ||||||
| 850 | if ( defined($config->{ $result->[0] }->{ $result->[1] }->{MAX_WORKERS}) ) { | ||||||
| 851 | $max_workers = $config->{ $result->[0] }->{ $result->[1] }->{MAX_WORKERS}; | ||||||
| 852 | } | ||||||
| 853 | |||||||
| 854 | # figure out status (normal/overdrive/holding/halting) | ||||||
| 855 | my $status; | ||||||
| 856 | my $halt_status = 0; | ||||||
| 857 | my $hold_status = 0; | ||||||
| 858 | my $overdrive_status = 0; | ||||||
| 859 | if ( (defined( $config->{$result->[0] }->{ $result->[1] }->{OVERDRIVE}) && ($config->{$result->[0] }->{ $result->[1] }->{OVERDRIVE} == 1) ) || | ||||||
| 860 | (defined( $config->{'*'}->{ $result->[1] }->{OVERDRIVE}) && ($config->{'*'}->{ $result->[1] }->{OVERDRIVE} == 1) ) ) { | ||||||
| 861 | $overdrive_status = 1; | ||||||
| 862 | $status = "Overdrive"; | ||||||
| 863 | } | ||||||
| 864 | if ( (defined( $config->{$result->[0] }->{ $result->[1] }->{HOLD}) && ($config->{$result->[0] }->{ $result->[1] }->{HOLD} == 1) ) || | ||||||
| 865 | (defined( $config->{'*'}->{ $result->[1] }->{HOLD}) && ($config->{'*'}->{ $result->[1] }->{HOLD} == 1) ) ) { | ||||||
| 866 | $hold_status = 1; | ||||||
| 867 | $status = "HOLDING"; | ||||||
| 868 | } | ||||||
| 869 | if ( defined( $config->{$result->[0] }->{ $result->[1] }->{HALT}) || | ||||||
| 870 | defined( $config->{'*'}->{ $result->[1] }->{HALT}) ) { | ||||||
| 871 | $halt_status = 1; | ||||||
| 872 | $status = "HALTING"; | ||||||
| 873 | } | ||||||
| 874 | push(@{ $current_host->{SERVICES} }, | ||||||
| 875 | { HOST => $result->[0], | ||||||
| 876 | SERVICE_CLASS => $result->[1], | ||||||
| 877 | SERVICE_VERSION => $result->[2], | ||||||
| 878 | PROCESS_ID => $result->[3], | ||||||
| 879 | REGISTER_TIME => $date_parts->{YYYY}.'-'.$date_parts->{MM}.'-'.$date_parts->{DD}.' '.$date_parts->{HH24}.':'.$date_parts->{MI}.':'.$date_parts->{SS}, | ||||||
| 880 | UPTIME => $uptime_string, | ||||||
| 881 | STATUS => $status, | ||||||
| 882 | MAX_WORKERS => $max_workers, | ||||||
| 883 | OVERDRIVE => $overdrive_status, | ||||||
| 884 | HOLDING => $hold_status, | ||||||
| 885 | HALTING => $halt_status, | ||||||
| 886 | }); | ||||||
| 887 | } | ||||||
| 888 | push(@collective, $current_host); | ||||||
| 889 | |||||||
| 890 | my $tmpl = $self->load_tmpl('collective_host.html', die_on_bad_params => 0); | ||||||
| 891 | $tmpl->param(TITLE => 'Helios - Collective View'); | ||||||
| 892 | $tmpl->param(COLLECTIVE => \@collective); | ||||||
| 893 | return $tmpl->output(); | ||||||
| 894 | } | ||||||
| 895 | |||||||
| 896 | |||||||
| 897 | =head2 _collective_service() | ||||||
| 898 | |||||||
| 899 | The _collective_service() method provides the Collective display grouped by service. | ||||||
| 900 | |||||||
| 901 | =cut | ||||||
| 902 | |||||||
| 903 | sub _collective_service { | ||||||
| 904 | my $self = shift; | ||||||
| 905 | my $dbh = $self->dbh(); | ||||||
| 906 | my $q = $self->query(); | ||||||
| 907 | my $config = $self->loadParams('worker_class'); | ||||||
| 908 | |||||||
| 909 | my $register_threshold = time() - 360; | ||||||
| 910 | |||||||
| 911 | my $sql = < | ||||||
| 912 | SELECT worker_class AS service, | ||||||
| 913 | host, | ||||||
| 914 | worker_version AS version, | ||||||
| 915 | process_id, | ||||||
| 916 | register_time, | ||||||
| 917 | start_time | ||||||
| 918 | FROM helios_worker_registry_tb | ||||||
| 919 | WHERE register_time > ? | ||||||
| 920 | ORDER BY service, host | ||||||
| 921 | STATUSSQL | ||||||
| 922 | |||||||
| 923 | my $sth = $dbh->prepare($sql); | ||||||
| 924 | unless ($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 925 | |||||||
| 926 | $sth->execute($register_threshold) or throw Error::Simple($dbh->errstr()); | ||||||
| 927 | |||||||
| 928 | my @collective; | ||||||
| 929 | my @dbresult; | ||||||
| 930 | my $current_service; | ||||||
| 931 | my $first_result = 1; | ||||||
| 932 | my $last_service = undef; | ||||||
| 933 | #t print $q->header(); | ||||||
| 934 | while ( my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 935 | #t print join("|", @$result)," \n"; |
||||||
| 936 | if ($first_result) { | ||||||
| 937 | $last_service = $result->[0]; | ||||||
| 938 | $first_result = 0; | ||||||
| 939 | } | ||||||
| 940 | if ($result->[0] ne $last_service) { | ||||||
| 941 | push(@collective, $current_service); | ||||||
| 942 | undef $current_service; | ||||||
| 943 | $last_service = $result->[0]; | ||||||
| 944 | } | ||||||
| 945 | |||||||
| 946 | my $date_parts = $self->splitEpochDate($result->[4]); | ||||||
| 947 | $current_service->{SERVICE_CLASS} = $result->[0]; | ||||||
| 948 | |||||||
| 949 | # calc uptime | ||||||
| 950 | my $uptime_string = ''; | ||||||
| 951 | { | ||||||
| 952 | use integer; | ||||||
| 953 | my $uptime = time() - $result->[5]; | ||||||
| 954 | my $uptime_days = $uptime/86400; | ||||||
| 955 | my $uptime_hours = ($uptime % 86400)/3600; | ||||||
| 956 | my $uptime_mins = (($uptime % 86400) % 3600)/60; | ||||||
| 957 | if ($uptime_days != 0) { $uptime_string .= $uptime_days.'d '; } | ||||||
| 958 | if ($uptime_hours != 0) { $uptime_string .= $uptime_hours.'h '; } | ||||||
| 959 | if ($uptime_mins != 0) { $uptime_string .= $uptime_mins.'m '; } | ||||||
| 960 | } | ||||||
| 961 | |||||||
| 962 | # max_workers | ||||||
| 963 | my $max_workers = 1; | ||||||
| 964 | if ( defined($config->{ $result->[0] }->{ '*' }->{MAX_WORKERS}) ) { | ||||||
| 965 | $max_workers = $config->{ $result->[0] }->{ '*' }->{MAX_WORKERS}; | ||||||
| 966 | } | ||||||
| 967 | if ( defined($config->{ $result->[0] }->{ $result->[1] }->{MAX_WORKERS}) ) { | ||||||
| 968 | $max_workers = $config->{ $result->[0] }->{ $result->[1] }->{MAX_WORKERS}; | ||||||
| 969 | } | ||||||
| 970 | |||||||
| 971 | # figure out status (normal/overdrive/holding/halting) | ||||||
| 972 | my $status; | ||||||
| 973 | my $halt_status = 0; | ||||||
| 974 | my $hold_status = 0; | ||||||
| 975 | my $overdrive_status = 0; | ||||||
| 976 | # determine overdrive status | ||||||
| 977 | if ( defined($config->{$result->[0]}->{'*'}->{OVERDRIVE}) && ($config->{$result->[0]}->{'*'}->{OVERDRIVE} == 1) ) { | ||||||
| 978 | $overdrive_status = 1; | ||||||
| 979 | } | ||||||
| 980 | if ( defined($config->{$result->[0]}->{$result->[1]}->{OVERDRIVE}) ) { | ||||||
| 981 | $overdrive_status = $config->{$result->[0]}->{$result->[1]}->{OVERDRIVE}; | ||||||
| 982 | } | ||||||
| 983 | |||||||
| 984 | # determine holding status | ||||||
| 985 | if ( defined($config->{$result->[0]}->{'*'}->{HOLD}) && ($config->{$result->[0]}->{'*'}->{HOLD} == 1) ) { | ||||||
| 986 | $hold_status = 1; | ||||||
| 987 | } | ||||||
| 988 | if ( defined($config->{$result->[0]}->{$result->[1]}->{HOLD}) ) { | ||||||
| 989 | $hold_status = $config->{$result->[0]}->{$result->[1]}->{HOLD}; | ||||||
| 990 | } | ||||||
| 991 | |||||||
| 992 | |||||||
| 993 | # determine halt status; if it's even defined, that means the service instance is HALTing | ||||||
| 994 | if ( defined( $config->{$result->[0] }->{ '*' }->{HALT}) || | ||||||
| 995 | defined( $config->{$result->[0]}->{ $result->[1] }->{HALT}) ) { | ||||||
| 996 | $halt_status = 1; | ||||||
| 997 | $status = "HALTING"; | ||||||
| 998 | } | ||||||
| 999 | push(@{ $current_service->{HOSTS} }, | ||||||
| 1000 | { HOST => $result->[1], | ||||||
| 1001 | SERVICE_CLASS => $result->[0], | ||||||
| 1002 | SERVICE_VERSION => $result->[2], | ||||||
| 1003 | PROCESS_ID => $result->[3], | ||||||
| 1004 | REGISTER_TIME => $date_parts->{YYYY}.'-'.$date_parts->{MM}.'-'.$date_parts->{DD}.' '.$date_parts->{HH24}.':'.$date_parts->{MI}.':'.$date_parts->{SS}, | ||||||
| 1005 | UPTIME => $uptime_string, | ||||||
| 1006 | STATUS => $status, | ||||||
| 1007 | MAX_WORKERS => $max_workers, | ||||||
| 1008 | OVERDRIVE => $overdrive_status, | ||||||
| 1009 | HOLDING => $hold_status, | ||||||
| 1010 | HALTING => $halt_status, | ||||||
| 1011 | }); | ||||||
| 1012 | } | ||||||
| 1013 | push(@collective, $current_service); | ||||||
| 1014 | |||||||
| 1015 | my $tmpl = $self->load_tmpl('collective_service.html', die_on_bad_params => 0); | ||||||
| 1016 | $tmpl->param(TITLE => 'Helios - Collective View'); | ||||||
| 1017 | $tmpl->param(COLLECTIVE => \@collective); | ||||||
| 1018 | return $tmpl->output(); | ||||||
| 1019 | } | ||||||
| 1020 | |||||||
| 1021 | |||||||
| 1022 | |||||||
| 1023 | |||||||
| 1024 | =head1 OTHER METHODS | ||||||
| 1025 | |||||||
| 1026 | These are auxiliary/utility methods used by the run mode methods. | ||||||
| 1027 | |||||||
| 1028 | =head2 splitEpochDate($epoch_seconds) | ||||||
| 1029 | |||||||
| 1030 | Given a datetime in epoch seconds, this method returns a hashref containing the component date | ||||||
| 1031 | parts. The keys of the hash follow Oracle naming conventions because that is what the author was | ||||||
| 1032 | most familiar with: | ||||||
| 1033 | |||||||
| 1034 | YYYY four-digit year | ||||||
| 1035 | MM two-digit month | ||||||
| 1036 | DD two-digit day | ||||||
| 1037 | HH twenty-four hour | ||||||
| 1038 | HH12 twelve hour | ||||||
| 1039 | HH24 twenty-four hour | ||||||
| 1040 | MI two-digit minutes | ||||||
| 1041 | SS two-digit seconds | ||||||
| 1042 | AMPM ante/post meridium | ||||||
| 1043 | |||||||
| 1044 | =cut | ||||||
| 1045 | |||||||
| 1046 | sub splitEpochDate { | ||||||
| 1047 | my $self = shift; | ||||||
| 1048 | my $epoch_secs = shift; | ||||||
| 1049 | |||||||
| 1050 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($epoch_secs); | ||||||
| 1051 | |||||||
| 1052 | my $return_date; | ||||||
| 1053 | |||||||
| 1054 | $return_date->{YYYY} = sprintf("%04d", $year + 1900); | ||||||
| 1055 | $return_date->{MM} = sprintf("%02d", $mon+1); | ||||||
| 1056 | $return_date->{DD} = sprintf("%02d", $mday); | ||||||
| 1057 | $return_date->{MI} = sprintf("%02d", $min); | ||||||
| 1058 | $return_date->{SS} = sprintf("%02d", $sec); | ||||||
| 1059 | |||||||
| 1060 | # hours | ||||||
| 1061 | if ($hour == 12) { | ||||||
| 1062 | $return_date->{AMPM} = 'PM'; | ||||||
| 1063 | $return_date->{HH12} = '12'; | ||||||
| 1064 | $return_date->{HH24} = '12'; | ||||||
| 1065 | $return_date->{HH} = '12'; | ||||||
| 1066 | } elsif ($hour == 0) { | ||||||
| 1067 | $return_date->{AMPM} = 'AM'; | ||||||
| 1068 | $return_date->{HH12} = '12'; | ||||||
| 1069 | $return_date->{HH24} = '00'; | ||||||
| 1070 | $return_date->{HH} = '12'; | ||||||
| 1071 | } elsif ($hour > 12) { | ||||||
| 1072 | $return_date->{AMPM} = 'PM'; | ||||||
| 1073 | $return_date->{HH12} = sprintf("%02d", $hour - 12); | ||||||
| 1074 | $return_date->{HH24} = sprintf("%02d", $hour); | ||||||
| 1075 | $return_date->{HH} = sprintf("%02d", $hour); | ||||||
| 1076 | } else { | ||||||
| 1077 | # hour is AM | ||||||
| 1078 | $return_date->{AMPM} = 'AM'; | ||||||
| 1079 | $return_date->{HH12} = sprintf("%02d", $hour); | ||||||
| 1080 | $return_date->{HH24} = sprintf("%02d", $hour); | ||||||
| 1081 | $return_date->{HH} = sprintf("%02d", $hour); | ||||||
| 1082 | } | ||||||
| 1083 | |||||||
| 1084 | return $return_date; | ||||||
| 1085 | } | ||||||
| 1086 | |||||||
| 1087 | |||||||
| 1088 | =head2 loadClassMap([$keyfield]) | ||||||
| 1089 | |||||||
| 1090 | Load the contents of the helios_class_map table into memory, returning it as a hashref. If | ||||||
| 1091 | $keyfield is specified (job_type, job_class), it will be the key field in the hash. If | ||||||
| 1092 | $keyfield is not specified, job_type is the default. | ||||||
| 1093 | |||||||
| 1094 | =cut | ||||||
| 1095 | |||||||
| 1096 | sub loadClassMap { | ||||||
| 1097 | my $self = shift; | ||||||
| 1098 | my $keyfield = @_ ? shift : 'job_type'; | ||||||
| 1099 | my $dbh = $self->dbh(); | ||||||
| 1100 | |||||||
| 1101 | my $classmap; | ||||||
| 1102 | my $sql = "SELECT job_type, job_class FROM helios_class_map "; | ||||||
| 1103 | |||||||
| 1104 | my $sth = $dbh->prepare($sql); | ||||||
| 1105 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 1106 | |||||||
| 1107 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 1108 | |||||||
| 1109 | while (my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 1110 | if ($keyfield eq 'job_class') { | ||||||
| 1111 | $classmap->{$result->[1]} = $result->[0]; | ||||||
| 1112 | } else { | ||||||
| 1113 | $classmap->{$result->[0]} = $result->[1]; | ||||||
| 1114 | } | ||||||
| 1115 | } | ||||||
| 1116 | return $classmap; | ||||||
| 1117 | } | ||||||
| 1118 | |||||||
| 1119 | |||||||
| 1120 | =head2 loadFuncMap | ||||||
| 1121 | |||||||
| 1122 | Load the contents of the funcmap table into memory, because it's small and lots of methods | ||||||
| 1123 | need it. THe funcmap table associates a funcid with a service class name for internal purposes. | ||||||
| 1124 | |||||||
| 1125 | =cut | ||||||
| 1126 | |||||||
| 1127 | sub loadFuncMap { | ||||||
| 1128 | my $self = shift; | ||||||
| 1129 | my $dbh = $self->dbh(); | ||||||
| 1130 | |||||||
| 1131 | my $funcmap; | ||||||
| 1132 | |||||||
| 1133 | my $sql = "SELECT funcid, funcname FROM funcmap"; | ||||||
| 1134 | my $sth = $dbh->prepare($sql); | ||||||
| 1135 | unless($sth) { throw Error::Simple($dbh->errstr); } | ||||||
| 1136 | |||||||
| 1137 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 1138 | |||||||
| 1139 | while (my $result = $sth->fetchrow_arrayref() ) { | ||||||
| 1140 | $funcmap->{$result->[0]} = $result->[1]; | ||||||
| 1141 | $funcmap->{$result->[1]} = $result->[0]; | ||||||
| 1142 | } | ||||||
| 1143 | return $funcmap; | ||||||
| 1144 | } | ||||||
| 1145 | |||||||
| 1146 | |||||||
| 1147 | =head2 loadParams([$keyfield]) | ||||||
| 1148 | |||||||
| 1149 | Returns a hashref data structure containing all of the Helios config params. The keyfield can be | ||||||
| 1150 | either 'worker_class' or 'host' ('worker_class' is the default). | ||||||
| 1151 | |||||||
| 1152 | =cut | ||||||
| 1153 | |||||||
| 1154 | sub loadParams { | ||||||
| 1155 | my $self = shift; | ||||||
| 1156 | my $keyfield = @_ ? shift : 'worker_class'; | ||||||
| 1157 | my $dbh = $self->dbh(); | ||||||
| 1158 | $dbh->{FetchHashKeyName} = 'NAME_lc'; | ||||||
| 1159 | my $keyfield2; | ||||||
| 1160 | my $config; | ||||||
| 1161 | |||||||
| 1162 | if ($keyfield eq 'worker_class') { | ||||||
| 1163 | $keyfield2 = 'host'; | ||||||
| 1164 | } else { | ||||||
| 1165 | $keyfield2 = 'worker_class'; | ||||||
| 1166 | } | ||||||
| 1167 | |||||||
| 1168 | my $sql = "SELECT $keyfield, $keyfield2, param, value FROM helios_params_tb ORDER BY $keyfield, $keyfield2"; | ||||||
| 1169 | my $sth = $dbh->prepare($sql); | ||||||
| 1170 | unless ($sth) { throw Error::Simple($dbh->errstr()); } | ||||||
| 1171 | $sth->execute() or throw Error::Simple($dbh->errstr()); | ||||||
| 1172 | |||||||
| 1173 | while(my $result = $sth->fetchrow_arrayref()) { | ||||||
| 1174 | $config->{ $result->[0] }->{ $result->[1] }->{ $result->[2] } = $result->[3]; | ||||||
| 1175 | } | ||||||
| 1176 | |||||||
| 1177 | return $config; | ||||||
| 1178 | } | ||||||
| 1179 | |||||||
| 1180 | |||||||
| 1181 | =head2 modParam($action, $worker_class, $host, $param, [$value]) | ||||||
| 1182 | |||||||
| 1183 | Modify Helios config parameters. Used by ctrl_panel() and collective() displays. | ||||||
| 1184 | |||||||
| 1185 | Valid values for $action: | ||||||
| 1186 | |||||||
| 1187 | =over 4 | ||||||
| 1188 | |||||||
| 1189 | =item add | ||||||
| 1190 | |||||||
| 1191 | Add the given parameter for the given class and host | ||||||
| 1192 | |||||||
| 1193 | =item delete | ||||||
| 1194 | |||||||
| 1195 | Delete the given parameter for the given class and host | ||||||
| 1196 | |||||||
| 1197 | =item modify | ||||||
| 1198 | |||||||
| 1199 | Modify the given parameter for the given class and host with a new value. Effectively the same | ||||||
| 1200 | as a delete followed by an add. | ||||||
| 1201 | |||||||
| 1202 | =back | ||||||
| 1203 | |||||||
| 1204 | Worker class is the name of the class. | ||||||
| 1205 | |||||||
| 1206 | Host is the name of the host. Use '*' to make the parameter global to all instances of the worker | ||||||
| 1207 | class. | ||||||
| 1208 | |||||||
| 1209 | Returns a true value if successful and throws an Error::Simple exception otherwise. | ||||||
| 1210 | |||||||
| 1211 | =cut | ||||||
| 1212 | |||||||
| 1213 | sub modParam { | ||||||
| 1214 | my $self = shift; | ||||||
| 1215 | my $dbh = $self->dbh(); | ||||||
| 1216 | my $action = shift; | ||||||
| 1217 | my $worker_class = shift; | ||||||
| 1218 | my $host = shift; | ||||||
| 1219 | my $param = shift; | ||||||
| 1220 | my $value = shift; | ||||||
| 1221 | |||||||
| 1222 | my $sql; | ||||||
| 1223 | |||||||
| 1224 | unless ($worker_class && $host && $param && $action) { | ||||||
| 1225 | throw Error::Simple("Worker class ($worker_class), host ($host), param ($param), and action ($action) required"); | ||||||
| 1226 | } | ||||||
| 1227 | |||||||
| 1228 | SWITCH: { | ||||||
| 1229 | if ($action eq 'add') { | ||||||
| 1230 | $sql = 'INSERT INTO helios_params_tb (host, worker_class, param, value) VALUES (?,?,?,?)'; | ||||||
| 1231 | $dbh->do($sql, undef, $host, $worker_class, $param, $value) or throw Error::Simple('modParam add FAILURE: '.$dbh->errstr); | ||||||
| 1232 | last SWITCH; | ||||||
| 1233 | } | ||||||
| 1234 | if ($action eq 'delete') { | ||||||
| 1235 | $sql = 'DELETE FROM helios_params_tb WHERE host = ? AND worker_class = ? AND param = ?'; | ||||||
| 1236 | $dbh->do($sql, undef, $host, $worker_class, $param) or throw Error::Simple('modParam delete FAILURE: '.$dbh->errstr); | ||||||
| 1237 | last SWITCH; | ||||||
| 1238 | } | ||||||
| 1239 | if ($action eq 'modify') { | ||||||
| 1240 | $self->modParam('delete', $worker_class, $host, $param); | ||||||
| 1241 | $self->modParam('add', $worker_class, $host, $param, $value); | ||||||
| 1242 | last SWITCH; | ||||||
| 1243 | } | ||||||
| 1244 | throw Error::Simple("modParam invalid action: $action"); | ||||||
| 1245 | } | ||||||
| 1246 | |||||||
| 1247 | return 1; | ||||||
| 1248 | } | ||||||
| 1249 | |||||||
| 1250 | |||||||
| 1251 | |||||||
| 1252 | |||||||
| 1253 | 1; | ||||||
| 1254 | __END__ |