User:AnomieBOT/source/show-task-status.pl
Appearance
< User:AnomieBOT | source
#!/usr/bin/perl -w
use strict;
use utf8;
binmode STDOUT, ":utf8";
use Cwd;
use File::Basename;
use lib File::Basename::dirname( Cwd::realpath( __FILE__ ) );
use LWP::UserAgent;
use JSON;
use AnomieBOT::API;
use POSIX qw/floor ceil/;
# For scaling sizes
my @sizescale = (
[ 1024**4, 'T' ],
[ 1024**3, 'G' ],
[ 1024**2, 'M' ],
[ 1024**1, 'K' ],
);
# First, get the list of running Kubernetes jobs.
my $jobs;
if ( -f '/var/run/secrets/kubernetes.io/serviceaccount/namespace' ) {
# We're running inside a container, hit the API.
open X, '<:utf8', '/var/run/secrets/kubernetes.io/serviceaccount/namespace' or die "Failed to read namespace: $!\n";
my $namespace = <X>;
close X;
my $ua = LWP::UserAgent->new(
ssl_opts => {
SSL_ca_file => "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
SSL_cert_file => "$ENV{HOME}/.toolskube/client.crt",
SSL_key_file => "$ENV{HOME}/.toolskube/client.key",
},
);
my $res = $ua->get( "https://kubernetes.default.svc/api/v1/namespaces/$namespace/pods/" );
if ( $res->is_success ) {
$jobs = JSON->new->decode( $res->decoded_content );
}
} else {
# We're running on the bastion, shell out to kubectl.
$jobs = JSON->new->decode( scalar `kubectl get pods -o json` );
}
my %running = ( 'unknown' => 1 );
if ( $jobs ) {
foreach my $job ( @{$jobs->{'items'}} ) {
my $jobhost = $job->{'metadata'}{'name'};
$running{$jobhost} = 1 if $job->{'status'}{'phase'} eq 'Running';
}
}
# Next, fetch the list of running tasks from Redis
my $api = AnomieBOT::API->new("conf.ini", 1, { db => 0 });
my @tasks;
my $now = ftime( time );
my $old = time - 1800;
while(1){
@tasks = ();
my ($tasks, $token) = $api->cache->gets( 'joblist' );
die "No joblist\n" unless ref($tasks) eq 'ARRAY';
die "No tasks\n" unless @$tasks;
# Now, query the status of every running task
my @keep = ();
for my $task (@$tasks) {
# Check that the task should still be running
AnomieBOT::API::load("tasks/$task.pm");
my $pkg = "tasks::$task";
my $t=$pkg->new();
my $a=$t->approved;
next if $a <= 0;
push @keep, $task;
my $status = $api->cache->get( "status:$task" ) // {
'botnum' => '?',
'hostname' => 'unknown',
'status' => 'unknown',
'lastrun' => 0,
'nextrun' => 0,
};
$status->{'task'} = $task;
if ( !exists( $running{$status->{'hostname'}} ) ) {
$status->{'status'} = 'pod missing';
$status->{'nextrun'} = 0;
}
$status->{'sort botnum'} = $status->{'botnum'};
$status->{'lastrun'} = ftime( $status->{'lastrun'} );
$status->{'sort nextrun'} = '9999-12-31 23:59:59' unless $status->{'nextrun'};
$status->{'isold nextrun'} = $status->{'nextrun'} < $old if $status->{'nextrun'};
$status->{'nextrun'} = ftime( $status->{'nextrun'} );
push @tasks, $status;
}
if ( @keep != @$tasks ) {
redo unless $api->cache->cas( 'joblist', \@keep, $token );
}
last;
}
# Now, output. Either plain text for command-line mode, or HTML if passed --html
my $RLdebug = 'true';
if ( grep $_ eq '--html', @ARGV ) {
print <<EOHTML;
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>AnomieBOT Task Status</title>
<link rel="stylesheet" href="https://tools-static.wmflabs.org/cdnjs/ajax/libs/sortable/0.8.0/css/sortable-theme-minimal.min.css" />
<link rel="stylesheet" href="https://tools-static.wmflabs.org/anomiebot/wikitable.css" />
<script src="https://tools-static.wmflabs.org/cdnjs/ajax/libs/sortable/0.8.0/js/sortable.min.js"></script>
</head>
<body>
<h1>AnomieBOT status</h1>
<p>This page gives the current status of AnomieBOT's various jobs and tasks. For details on each task, see <a href="https://wiki.riteme.site/wiki/User:AnomieBOT/TaskList">User:AnomieBOT/TaskList</a>.</p>
<h2>Tasks</h2>
EOHTML
print "Report generated $now\n";
print qq(<table data-sortable class="wikitable tasks">\n);
}
# First, the list of tasks
my @fields = qw/task botnum hostname status lastrun nextrun/;
my %labels = ( task => 'Task', botnum => 'Bot', hostname => 'Host', status => 'Status', lastrun => 'Last run', nextrun => 'Next run' );
my %align = ( task => '-', botnum => '', hostname => '-', status => '-', lastrun => '-', nextrun => '-' );
my %l = ();
print_data( sort { tasksorter($a,$b) } @tasks );
if ( grep $_ eq '--html', @ARGV ) {
print <<EOHTML;
</table>
<script>if(window.mw){
mw.loader.load(["mediawiki.page.ready"],null,true);
}</script>
</body>
</html>
EOHTML
}
# Utility function to find the max of two numbers
sub max {
return $_[0] > $_[1] ? $_[0] : $_[1];
}
# Utility function to format a timestamp
sub ftime {
my $t = shift;
return '-' unless $t;
return POSIX::strftime( '%F %T', gmtime $t );
}
# Sorting function for the bot tasks table
sub tasksorter {
my ($a,$b) = @_;
my $na = $a->{'botnum'};
my $nb = $b->{'botnum'};
return $a->{'task'} cmp $b->{'task'} if $na eq $nb;
return $na <=> $nb if $na =~ /^\d+/ && $nb =~ /^\d+/;
return -1 if $na =~ /^\d+/;
return 1 if $nb =~ /^\d+/;
return $na cmp $nb;
}
# Simple HTML encoding
sub esc {
my $s = shift;
$s=~s/&/&/g;
$s=~s/</</g;
$s=~s/>/>/g;
$s=~s/"/"/g;
return $s;
}
# Print the table rows
sub print_data {
my @rows = @_;
%l = ();
for my $row (@rows) {
for my $k (@fields) {
$l{$k} = max( $l{$k} // length($labels{$k}), length( $row->{$k} ) );
}
}
my ($make, $pre, $mid, $post);
if ( grep $_ eq '--html', @ARGV ) {
$make = sub {
my ($obj, $k) = @_;
my $v = esc( $obj->{$k} );
my $sv = esc( $obj->{"sort $k"} // $obj->{$k} );
my $td = '<td';
$td .= $align{$k} eq '' ? ' align="right"' : '';
$td .= ' style="background-color:#fcc"' if $obj->{"isold $k"} // 0;
$td .= qq( data-value="$sv") if $sv ne $v;
$td .= ">$v</td>";
return $td;
};
($pre, $mid, $post) = ('<tr>', '', '</tr>');
print "<thead><tr>";
for my $k (@fields) {
printf '<th>%s</th>', esc( $labels{$k} );
}
print "</tr></thead>\n";
print "<tbody>\n";
} else {
$make = sub {
my ($obj, $k) = @_;
return sprintf( "%$align{$k}$l{$k}s", $obj->{$k} );
};
($pre, $mid, $post) = ('', ' ', '');
my @line1 = ();
my @line2 = ();
for my $k (@fields) {
push @line1, $labels{$k} . (" " x ($l{$k} - length($labels{$k})));
push @line2, "-" x $l{$k};
}
print join(" ", @line1) . "\n";
print join(" ", @line2) . "\n";
}
for my $row (@rows) {
print $pre . join( $mid, map { $make->($row, $_) } @fields) . $post . "\n";
}
if ( grep $_ eq '--html', @ARGV ) {
print "</tbody>\n";
}
}