App-FQStat
view release on metacpan or search on metacpan
script/fqstat.pl view on Meta::CPAN
#!/usr/bin/perl
use 5.008;
# fqstat.pl (version see FQStat.pm) is (c) 2007-2009 Steffen Mueller
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
use strict;
use warnings;
use threads;
use threads::shared;
use IO::Handle;
use Time::HiRes qw/sleep time/;
use Term::ANSIScreen qw/RESET cls/;
use Term::ReadKey;
use Getopt::Long;
use constant DEBUG => 0;
use constant STARTTIME => Time::HiRes::time();
###################
# prepare for logging if in debug mode
BEGIN {
use vars qw/*DEBUGFH/;
my $DEBUGFH;
sub debug ($) {
require FileHandle;
my $arg = shift;
return if not DEBUG;
if (not $DEBUGFH) {
$DEBUGFH = FileHandle->new();
$DEBUGFH->open('fqstat.debug', '>>') or die $!;
$DEBUGFH->autoflush(1);
*DEBUGFH = $DEBUGFH;
}
chomp $arg;
print $DEBUGFH $arg."\n";
}
debug("Setting up debug mode");
open(STDERR, ">&DEBUGFH");
}
######################
# Record key constants
use constant {
F_id => 0,
F_prio => 1,
F_name => 2,
F_user => 3,
F_status => 4,
F_date => 5,
F_time => 6,
F_queue => 7,
};
use constant RECORD_KEY_CONSTANT => {
id => F_id, prio => F_prio, name => F_name,
user => F_user, status => F_status, date => F_date,
'time' => F_time, queue => F_queue,
};
use constant RECORD_CONSTANT_KEY => [
qw/ id prio name user status date time queue /
];
################
# load local modules
use App::FQStat;
script/fqstat.pl view on Meta::CPAN
# displaying globals
our $Initialized = 0;
our @Termsize : shared; # holds terminal size
our $DisplayOffset : shared = 0; # Offset of the first displayed job
our $SortField : shared; # may hold name of sort field
our $Interval : shared; # Effective data refreshing interval. Do not change. Is set to $UserInterval below
our $HighlightUser;
{
my $curuser = $ENV{USER};
if (defined $curuser) {
$HighlightUser = quotemeta($curuser);
}
}
# application mode globals
our $MenuMode = 0; # in menu or not
our $SummaryMode :shared = App::FQStat::Config::get("summary_mode") || 0; # in summary mode or not
# menu globals
our $MenuNumber = 0; # which menu (see @App::FQStat::Menu::Menus)
our $MenuEntryNumber = 0; # in which entry of that menu
# Displayed column descriptions
our %Columns = (
prio => { format => '%.5f', width => 7, name => 'Prio', key => 'prio', 'index' => F_prio, order => 'num_highlow' },
name => { format => '%-10s', width => 10, name => 'Name', key => 'name', 'index' => F_name, order => 'alpha' },
user => { format => '%-12s', width => 12, name => 'Owner', key => 'user', 'index' => F_user, order => 'alpha' },
id => { format => '%7u', width => 7, name => 'Id', key => 'id', 'index' => F_id, order => 'num' },
date => { format => '%-10s', width => 10, name => 'Date', key => 'date', 'index' => F_date, order => 'date' },
'time' => { format => '%-8s', width => 8, name => 'Time', key => 'time', 'index' => F_time, order => 'time' },
queue => { format => '%30s', width => 30, name => 'Queue', key => 'queue', 'index' => F_queue, order => 'alpha' },
);
# Column order
our @Columns = qw(id name prio user date time queue);
# Summary Mode: Displayed column descriptions
our %SummaryColumns = (
user => { format => '%-12s', width => 12, name => 'Owner', key => 'user', 'index' => 0, order => 'alpha' },
name => { format => '%-12s', width => 12, name => 'Name-Like', key => 'name', 'index' => 1, order => 'alpha' },
n_run => { format => '%-5u', width => 5, name => 'NRun', key => 'nrun', 'index' => 2, order => 'num' },
n_err => { format => '%-5u', width => 5, name => 'NErr', key => 'nerr', 'index' => 3, order => 'num' },
n_hld => { format => '%-5u', width => 5, name => 'NHold', key => 'nhold', 'index' => 4, order => 'num' },
n_wait => { format => '%-5u', width => 5, name => 'NWait', key => 'nwait', 'index' => 5, order => 'num' },
prio => { format => '%.6f', width => 8, name => 'AvrgPrio', key => 'prio', 'index' => 6, order => 'num_highlow' },
'time' => { format => '%-11s', width => 11, name => 'AvrgRunTime', key => 'time', 'index' => 7, order => 'time' },
maxtime => { format => '%-11s', width => 11, name => 'MaxRunTime', key => 'maxtime', 'index' => 9, order => 'time' },
);
# Summary Mode: Column order
our @SummaryColumns = qw(user name n_run n_err n_hld n_wait prio time maxtime);
# Data structure to hold information about the current state of affairs
our $Records = [];
our $RecordsChanged : shared = 0;
our $RecordsReversed : shared = 0;
our $NoActiveNodes = 0;
our $Summary = [];
# scanner thread globals, see below.
##############
# Get Command line arguments
our $User : shared;
our $UserInterval = 30;
our $SlowRedraw = 0;
my $SSHCommand;
our $ResetConfig;
Getopt::Long::Configure("no_ignore_case");
GetOptions(
'u|user=s' => \$User,
'H|highlight=s' => \$HighlightUser,
'i|interval=f' => \$UserInterval,
's|slow' => \$SlowRedraw,
'ssh=s' => \$SSHCommand,
'resetconfig' => \$ResetConfig,
'h|help|?' => sub {
ReadMode 1;
print RESET;
print usage();
thread_cleanup();
exit(1);
},
);
$UserInterval ||= 30;
$Interval = $UserInterval; # start out with requested interval
##############
# Get/prepare configuration
if ($ResetConfig) {
App::FQStat::Config::reset_configuration();
App::FQStat::Config::save_configuration();
cleanup_and_exit();
}
if (defined $SSHCommand) {
App::FQStat::Config::set("sshcommand", $SSHCommand);
}
#################
# Default qstat paths
our $QStatCmd = App::FQStat::Config::get("qstat") || 'qstat';
our $QDelCmd = App::FQStat::Config::get("qdel") || 'qdel';
our $QAlterCmd = App::FQStat::Config::get("qalter") || 'qalter';
our $QModCmd = App::FQStat::Config::get("qmod") || 'qmod';
###################
# Check that we can run qstat and friends
if (not App::FQStat::System::module_install_can_run($QStatCmd)) {
print <<HERE;
ERROR!
You cannot run fqstat without having a working "qstat" command in your
application search path. Please add a "qstat" to \$PATH and retry.
(Or use the --ssh option correctly.)
HERE
print RESET;
thread_cleanup();
ReadMode 1;
exit(1);
}
# XXX Check for qdel and qalter too?
###################
# setup scanner thread
our $ScannerStartRun : shared = 0;
our $ScannerThread;# = threads->new(\&App::FQStat::Scanner::scanner_thread);
# thread exit handler
sub thread_cleanup {
warnenter if ::DEBUG;
if (defined $ScannerThread and $ScannerThread->is_running()) {
print "Cleaning up polling threads...\n";
$ScannerThread->kill('SIGKILL');
}
}
# exit handler
sub cleanup_and_exit {
warnenter if ::DEBUG;
print RESET;
Term::ANSIScreen::locate($Termsize[1],1);
thread_cleanup();
ReadMode 1;
print "Have a nice day!\n" unless @_;
exit();
}
$SIG{INT} = \&cleanup_and_exit;
$SIG{HUP} = \&cleanup_and_exit;
$SIG{TERM} = \&cleanup_and_exit;
$SIG{__DIE__} = sub{warn @_;ReadMode 1;exit(1);};
###########################
# RUN
GetTermSize();
cls();
App::FQStat::Drawing::update_display(1);
print_module_versions() if ::DEBUG;
%PAR::FileCache = %PAR::FileCache = () if exists $ENV{PAR_TEMP};
main_loop();
exit(0);
##############################
# Update @TermSize variable
sub GetTermSize {
warnenter if ::DEBUG > 2;
@Termsize = Term::ReadKey::GetTerminalSize();
@Termsize = (80,25,0,0) if not @Termsize == 4;
}
####################
# MAIN LOOP
BEGIN {
%ControlKeys = (
'A' => sub { App::FQStat::Actions::scroll_up(1); 1 }, # up
'B' => sub { App::FQStat::Actions::scroll_down(1); 1 }, # down
'5' => sub { App::FQStat::Actions::scroll_up($Termsize[1]-4); 1 }, # pgup
'6' => sub { App::FQStat::Actions::scroll_down($Termsize[1]-4); 1 }, # pgdown
'H' => sub { App::FQStat::Actions::scroll_up(1e9); 1 }, # pos1 (640kb ought to be enough for everyone!)
'F' => sub { App::FQStat::Actions::scroll_down(1e9); 1 }, # end (640kb ought to be enough for everyone!)
'15' => sub { App::FQStat::Drawing::update_display(1) }, # F5
'21' => sub { App::FQStat::Menu::toggle_menu() }, # F10
);
%Keys = (
'q' => \&cleanup_and_exit,
'i' => \&App::FQStat::Actions::set_user_interval,
'H' => \&App::FQStat::Actions::update_highlighted_user_name,
'r' => \&App::FQStat::Actions::toggle_reverse_sort,
's' => \&App::FQStat::Actions::select_sort_field,
'u' => \&App::FQStat::Actions::update_user_name,
'k' => \&App::FQStat::Actions::kill_jobs,
'p' => \&App::FQStat::Actions::change_priority,
'o' => \&App::FQStat::Actions::hold_jobs,
'O' => \&App::FQStat::Actions::resume_jobs,
'h' => \&App::FQStat::Actions::show_manual,
'?' => \&App::FQStat::Actions::show_manual,
'c' => \&App::FQStat::Actions::clear_job_error_state,
'd' => \&App::FQStat::Actions::change_dependencies,
' ' => \&App::FQStat::Actions::show_job_details,
script/fqstat.pl view on Meta::CPAN
%MenuKeys = %Keys;
$MenuKeys{"\n"} = \&App::FQStat::Menu::menu_select, # Enter
$MenuKeys{" "} = \&App::FQStat::Menu::menu_select, # space
delete $MenuKeys{$_} foreach qw(S);
%SummaryKeys = map {($_ => $Keys{$_})} qw(q i h S);
$SummaryKeys{c} = \&App::FQStat::Actions::toggle_summary_name_clustering;
$SummaryKeys{s} = $SummaryKeys{S};
%SummaryControlKeys = map {($_ => $ControlKeys{$_})} qw(15 21);
}
my @OldTermSize = @Termsize;
sub main_loop {
warnenter if ::DEBUG;
my $Redraw = 1;
my $RedrawTime = time();
my $RedrawOffset = $DisplayOffset;
while (1) {
my $input = get_input_key();
if (defined $input) {
my ($KeysHash, $ControlKeysHash);
if ($MenuMode) {
$KeysHash = \%MenuKeys;
$ControlKeysHash = \%MenuControlKeys;
}
elsif ($SummaryMode) {
$KeysHash = \%SummaryKeys;
$ControlKeysHash = \%SummaryControlKeys;
}
else {
$KeysHash = \%Keys;
$ControlKeysHash = \%ControlKeys;
}
#warn "-I->$input<---->".ord($input);
if ($KeysHash->{$input}) {
my $redraw = $KeysHash->{$input}->($input);
$Redraw = 1 if $redraw;
}
elsif ($input eq '[') { # control-key!
my $key = get_input_key(0.001);
#warn "-K->$key<---->".ord($key);
if (defined $key and exists $ControlKeysHash->{$key}) {
my $redraw = $ControlKeysHash->{$key}->($key);
$Redraw = 1 if $redraw;
}
elsif ($key eq '1' or $key eq '2') { # F-keys
my $innerkey = get_input_key(0.001);
#warn "-IK->$innerkey<---->".ord($innerkey);
if (defined $innerkey and exists($ControlKeysHash->{"$key$innerkey"})) {
my $redraw = $ControlKeysHash->{"$key$innerkey"}->("$key$innerkey");
$Redraw = 1 if $redraw;
}
}
} # end control keys
} # end if defined input
# Fetch new scanner results if applicable
if (defined $ScannerThread and $ScannerThread->is_joinable()) {
warnline "Scanner thread joinable in main loop. Joining" if ::DEBUG;
my $return = $ScannerThread->join();
($Records, $NoActiveNodes) = @$return;
$Initialized = 1;
warnline "Scanner thread joined in main loop" if ::DEBUG;
lock($RecordsChanged);
$RecordsChanged = 1;
$Summary = [];
}
my $startRun;
{
lock($ScannerStartRun);
$startRun = $ScannerStartRun;
}
if ($startRun) {
App::FQStat::Scanner::run_qstat();
}
{
lock($RecordsChanged);
lock($DisplayOffset);
$Redraw = 1 if $RecordsChanged;
$Redraw = 1 if $DisplayOffset != $RedrawOffset;
}
GetTermSize();
$Redraw = 1 if !@OldTermSize or $OldTermSize[0] != $Termsize[0] or $OldTermSize[1] != $Termsize[1];
@OldTermSize = @Termsize;
$Redraw = 1 if time()-$RedrawTime > ($SlowRedraw ? 20.0 : 3.0);
if ($Redraw) {
App::FQStat::Drawing::update_display();
$RedrawOffset = $DisplayOffset;
$Redraw = 0;
lock($RecordsChanged);
$RecordsChanged = 0;
$RedrawTime = time();
restart() if $RedrawTime - STARTTIME() > 4*60*60; # restart every four hours (wallclock)
}
} # end while(1)
}
sub print_module_versions {
warnenter if ::DEBUG;
foreach my $file (sort keys %INC) {
my $path = $INC{$file};
my $module = $file;
$module =~ s/\.pm$//;
$module =~ s/\//::/g;
my $version;
eval "\$version = $module->VERSION;";
$version = 'undef' if not defined $version;
debug("$module ($version): $path\n");
}
}
sub restart {
warnenter if ::DEBUG;
my @args;
push @args, '-u', $User
if defined $User and $User ne '';
push @args, '-H', $HighlightUser
if defined $HighlightUser and $HighlightUser ne '';
( run in 1.176 second using v1.01-cache-2.11-cpan-39bf76dae61 )