#!/usr/bin/perl -Tw ############################################################################### # # chart_stats.cgi - display job usage statistics and cluster utilization # # chart_stats creates bar charts of batch job usage and cluster utilization # It initially presents a page that allows the user to specify the chart # s/he wants to see. It then invokes sreport to retrieve the data from the # Slurm database and creates a page containing a chart of this data. # ############################################################################# # Copyright (C) 2010 Lawrence Livermore National Security. # Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). # Written by Don Lipari . # CODE-OCEC-09-009. All rights reserved. # # This file is part of Slurm, a resource management program. # For details, see . # Please also read the included file: DISCLAIMER. # # Slurm is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # In addition, as a special exception, the copyright holders give permission # to link the code of portions of this program with the OpenSSL library under # certain conditions as described in each individual source file, and # distribute linked combinations including the two. You must obey the GNU # General Public License in all respects for all of the code used other than # OpenSSL. If you modify file(s) with this exception, you may extend this # exception to your version of the file(s), but you are not obligated to do # so. If you do not wish to do so, delete this exception statement from your # version. If you delete this exception statement from all source files in # the program, then also delete it here. # # Slurm is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along # with Slurm; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################# use strict; use warnings; use CGI qw(:standard); # uncomment to debug... # use CGI::Carp qw(fatalsToBrowser); use GD; use Chart::StackedBars; use Expect; use Time::Local; # # Sanitize the environment # delete @ENV{qw( BASH_ENV CDPATH ENV IFS PATH )}; my $obj = Chart::StackedBars->new(600, 400) or die "Failed to create bar chart: $!\n"; my ($time_begin, $time_end); my ($yb, $mb, $db, $ye, $me, $de); my ($user, $account, $x_axis, $y_axis, $cluster); my $slurm_bindir = "/usr/bin"; my $sacctmgr_bin = "$slurm_bindir/sacctmgr"; my $sreport_bin = "$slurm_bindir/sreport"; if (param) { if (valid_input()) { plot_results(); } } else { print_form(); } # # When the chart cannot be constructed, tell the user what went wrong. # sub print_msg { my ($msg) = @_; print header, start_html('No Results'); print $msg; print end_html; } # # Ask the user to specify the chart s/he wants to see # sub print_form { print header, start_html('HPC Stats'); my @values = qw( top_user top_acct job_size size_acct utilizatn ); my %labels = ( user_usage => 'Usage - Single User: ', acct_usage => 'Usage - Single Account: ', top_user => 'Usage - Top Ten Users', top_acct => 'Usage - Top Ten Accounts', job_size => 'Job Size', size_acct => 'Job Size by Accounts', utilizatn => 'Cluster Utilization', ); print start_form; print h2("SLURM Accounting Reports"); print h3("Cluster"), popup_menu(-name=>'cluster', -values=>cluster_list()); print h3("Report"); print radio_group(-name => 'report', -values => 'user_usage', -labels => \%labels), textfield(-name => 'user', -default => remote_user()), br; print radio_group(-name => 'report', -values => 'acct_usage', -labels => \%labels), textfield(-name => 'account'), br; print radio_group(-name => 'report', -values => \@values, -linebreak => 'true', -labels => \%labels); print h3("From"); print b("Month: "), textfield(-name=>'month_begin', -value=>'mm', -size=>2, -maxlength=>2), b(" Day: "), textfield(-name=>'day_begin', -value=>'dd', -size=>2, -maxlength=>2), b(" Year: "), textfield(-name=>'year_begin', -value=>'11', -size=>2, -maxlength=>2), br; print h3("Up To But Not Including"); print b("Month: "), textfield(-name=>'month_end', -value=>'mm', -size=>2, -maxlength=>2), b(" Day: "), textfield(-name=>'day_end', -value=>'dd', -size=>2, -maxlength=>2), b(" Year: "), textfield(-name=>'year_end', -value=>'11', -size=>2, -maxlength=>2), br; print h3("X Axis"), radio_group(-name=>'x_axis', -values=>['Days','Weeks','Months', 'Quarters','Years','aggregate'], -default=>'Days', -linebreak=>'true'); print h3("Y Axis"), radio_group(-name=>'y_axis', -values=>['percent','hours'], -default=>'percent', -labels=>{'percent'=>'percent', 'hours'=>'processor-hours'}, -linebreak=>'true'), br; print submit, p, reset; print end_form; print end_html; } sub untaint_user_input { my ($user_input) = @_; if ($user_input =~ /^(\w+)$/) { return $1; } print_msg("Invalid User Input: $user_input"); exit; } sub untaint_digits { my ($user_input) = @_; if ($user_input =~ /^(\d+)$/) { return $1; } print_msg("Invalid Numeric Value: $user_input"); exit; } # # Manually validate the calendar fields # sub valid_input { $yb = untaint_digits(param('year_begin')); $yb =~ s/^0*//; $mb = untaint_digits(param('month_begin')); $mb =~ s/^0*//; $db = untaint_digits(param('day_begin')); $db =~ s/^0*//; $ye = untaint_digits(param('year_end')); $ye =~ s/^0*//; $me = untaint_digits(param('month_end')); $me =~ s/^0*//; $de = untaint_digits(param('day_end')); $de =~ s/^0*//; my (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime(); if ($ye > ($year - 100)) { ($ye, $me, $de) = ($year - 100, $mon + 1, $mday); } elsif ($ye == ($year - 100)) { if ($me > ($mon + 1)) { ($me, $de) = ($mon + 1, $mday); } elsif (($me == ($mon + 1)) && ($de > $mday)) { $de = $mday; } } if ($yb < 0 || $yb > $ye) { print_msg("Invalid From Year: $yb"); return 0; } elsif ($ye < 0) { print_msg("Invalid To Year: $ye"); return 0; } elsif ($mb < 1 || $mb > 12) { print_msg("Invalid From Month: $mb"); return 0; } elsif ($me < 1 || $me > 12) { print_msg("Invalid To Month: $me"); return 0; } elsif ($db < 1 || $db > 31) { print_msg("Invalid From Day: $db"); return 0; } elsif ($de < 1 || $de > 31) { print_msg("Invalid To Day: $de"); return 0; } if (timelocal(0, 0, 0, $db, $mb - 1, $yb + 100) >= timelocal(0, 0, 0, $de, $me - 1, $ye + 100)) { print_msg("From Date Must be Less Than To Date"); return 0; } $time_begin = sprintf "%.2d/%.2d/%.2d", $mb, $db, $yb; $time_end = sprintf "%.2d/%.2d/%.2d", $me, $de, $ye; if (param('report') =~ /user_usage/) { if (!param('user')) { print_msg("User not specified"); return 0; } $user = untaint_user_input(param('user')); } elsif (param('report') =~ /acct_usage/) { if (!param('account')) { print_msg("Account not specified"); return 0; } $account = untaint_user_input(param('account')); } $cluster = untaint_user_input(param('cluster')); $x_axis = untaint_user_input(param('x_axis')); $y_axis = untaint_user_input(param('y_axis')); return 1; } # # Construct the end time for the next query # sub next_time { my ($yn, $mn, $dn) = ($yb, $mb, $db); my $new_time; my $end_time = timelocal(0, 0, 0, $de, $me - 1, $ye + 100); if ($_ = $x_axis) { /Years/ && do { $yn++; $new_time = timelocal(0, 0, 0, $dn, $mn - 1, $yn + 100); }; /Quarters/ && do { $mn += 3; if ($mn > 12) { $mn %= 12; $yn++; } $new_time = timelocal(0, 0, 0, $dn, $mn - 1, $yn + 100); }; /Months/ && do { $mn++; if ($mn > 12) { $mn = 1; $yn++; } $new_time = timelocal(0, 0, 0, $dn, $mn - 1, $yn + 100); }; /Weeks/ && do { $new_time = timelocal(0, 0, 0, $db, $mb - 1, $yb + 100); $new_time += 7 * 24 * 3600; (undef,undef,undef,$dn,$mn,$yn,undef,undef,undef) = localtime($new_time); $mn++; $yn -= 100; }; /Days/ && do { $new_time = timelocal(0, 0, 0, $db, $mb - 1, $yb + 100); $new_time += 24 * 3600; (undef,undef,undef,$dn,$mn,$yn,undef,undef,undef) = localtime($new_time); $mn++; $yn -= 100; }; } if ($new_time > $end_time) { ($yn, $mn, $dn) = ($ye, $me, $de); } return ($yn, $mn, $dn); } # # Chart the bars for the User and Account queries # These queries return 2 values per line: a user or account and its usage. # The aggregate queries require just one sreport invocation. # The periodic queries require multiple sreport invocations. # We use the Expect module to open just one sreport connection and # issue multiple queries. # sub add_bars { my ($cmd_base, $request) = @_; my ($consumer, $usage, $valid_data, $cmd, @results); if ($x_axis eq 'aggregate') { $obj->set ('legend' => 'none'); $obj->set ('x_label' => "$time_begin To $time_end"); $cmd = "$cmd_base $request start=$time_begin end=$time_end"; @results = `$cmd`; for (@results) { ($consumer, $usage) = split; if ($usage) { chop($usage) if ($y_axis =~ /percent/); $obj->add_pt($consumer, $usage); } } $valid_data = @{$obj->get_data()}; } else { my ($i, $j, $k, $l, @data_points, %consum_id, @legend_labels, $before); my ($yn, $mn, $dn); my ($start, $end); my $exp = new Expect or die "Failed to initiate Expect session: $!\n"; $exp->raw_pty(1); $exp->log_stdout(0); $exp->spawn($cmd_base) or die "Cannot spawn sreport: $!\n"; $exp->expect(5, 'sreport:'); $i = 0; $j = 1; while (($i < 12) && ($yb < $ye || $mb < $me || $db < $de)) { ($yn, $mn, $dn) = next_time($yb, $mb, $db); $start = sprintf "%.2d/%.2d/%.2d", $mb, $db, $yb; $end = sprintf "%.2d/%.2d/%.2d", $mn, $dn, $yn; $exp->send("$request start=$start end=$end\n"); $exp->expect(30, 'sreport:'); $before = $exp->before(); @results = split "\n", $before; $data_points[$i][0] = $start; for (@results) { ($consumer, $usage) = split; if ($usage) { chop($usage) if ($y_axis =~ /percent/); $consumer = untaint_user_input($consumer); unless ($consum_id{$consumer}) { $consum_id{$consumer} = $j; $legend_labels[$j++ - 1] = $consumer; } $data_points[$i][$consum_id{$consumer}] = $usage; } } ($yb, $mb, $db) = ($yn, $mn, $dn); $i++; } $exp->send("quit\n"); $exp->soft_close(); for ($k = 0; $k < $i; $k++) { for ($l = 0; $l < $j; $l++) { # Chart.pm complains if you pass it null data points. $data_points[$k][$l] = 0 unless ($data_points[$k][$l]); } $obj->add_pt(@{$data_points[$k]}); } $obj->set ('legend_labels' => \@legend_labels); $valid_data = ($j > 0); } return $valid_data; } # # There is currently no "account TopUsage" command available in sreport. # If there were we could use add_bars(). Instead, we must use sreport's # "cluster AccountUtilizationByUser" command and sift through the copious # output. We have to carefully ignore all the lines of user usage as well # as the parent accounts of accounts. I.e., we only want to report "leaf" # accounts. Then we have to sort the accounts by usage and only report the # top ten. # add_top_account_bars() is essentially add_bars() that has been modified to # do all this. The %last_account saves each account's usage and is only # copied to %top_accounts when it is a leaf account. # sub add_top_account_bars { my ($cmd_base, $request) = @_; my ($account, $usage, $valid_data, $cmd, @results); my (%last_account, %top_accounts, $m, $key); if ($x_axis eq 'aggregate') { $obj->set ('legend' => 'none'); $obj->set ('x_label' => "$time_begin To $time_end"); $cmd = "$cmd_base $request start=$time_begin end=$time_end"; @results = `$cmd`; for (@results) { ($account, $usage) = split; if ($usage) { chop($usage) if ($y_axis =~ /percent/); if (!$last_account{$account}) { $last_account{$account} = $usage; } elsif ($last_account{$account} > 0) { $top_accounts{$account} = $last_account{$account}; $last_account{$account} = -1; } } } $m = 1; foreach $key (sort { $top_accounts{$b} <=> $top_accounts{$a} } keys %top_accounts) { $obj->add_pt($key, $top_accounts{$key}); last if ($m++ > 9); } $valid_data = ($m > 1); } else { my ($i, $j, $k, $l, @data_points, %acct_id, @legend_labels, $before); my ($yn, $mn, $dn); my ($start, $end); my $exp = new Expect or die "Failed to initiate Expect session: $!\n"; $exp->raw_pty(1); $exp->log_stdout(0); $exp->spawn($cmd_base) or die "Cannot spawn sreport: $!\n"; $exp->expect(5, 'sreport:'); $i = 0; $j = 1; while (($i < 12) && ($yb < $ye || $mb < $me || $db < $de)) { ($yn, $mn, $dn) = next_time($yb, $mb, $db); $start = sprintf "%.2d/%.2d/%.2d", $mb, $db, $yb; $end = sprintf "%.2d/%.2d/%.2d", $mn, $dn, $yn; $exp->send("$request start=$start end=$end\n"); $exp->expect(30, 'sreport:'); $before = $exp->before(); @results = split "\n", $before; $data_points[$i][0] = $start; for (@results) { ($account, $usage) = split; if ($usage) { chop($usage) if ($y_axis =~ /percent/); if (!$last_account{$account}) { $last_account{$account} = $usage; } elsif ($last_account{$account} > 0) { $top_accounts{$account} = $last_account{$account}; $last_account{$account} = -1; } } } $m = 1; foreach $key (sort { $top_accounts{$b} <=> $top_accounts{$a} } keys %top_accounts) { unless ($acct_id{$key}) { $acct_id{$key} = $j; $legend_labels[$j++ - 1] = $key; } $data_points[$i][$acct_id{$key}] = $top_accounts{$key}; last if ($m++ > 9); } %last_account = (); %top_accounts = (); ($yb, $mb, $db) = ($yn, $mn, $dn); $i++; } $exp->send("quit\n"); $exp->soft_close(); for ($k = 0; $k < $i; $k++) { for ($l = 0; $l < $j; $l++) { # Chart.pm complains if you pass it null data points. $data_points[$k][$l] = 0 unless ($data_points[$k][$l]); } $obj->add_pt(@{$data_points[$k]}); } $obj->set ('legend_labels' => \@legend_labels); $valid_data = ($j > 0); } return $valid_data; } # # add_job_size_bars() presents a report of cluster usage based on the # size of the jobs that were allocated the cycles. Four job size # groups have been pre-selected and are based on the percentage of the # cluster's processors the job was allocated: 1 to 10%, 10 to 29%, 30 # to 74%, and 75 to 100%. # # There is currently no sreport command that returns job size reports # for the entire cluster. Instead, this routine has to sum the # reports for each account and chart the results for the whole # cluster. # sub add_job_size_bars { my ($cmd_base, $request, $cpus) = @_; my ($cmd, @results, @field, @group, $i); $obj->set ('y_label' => 'Percent of Allocated Cycles') if ($y_axis =~ /percent/); if ($x_axis eq 'aggregate') { $obj->set ('legend' => 'none'); $obj->set ('x_label' => "Job Size as a Portion of $cluster ($cpus total processors)"); $cmd = "$cmd_base $request start=$time_begin end=$time_end"; @results = `$cmd`; for (@results) { (undef, $field[0], $field[1], $field[2], $field[3], undef) = split; for ($i = 0; $i < 4; $i++) { $group[$i] += $field[$i]; } @field = (); } if ($y_axis =~ /percent/) { my $total_used = $group[0] + $group[1] + $group[2] + $group[3]; $obj->add_pt('1 - 9%', 100 * $group[0] / $total_used); $obj->add_pt('10 - 29%', 100 * $group[1] / $total_used); $obj->add_pt('30 - 74%', 100 * $group[2] / $total_used); $obj->add_pt('75 - 100%', 100 * $group[3] / $total_used); } else { $obj->add_pt('1 - 9%', $group[0]); $obj->add_pt('10 - 29%', $group[1]); $obj->add_pt('30 - 74%', $group[2]); $obj->add_pt('75 - 100%', $group[3]); } } else { my ($i, $k, $g, @data_points, @legend_labels, $before); my ($yn, $mn, $dn); my ($start, $end); my $exp = new Expect or die "Failed to initiate Expect session: $!\n"; $exp->raw_pty(1); $exp->log_stdout(0); $exp->spawn($cmd_base) or die "Cannot spawn sreport: $!\n"; $exp->expect(5, 'sreport:'); @legend_labels = ('1 - 9%', '10 - 29%', '30 - 74%', '75 - 100%'); $i = 0; while (($i < 12) && ($yb < $ye || $mb < $me || $db < $de)) { ($yn, $mn, $dn) = next_time($yb, $mb, $db); $start = sprintf "%.2d/%.2d/%.2d", $mb, $db, $yb; $end = sprintf "%.2d/%.2d/%.2d", $mn, $dn, $yn; $exp->send("$request start=$start end=$end\n"); $exp->expect(30, 'sreport:'); $before = $exp->before(); @results = split "\n", $before; for (@results) { (undef, $field[0], $field[1], $field[2], $field[3], undef) = split; for ($g = 0; $g < 4; $g++) { $group[$g] += $field[$g]; } @field = (); } $data_points[$i][0] = $start; if ($y_axis =~ /percent/) { my $total_used = $group[0] + $group[1] + $group[2] + $group[3]; $data_points[$i][1] = 100 * $group[0] / $total_used; $data_points[$i][2] = 100 * $group[1] / $total_used; $data_points[$i][3] = 100 * $group[2] / $total_used; $data_points[$i][4] = 100 * $group[3] / $total_used; } else { $data_points[$i][1] = $group[0]; $data_points[$i][2] = $group[1]; $data_points[$i][3] = $group[2]; $data_points[$i][4] = $group[3]; } @group = (); ($yb, $mb, $db) = ($yn, $mn, $dn); $i++; } $exp->send("quit\n"); $exp->soft_close(); for ($k = 0; $k < $i; $k++) { $obj->add_pt(@{$data_points[$k]}); } $obj->set ('legend_labels' => \@legend_labels); } return 1; } # # add_job_size_account_bars charts usage for the top ten accounts, # with each bar divided into job size ranges. There are a fixed # number of job size bins which are based on a percentage of the # cluster: '1 - 9%', '10 - 29%', '30 - 74%', and '75 - 100%' # # In order to accommodate both account and job size data on the same # chart, we must ignore the x_axis selection and provide an aggregate # picture only. # sub add_job_size_account_bars { my ($cmd_base, $request) = @_; my ($cmd, @results, $account, @field); my ($i, $j, $m, @data_points, @top_data, @legend_labels, $total_used); $obj->set ('x_label' => 'Accounts'); $obj->set ('y_label' => 'Percent of Allocated Cycles') if ($y_axis =~ /percent/); @legend_labels = ('1 - 9%', '10 - 29%', '30 - 74%', '75 - 100%'); $cmd = "$cmd_base $request start=$time_begin end=$time_end"; @results = `$cmd`; $i = 0; for (@results) { ($account, $field[0], $field[1], $field[2], $field[3], undef) = split; chop @field if ($y_axis =~ /percent/); $data_points[$i][0] = $account; $data_points[$i][1] = $field[0]; $data_points[$i][2] = $field[1]; $data_points[$i][3] = $field[2]; $data_points[$i][4] = $field[3]; $data_points[$i][5] = $field[0] + $field[1] + $field[2] + $field[3]; $total_used += $data_points[$i][5]; @field = (); $i++; } $m = 1; foreach $i (sort { @$b[5] <=> @$a[5] } @data_points) { $top_data[0] = @$i[0]; for ($j = 1; $j < 5; $j++) { if ($y_axis =~ /percent/) { $top_data[$j] = @$i[$j] * 100 / $total_used; } else { $top_data[$j] = @$i[$j]; } } $obj->add_pt(@top_data); last if ($m++ > 9); @top_data = (); } $obj->set ('legend_labels' => \@legend_labels); return ($m > 1); } # # Cluster Utilization queries return 4 values per line # sub add_utilizatn_bars { my ($cmd_base, $request) = @_; my ($allocated, $reserved, $idle, $down, $cmd, $results); if ($x_axis eq 'aggregate') { $obj->set ('legend' => 'none'); $obj->set ('x_label' => "$time_begin To $time_end"); $cmd = "$cmd_base $request start=$time_begin end=$time_end"; $results = `$cmd`; ($allocated, $reserved, $idle, $down) = split ' ', $results; chop($allocated, $reserved, $idle, $down) if ($y_axis =~ /percent/); $obj->add_pt('Allocated', $allocated); $obj->add_pt('Reserved', $reserved); $obj->add_pt('Idle', $idle); $obj->add_pt('Down', $down); } else { my ($i, $k, @data_points, @legend_labels); my ($yn, $mn, $dn); my ($start, $end); my $exp = new Expect or die "Failed to initiate Expect session: $!\n"; $exp->raw_pty(1); $exp->log_stdout(0); $exp->spawn($cmd_base) or die "Cannot spawn sreport: $!\n"; $exp->expect(5, 'sreport:'); @legend_labels = qw( Allocated Reserved Idle Down ); $i = 0; while (($i < 12) && ($yb < $ye || $mb < $me || $db < $de)) { ($yn, $mn, $dn) = next_time($yb, $mb, $db); $start = sprintf "%.2d/%.2d/%.2d", $mb, $db, $yb; $end = sprintf "%.2d/%.2d/%.2d", $mn, $dn, $yn; $exp->send("$request start=$start end=$end\n"); $exp->expect(30, 'sreport:'); $results = $exp->before(); ($allocated, $reserved, $idle, $down) = split ' ', $results; chop($allocated, $reserved, $idle, $down) if ($y_axis =~ /percent/); $data_points[$i][0] = $start; $data_points[$i][1] = $allocated; $data_points[$i][2] = $reserved; $data_points[$i][3] = $idle; $data_points[$i][4] = $down; ($yb, $mb, $db) = ($yn, $mn, $dn); $i++; } $exp->send("quit\n"); $exp->soft_close(); for ($k = 0; $k < $i; $k++) { $obj->add_pt(@{$data_points[$k]}); } $obj->set ('legend_labels' => \@legend_labels); } return 1; } # # Discovers the available clusters # sub cluster_list { my @clusters; my @results = `$sacctmgr_bin -nr show cluster format=cluster`; for (@results) { chomp; s/^\s+//; s/\s+$//; push @clusters, $_; } return \@clusters; } # # Charts the user usage request # sub chart_user { my $title = "$user Usage of $cluster"; $obj->set ('title' => $title); my $cmd = "$sreport_bin -nt $y_axis"; my $request = " cluster UserUtilizationByAccount" . " cluster=$cluster user=$user format=Account,Used"; if (add_bars($cmd, $request)) { $obj->cgi_png(); } else { print_msg("No significant usage found for user $user" . " from $time_begin to $time_end"); } } # # Charts the account usage request # sub chart_account { my $title = "$account Usage of $cluster"; $obj->set ('title' => $title); my $cmd = "$sreport_bin -nt $y_axis"; my $request = " cluster AccountUtilizationByUser" . " cluster=$cluster account=$account format=Login,Used"; if (add_bars($cmd, $request)) { $obj->cgi_png(); } else { print_msg("No significant usage found for account $account" . " from $time_begin to $time_end"); } } # # Charts the usage of the top ten users. Use the "Group" option to # retrieve the combined usage from all the accounts the user charged. # sub chart_top_users { my $title = "$cluster Top Users"; $obj->set ('title' => $title); my $cmd = "$sreport_bin -nt $y_axis"; my $request = " user TopUsage Group cluster=$cluster format=Login,Used"; if (add_bars($cmd, $request)) { $obj->cgi_png(); } else { print_msg("Failed to report top users from $time_begin to $time_end"); } } # # Charts the usage of the top ten accounts. # sub chart_top_accounts { my $title = "$cluster Top Accounts"; $obj->set ('title' => $title); my $cmd = "$sreport_bin -nt $y_axis"; my $request = " cluster AccountUtilizationByUser" . " cluster=$cluster format=Account,Used"; if (add_top_account_bars($cmd, $request)) { $obj->cgi_png(); } else { print_msg("Failed to report top accounts from $time_begin to $time_end"); } } # # Charts the cluster usage based on job size. # sub chart_job_size { my @brink; my $cpus = `$sacctmgr_bin -nr show cluster cluster=$cluster format=cpucount`; chomp; $cpus =~ s/^\s+//; $cpus =~ s/\s+$//; $cpus = untaint_digits($cpus); my $title = "$cluster Usage Grouped by Job Size"; $obj->set ('title' => $title); $brink[0] = int(0.1 * $cpus); $brink[1] = int(0.3 * $cpus); $brink[2] = int(0.75 * $cpus); my $cmd = "$sreport_bin -nt hours"; my $request = " job SizesByAccount cluster=$cluster" . " format=Account grouping=$brink[0],$brink[1],$brink[2]"; if (add_job_size_bars($cmd, $request, $cpus)) { $obj->cgi_png(); } else { print_msg("Failed to return Job Sizes for $cluster" . " from $time_begin to $time_end"); } } # # Charts the usage of the top ten accounts broken down by job size # categories. We're unable to provide an interval option - only the # aggregate form is supported. # sub chart_size_accounts { my @brink; my $cpus = `$sacctmgr_bin -nr show cluster cluster=$cluster format=cpucount`; chomp; $cpus =~ s/^\s+//; $cpus =~ s/\s+$//; $cpus = untaint_digits($cpus); my $title = "$cluster Usage Grouped by Job Size and Accounts"; $obj->set ('title' => $title); $brink[0] = int(0.1 * $cpus); $brink[1] = int(0.3 * $cpus); $brink[2] = int(0.75 * $cpus); my $cmd = "$sreport_bin -nt hours"; my $request = " job SizesByAccount FlatView cluster=$cluster" . " format=Account grouping=$brink[0],$brink[1],$brink[2]"; if (add_job_size_account_bars($cmd, $request, $cpus)) { $obj->cgi_png(); } else { print_msg("Failed to return Job Sizes/Accounts for $cluster" . " from $time_begin to $time_end"); } } # # Charts the cluster utilization # sub chart_utilizatn { my $title = "Utilization of $cluster"; $obj->set ('title' => $title); my $cmd = "$sreport_bin -nt $y_axis"; my $request = " cluster Utilization cluster=$cluster" . " format=Allocated,Reserved,Idle,Down"; if (add_utilizatn_bars($cmd, $request)) { $obj->cgi_png(); } else { print_msg("Failed to return Utilization for $cluster" . " from $time_begin to $time_end"); } } # # Setup some chart attributes and select the subroutine that charts the # user's request # sub plot_results { my $sub_title = "From $time_begin To $time_end"; $obj->set ('sub_title' => $sub_title); $obj->set ('x_label' => $x_axis); if ($y_axis =~ /percent/) { $obj->set ('y_label' => 'Percent of Cluster'); } else { $obj->set ('y_label' => 'Processor - Hours'); } $obj->set ('include_zero' => 'true'); if ($_ = param('report')) { /user_usage/ && chart_user(); /acct_usage/ && chart_account(); /top_user/ && chart_top_users(); /top_acct/ && chart_top_accounts(); /job_size/ && chart_job_size(); /size_acct/ && chart_size_accounts(); /utilizatn/ && chart_utilizatn(); } }