From: bw1 Date: Wed, 29 May 2019 14:14:16 +0000 (+0200) Subject: [boinc] add boinc.pl X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=74d1bb4011e8610cb8f2a701894d0bc9250e5d9c;p=irssi-scripts.irssi.org.git [boinc] add boinc.pl --- diff --git a/scripts/boinc.pl b/scripts/boinc.pl new file mode 100644 index 0000000..e76e7e5 --- /dev/null +++ b/scripts/boinc.pl @@ -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 . + +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();