]> git.99rst.org Git - irssi-scripts.irssi.org.git/commitdiff
[boinc] add boinc.pl
authorbw1 <redacted>
Wed, 29 May 2019 14:14:16 +0000 (16:14 +0200)
committerbw1 <redacted>
Mon, 10 Jun 2019 20:27:19 +0000 (22:27 +0200)
scripts/boinc.pl [new file with mode: 0644]

diff --git a/scripts/boinc.pl b/scripts/boinc.pl
new file mode 100644 (file)
index 0000000..e76e7e5
--- /dev/null
@@ -0,0 +1,547 @@
+# Copyright (C) 2019 bw1
+# 
+# This program 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 3 of the License, or
+# (at your option) any later version.
+# 
+# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use vars qw($VERSION %IRSSI);
+
+use Irssi;
+use Irssi::TextUI;
+use Time::Piece;
+use Time::Seconds;
+use Getopt::Long qw/GetOptionsFromString/;
+use IPC::Open3;
+use YAML qw/Dump DumpFile LoadFile/;
+use Time::HiRes qw/sleep alarm/;
+
+$VERSION = '0.1';
+%IRSSI = (
+    authors    => 'bw1',
+    contact    => 'bw1@aol.at',
+    name       => 'boinc',
+    description        => 'interface to boinc',
+    license    => 'gpl',
+    url                => 'https://scripts.irssi.org/',
+    modules => 'Time::Piece Time::Seconds Getopt::Long IPC::Open3 YAML Time::HiRes',
+    changed    => '2019-05-29',
+);
+
+my $help = <<'END';
+%9NAME%9
+  boinc.pl - interface to boinc
+%9SYNOPSIS%9
+  /boinc {-all|-tasks|-credit|-old|-info|-update|-help|-h|-list}
+  /boinc <-host hostname> [-password password] [{-enable|-disable}]
+%9DESCRIPTION%9
+  interface to the boinc-client (https://boinc.berkeley.edu/)
+
+  add the statusbar item
+    /STATUSBAR window ADD boinc_credit
+
+%9OPTIONS%9
+  -all      view all at once
+  -tasks    view the actual tasks
+  -credit   view the credits
+  -old      view old tasks
+  -info     print all internal data (debug)
+  -update   update the statusbar item
+  -help|-h  show a help message
+  -list     list the configured hosts
+  -host     add or modify a host entry
+
+%9SETTINGS%9
+  /set boinc_update_cycle 15
+    update time in minutes of the statusbar item.
+  /set boinc_command boinccmd
+    command line interface of the BOINC client.
+END
+
+my ($a_host, $a_password, $a_disable, $a_enable);
+my %options = (
+       'all' => \&cmd_all,
+       'tasks' => \&cmd_tasks,
+       'credit' => \&cmd_credit,
+       'old' => \&cmd_old,
+       'info' => \&cmd_info,
+       'update' => \&cmd_update,
+       'help' => \&cmd_help,
+       'h' => \&cmd_help,
+       'host=s' => \$a_host,
+       'password=s' => \$a_password,
+       'disable' => sub {$a_disable =1},
+       'enable' => sub {$a_disable =0},
+       'list' => \&cmd_clist,
+);
+
+my $boinc_cmd; #='boinccmd';
+my $boinc_cmd_host='--host';
+my $boinc_cmd_passwd='--passwd';
+my $boinc_cmd_state='--get_state';
+my $boinc_cmd_old_task='--get_old_tasks';
+
+my %section= (
+       '======== Tasks ========'=> 'tasks',
+       '======== Projects ========'=> 'proj',
+       '======== Applications ========'=> 'app',
+       '======== Application versions ========'=> 'app_ver',
+       '======== Workunits ========'=> 'units',
+       '======== Time stats ========'=> 'time',
+       '======== end ========'=> 'end', # help the last round
+);
+
+my $arry_start ='-----------';
+
+my ($args, $server, $witem);
+
+my %config;
+my %info;
+
+my ($pid, %readex, $instr, $errstr);
+
+my $total_credit;
+my $expavg_credit;
+my $run_tasks;
+
+my ($time_tag, $time_cycle);
+
+sub mytime {
+       my ($tstr) = @_;
+       #my $tstr='Sun May  5 18:40:48 2019';
+       my $t= Time::Piece->strptime($tstr,'%a %b %d %H:%M:%S %Y');
+       return $t->strftime('%Y-%m-%d %H:%M');
+}
+
+sub mydifftime {
+       my ( $ts2) = @_;
+       #my $tstr='Sun May  5 18:40:48 2019';
+       my $t1= localtime;
+       my $t2= Time::Piece->strptime($ts2,'%a %b %d %H:%M:%S %Y');
+       my $td= $t2-$t1;
+       return $td->hours;
+}
+
+sub read_exec {
+       my ($cmd, $host, $rfunc) = @_;
+
+       my ($in, $out, $err);
+       use Symbol 'gensym'; $err = gensym;
+       $pid = open3($in, $out, $err, $cmd);
+       $readex{$pid}->{pid}=$pid;
+       $readex{$pid}->{cmd}=$cmd;
+       $readex{$pid}->{in}=$in;
+       $readex{$pid}->{out}=$out;
+       $readex{$pid}->{err}=$err;
+       $readex{$pid}->{host}=$host;
+       $readex{$pid}->{rfunc}=$rfunc;
+
+       Irssi::pidwait_add($pid);
+}
+
+sub sig_read_exec {
+       my ($pid, $status) = @_;
+
+       if (defined $readex{$pid} ) {
+               my $out =$readex{$pid}->{out};
+               my $err =$readex{$pid}->{err};
+               my $host =$readex{$pid}->{host};
+               my $rfunc =$readex{$pid}->{rfunc};
+
+               delete $readex{$pid};
+
+               my $old = select $out;
+               local $/;
+               $instr = <$out>;
+               select $old;
+
+               my $old = select $err;
+               local $/;
+               $errstr = <$err>;
+               $errstr =~ s/[\n\r]//g;
+               select $old;
+
+               &$rfunc($host) if (defined $rfunc);
+               if ( scalar(keys(%readex)) == 1 &&
+                               exists $readex{job}) {
+                       foreach my $j ( @{$readex{job}} ) {
+                               if ( ref( $j) eq 'CODE' ) {
+                                       &$j();
+                               } else {
+                                       eval( $j );
+                               }
+                       }
+                       delete $readex{job};
+               }
+               Irssi::signal_stop();
+       }
+}
+
+sub read_host_old_task {
+       my ($host) = @_;
+       if ( $errstr ne '') {
+               $info{$host}->{error}=$errstr;
+       }
+
+       my @lines = split /\n/,$instr;
+       push @lines, 'task last:';
+
+       my $count=0;
+       my $tl=[];
+       my $task={};
+       foreach my $l (@lines) {
+               if ($l =~ m/^task (.*):$/) {
+                       if ( $count >0 ) {
+                               push @{$tl}, $task;
+                               $task={};
+                       }
+                       $task->{taskname}=$1;
+               } else {
+                       if ($l =~ m/^\s+(.*?):\s*(.*)$/ ) {
+                               $task->{$1}=$2;
+                       }
+               }
+               $count++;
+       }
+       $info{$host}->{old_tasks}=$tl;
+}
+
+sub read_host_state {
+       my ($host) = @_;
+
+       my $h;
+       if (!exists $info{$host}) {
+               my $h={};
+               $info{$host}=$h;
+       } else {
+               $h=$info{$host};
+       }
+       my $array=0;
+
+       if ( $errstr ne '') {
+               $info{$host}->{error}=$errstr;
+       }
+       my @lines = split /\n/,$instr;
+       push @lines, '======== end ========';
+
+       my $sec='';
+       my $sec_c=0;
+       my $sec_r='';
+
+       my $arr_c=0;
+       my $arr_e=0;
+       my $arr_r='';
+
+       my $par_r={};
+
+       foreach my $line ( @lines) {
+               # section
+               if (exists $section{$line}) {
+                       if ($sec_c >0) {
+                               if ( $arr_c ==0) {
+                                       $h->{$sec}=$par_r;
+                                       $par_r={};
+                               } else {
+                                       $h->{$sec}=$arr_r;
+                               }
+                       } else {
+                       }
+                       $sec=$section{$line};
+                       $sec_c++;
+                       $sec_r='';
+                       $arr_e=1;
+               }
+               # array
+               if ($line =~ m/$arry_start/ || $arr_e != 0 ) {
+                       if ($arr_c >0) {
+                               push @{$arr_r}, $par_r;
+                               $par_r={};
+                       } else {
+                               $arr_r=[];
+                       }
+                       $arr_c++;
+                       if ($arr_e != 0) {
+                               $arr_e=0;
+                               $arr_c=0;
+                       }
+               }
+               # parameter
+               if ($line =~ m/^\s+(.*?):\s*(.*)$/ ) {
+                       $par_r->{$1}=$2;
+               }
+       }
+}
+
+sub read_hosts {
+       my ( $jobs ) = @_;
+       if (!exists $readex{job}) {
+               $readex{job}=$jobs;
+               foreach my $host (keys %config) {
+                       if ($config{$host}->{disable} != 1) {
+                               $info{$host}={};
+                               my $cmd="$boinc_cmd $boinc_cmd_host $host";
+                               if ( defined $config{$host}->{password} ) {
+                                       $cmd = "$cmd $boinc_cmd_passwd $config{$host}->{password}";
+                               }
+                               my $cmd1="$cmd $boinc_cmd_state";
+                               read_exec($cmd1, $host, \&read_host_state );
+                               my $cmd2="$cmd $boinc_cmd_old_task";
+                               read_exec($cmd2, $host, \&read_host_old_task );
+                       } else {
+                               delete $info{$host};
+                       }
+               }
+       }
+}
+
+sub calc_credit {
+       $total_credit=0;
+       $expavg_credit;
+       $run_tasks=0;
+       foreach my $host (sort keys %info) {
+               my $h=$info{$host};
+               my $utc=0;
+               my $uec=0;
+               foreach my $prj (@{$h->{proj}}) {
+                       $utc += $prj->{user_total_credit};
+                       $uec += $prj->{user_expavg_credit};
+               }
+               if ($utc > $total_credit) {
+                       $total_credit=$utc;
+                       $expavg_credit=$uec;
+               }
+               foreach my $t (@{$h->{tasks}}) {
+                       if ($t->{active_task_state} eq 'EXECUTING') {
+                               $run_tasks++;
+                       }
+               }
+       }
+}
+
+sub print_tasks {
+       foreach my $host (sort keys %info) {
+               Irssi::printformat(MSGLEVEL_CLIENTCRAP,'myhl',$host);
+               foreach my $t ( @{$info{$host}->{tasks}} ) {
+                       my $s='';
+                       $s .= sprintf " %s  ", substr($t->{name},0,3);
+                       $s .= sprintf "%5.1f ", $t->{'fraction done'}*100;
+                       my $st='W';
+                       $st='%gR%N' if ($t->{active_task_state} eq 'EXECUTING');
+                       $st='%bD%N' if ($t->{'ready to report'} eq 'yes');
+                       $s .= sprintf "%s ", $st;
+                       $s .= mytime($t->{received})."  ";
+                       $s .= mytime($t->{'report deadline'})." ";
+                       $s .= sprintf "%8.1f", mydifftime($t->{'report deadline'});
+                       Irssi::print($s, MSGLEVEL_CLIENTCRAP);
+               }
+       }
+       Irssi::print('', MSGLEVEL_CLIENTCRAP);
+}
+
+sub print_old_tasks {
+       foreach my $host (sort keys %info) {
+               Irssi::printformat(MSGLEVEL_CLIENTCRAP,'myhl',$host);
+               foreach my $t ( @{$info{$host}->{old_tasks}} ) {
+                       my $s='';
+                       $s .= sprintf "  %-20s", $t->{taskname};
+                       $s .= sprintf " %3d", $t->{'exit status'};
+                       $s .= sprintf " %8.2fh", $t->{'elapsed time'} /60/60;
+                       Irssi::print($s, MSGLEVEL_CLIENTCRAP);
+               }
+       }
+       Irssi::print('', MSGLEVEL_CLIENTCRAP);
+}
+
+sub print_credit {
+       my $total_credit=0;
+       my $expavg_credit;
+       my $he= sprintf "%-20s %10s %10s", 'host', 'avg', 'total';
+       Irssi::printformat(MSGLEVEL_CLIENTCRAP,'myhl',$he);
+       foreach my $host (sort keys %info) {
+               my $h=$info{$host};
+               my $hec=0;
+               my $htc=0;
+               my $utc=0;
+               my $uec=0;
+               foreach my $prj (@{$h->{proj}}) {
+                       $hec += $prj->{host_expavg_credit};
+                       $htc += $prj->{host_total_credit};
+                       $utc += $prj->{user_total_credit};
+                       $uec += $prj->{user_expavg_credit};
+               }
+               my $sh= sprintf ' %-19s',$host;
+               my $sa .= sprintf "%10.0f", $hec;
+               my $st .= sprintf "%10.0f", $htc;
+               Irssi::printformat(MSGLEVEL_CLIENTCRAP,'myhl', $sh, $sa, $st);
+               if ($utc > $total_credit) {
+                       $total_credit=$utc;
+                       $expavg_credit=$uec;
+               }
+       }
+       my $str= sprintf "%-20s %10.0f %10.0f",'sum', $expavg_credit, $total_credit;
+       Irssi::printformat(MSGLEVEL_CLIENTCRAP,'myhl',$str);
+       Irssi::print('', MSGLEVEL_CLIENTCRAP);
+}
+
+sub print_error {
+       foreach my $h (keys %info) {
+               if ( exists $info{$h}->{error} ) {
+                       Irssi::print "Error: $info{$h}->{error}", MSGLEVEL_CLIENTCRAP;
+               }
+       }
+}
+
+sub sb_boinc_credit {
+       my ($sb_item, $get_size_only) = @_;
+       my $sb = 
+               sprintf "run:%d avg:%d sum:%d",
+                       $run_tasks, $expavg_credit, $total_credit;
+       $sb_item->default_handler($get_size_only, "{sb $sb}", '', 0);
+}
+
+sub cmd {
+       ($args, $server, $witem)=@_;
+       my ($ret, $arg) = GetOptionsFromString($args, %options);
+
+       if (defined $a_host) {
+               $config{$a_host}->{host}=$a_host;
+               $config{$a_host}->{password}=$a_password 
+                               if (defined $a_password);
+               $config{$a_host}->{disable}=$a_disable
+                               if (defined $a_disable);
+               $a_host=undef;
+               $a_password=undef;
+               $a_disable=undef;
+               $a_enable=undef;
+       }
+}
+
+sub cmd_clist {
+       my $s;
+       $s =sprintf '%-20s %-20s %-2s','host','password','disable';
+       Irssi::print $s, MSGLEVEL_CLIENTCRAP;
+       foreach my $h (sort keys %config) {
+               $s =sprintf '%-20s %-20s %2d',
+                               $config{$h}->{host},
+                               $config{$h}->{password},
+                               $config{$h}->{disable};
+               Irssi::print $s, MSGLEVEL_CLIENTCRAP;
+       }
+}
+
+sub cmd_all {
+       read_hosts([
+               \&print_tasks,
+               \&print_credit,
+               \&print_old_tasks,
+               \&print_error,
+       ]);
+}
+
+sub cmd_tasks {
+       read_hosts([
+               \&print_tasks,
+               \&print_error,
+       ]);
+}
+
+sub cmd_credit {
+       read_hosts([
+               \&print_credit,
+               \&print_error,
+       ]);
+}
+
+sub cmd_old {
+       read_hosts([
+               \&print_old_tasks,
+               \&print_error,
+       ]);
+}
+
+sub cmd_info {
+       Irssi::print "%info", MSGLEVEL_CLIENTCRAP;
+       Irssi::print Dump(\%info), MSGLEVEL_CLIENTCRAP;
+}
+
+sub cmd_update {
+       read_hosts();
+       $readex{job} = [
+               \&calc_credit,
+               'Irssi::statusbar_items_redraw("boinc_credit");',
+       ];
+}
+
+sub cmd_help {
+       Irssi::print $help, MSGLEVEL_CLIENTCRAP;
+}
+
+sub write_config {
+       my $fn=Irssi::get_irssi_dir().'/boinc.yaml';
+       DumpFile( $fn, \%config);
+}
+
+sub read_config {
+       my $fn=Irssi::get_irssi_dir().'/boinc.yaml';
+       if (-e $fn) {
+               %config = %{ LoadFile($fn) };
+       }
+}
+
+sub sig_setup_changed {
+       $boinc_cmd= Irssi::settings_get_str('boinc_command');
+       my $new_time = Irssi::settings_get_int('boinc_update_cycle');
+       if ( $time_cycle != $new_time ) {
+               if ( defined $time_tag) {
+                       Irssi::timeout_remove($time_tag);
+                       $time_tag= undef;
+                       $time_cycle= 0;
+               }
+               if ($new_time !=0 ) {
+                       $time_tag= 
+                               Irssi::timeout_add($new_time*60*1000, \&cmd_update, '');
+                       cmd_update();
+                       $time_cycle= $new_time;
+               }
+       }
+}
+
+sub UNLOAD {
+       write_config();
+}
+
+Irssi::theme_register([
+       'myhl', '{hilight $0} $1 $2',
+]);
+
+Irssi::command_bind('help', sub {
+               if ($_[0] =~ m/boinc/ ) {
+                       cmd_help();
+                       Irssi::signal_stop;
+               }
+       }
+);
+
+Irssi::signal_add('pidwait', 'sig_read_exec');
+Irssi::signal_add('setup changed', 'sig_setup_changed');
+
+Irssi::statusbar_item_register ('boinc_credit', 0, 'sb_boinc_credit');
+
+Irssi::settings_add_int($IRSSI{'name'}, 'boinc_update_cycle', 15);
+Irssi::settings_add_str($IRSSI{'name'}, 'boinc_command', 'boinccmd');
+
+Irssi::command_bind($IRSSI{name},\&cmd);
+my @opt=map {$_ =~ s/=.*$//, $_ } keys %options;
+Irssi::command_set_options($IRSSI{name}, join(" ", @opt));
+
+read_config();
+sig_setup_changed();
git clone https://git.99rst.org/PROJECT