Aktualne wersje skryptów znajdują się na http://boinc.mindc.net
Przy robieniu swoich własnych statystyk BOINC, wykorzystywałem pliki ze statystykami generowanymi przez BOINC Manager, wymagało to jednak instalacji BOINC na danym komputerze. Po dyskusji na forum BOINC@Poland powstał skrypt (odbiegający trochę od pierwowzoru), który pobiera sam statystyki, bezpośrednio z schedulera danego projektu.
Konfiguracja skryptu znajduje się pliku xml, domyślnie config.xml w katalogu bieżącym. Gdy dany projekt wymaga alternatywnych parametrów konta, można je sprecyzować osobno dla każdego z projektów. Gdy poda się bezpośrednio klucz do danego projektu, skrypt pomija procedurę sprawdzania konta w danym projekcie. W tym przypadku pomijany jest adres e-mail i hasło do konta. W przypadku projektu World Community Grid w miejsce adresu e-mail wpisujemy nazwę użytkownika.
<config> <account_email>główny@adres.email</account_email> <account_passwd>głównehasło</account_passwd> <projects> <project> <account_email>alternatywny@adres.email</account_email> <account_passwd>alternatywnehasło</account_passwd> <project_url>http://www.ufluids.net/</project_url> </project> <project> <project_url>http://boinc.fzk.de/poem/</project_url> </project> <project> <project_url>http://milkyway.cs.rpi.edu/milkyway/</project_url> <account_key>a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0</account_key> </project> </projects> </config> |
W wyniku działania skryptu, otrzymujemy plik xml, domyślnie stats.xml w katalogu bieżącym. Gdy w danym momencie dany projekt jest niedostępny, w statystykach podawane są dane z ostatniego udanego ich pobrania.
<projects> <project> <user_total_credit>520289.524222</user_total_credit> <user_expavg_credit>3773.017568</user_expavg_credit> <project_name>Poem@Home</project_name> <project_url>http://boinc.fzk.de/poem/</project_url> <last_update>1223280309</last_update> </project> <project> <user_total_credit>128.281442</user_total_credit> <user_expavg_credit>3.607507</user_expavg_credit> <project_name>uFluids</project_name> <project_url>http://www.ufluids.net/</project_url> <last_update>1223280309</last_update> </project> <project> <user_total_credit>1713084.284584</user_total_credit> <user_expavg_credit>22483.589985</user_expavg_credit> <project_name>Milkyway@home</project_name> <project_url>http://milkyway.cs.rpi.edu/milkyway/</project_url> <last_update>1223280345</last_update> </project> </projects> |
Skrypt można uruchomić z parametrami wskazującymi alternatywny plik konfiguracyjny i/lub alternatywny plik ze statystykami. Uruchomiony bez parametrów, przyjmuje wartości domyślne.
#!/usr/bin/perl -w use strict; use Digest::MD5 qw(md5_hex); use XML::Simple; use Data::Dumper; use LWP::UserAgent; use Getopt::Long qw(:config bundling pass_through); use Carp; $|++; # defaults my $config_file = './config.xml', my $stats_file = './stats.xml', my $debug = 0; my $ua = LWP::UserAgent->new; $ua->timeout(20); my $stats = {}; my $stats_tmp = {}; my $config = {}; GetOptions( "c|config=s" => $config_file, "o|output=s" => $stats_file, "d|debug" => $debug, "h|help|usage" => &usage, ); usage() if $ARGV[0]; # wymagany plik konfiguracyjny if ( -r $config_file ) { $config = XMLin( $config_file, KeyAttr => '', GroupTags => { projects => 'project' }, ForceArray => [ 'project' ], ); print Dumper $config if $debug; } else { croak "$config_file $!n"; } # odczytanie poprzednich wartości statystyk, # na wypadek gdy projekt jest nieaktywny if ( -r $stats_file ) { $stats = XMLin( $stats_file, KeyAttr => '', GroupTags => { projects => 'project' }, ForceArray => [ 'project' ], ); print Dumper $stats if $debug; foreach my $project ( @{$stats->{project}} ) { $stats_tmp->{$project->{project_url}} = { total_credit => $project->{user_total_credit} || undef, expavg_credit => $project->{user_expavg_credit} || undef, project_url => $project->{project_url} || undef, project_name => $project->{project_name} || undef, last_update => $project->{last_update} || undef, } if exists($stats_tmp->{$project->{project_url}}); } } else { carp "$stats_file $!n"; } # główna pętla MAIN: foreach my $project ( @{$config->{projects}} ) { my $prj_url = $project->{project_url}; my $prj_email = $project->{account_email} || $config->{account_email}; my $prj_passwd = $project->{account_passwd} || $config->{account_passwd}; my $prj_key; my $sch_url; if ( $prj_email && $prj_passwd ) { print "checking `$prj_url' against `@{[ $project->{account_key} || $prj_email ]}'n"; unless ( $prj_key = ($project->{account_key} || lookup_account($prj_url,$prj_email,$prj_passwd)) ) { carp "account key not foundn"; next MAIN; } } else { carp "project email or password not found, skipping..."; next MAIN; } unless ( $sch_url = get_scheduler_url($prj_url) ) { carp "scheduler url not found, skipping..."; next MAIN; } if ( $sch_url && $prj_key ) { if ( my $st = get_stats($sch_url,$prj_key) ) { $stats_tmp->{$prj_url}{project_name} = $st->{project_name}; $stats_tmp->{$prj_url}{total_credit} = $st->{user_total_credit}; $stats_tmp->{$prj_url}{expavg_credit} = $st->{user_expavg_credit}; $stats_tmp->{$prj_url}{project_url} = $prj_url; $stats_tmp->{$prj_url}{last_update} = time; } else { carp "cannot get statistics, skipping..."; next MAIN; } } } print Dumper $stats_tmp if $debug; # wyjście my $out = "<projects>n"; foreach ( keys %$stats_tmp ) { $out .= " <project>n"; $out .= " <project_name>@{[$stats_tmp->{$_}{project_name}]}</project_name>n"; $out .= " <project_url>@{[$stats_tmp->{$_}{project_url}]}</project_url>n"; $out .= " <user_total_credit>@{[$stats_tmp->{$_}{total_credit}]}</user_total_credit>n"; $out .= " <user_expavg_credit>@{[$stats_tmp->{$_}{expavg_credit}]}</user_expavg_credit>n"; $out .= " <last_update>@{[$stats_tmp->{$_}{last_update}]}</last_update>n"; $out .= " </project>n"; } $out .= "</projects>n"; open my $fh,'>',$stats_file; print $fh $out; close $fh; exit; sub get_stats { my ($url,$key) = @_; $url =~ s!https://secure!http://www!; # obejście przy World Community Grid # wysyłamy zapytanie do schedulera # w większości projektów nie trzeba podawać wersji klienta # ale POEM wymagał tego my $req = HTTP::Request->new(POST => $url ); $req->content_type('application/x-www-form-urlencoded'); $req->content("<scheduler_request> <authenticator>$key</authenticator> <core_client_major_version>6</core_client_major_version> <core_client_minor_version>2</core_client_minor_version> <core_client_release>18</core_client_release> </scheduler_request> "); my $res = $ua->request($req); if ( $res->is_success ) { my $tmp = $res->content; $tmp =~ s!< <!<!g; # obejście przy uFluids $tmp =~ s!</scheduler_reply>.*!!s; # obejście przy World Community Grid my $cnt; eval { $cnt = XMLin($tmp); }; if ( $@ ) { carp $@; return 0; } if ( exists($cnt->{project_is_down}) ) { carp "$cnt->{message}{content}n"; return 0; } else { return $cnt; } } else { carp $res->status_line; return 0; } } sub lookup_account { my ($url,$email,$passwd) = @_; $url =~ s!/$!!; # pobieramy uid my $res = $ua->get( "$url/lookup_account.php?email_addr=$email&passwd_hash=@{[md5_hex($passwd.$email)]}" ); if ( $res->is_success ) { my $tmp = $res->content; my $cnt; eval { $cnt = XMLin($tmp); }; if ( $@ ) { carp $@; return 0; } # sprawdzamy czy nie było jakiegoś błędu przy identyfikacji if ( exists($cnt->{error_msg})) { carp "$cnt->{error_msg}n"; return 0; } else { return $cnt->{authenticator}; } } else { carp $res->status_line; return 0; } } sub get_scheduler_url { my $url = shift; my $scheduler_url; my $res = $ua->get($url); if ( $res->is_success ) { # szukanie na głównej stronie projektu adresu schedulera ($scheduler_url) = $res->content =~ m'<scheduler>s*(.*?)s*</scheduler>'; return $scheduler_url; } else { carp $res->status_line; return 0 } } sub usage { print "$0 [-c|--config=FILE] [-o|--output=FILE] [-d] [-h|--help|--usage]nn"; exit(1); } |