Automatic headers & improvements.

This commit is contained in:
Brian Fraser
2012-01-18 19:01:19 -03:00
parent 5b43ed0d5e
commit ad552756b2
8 changed files with 296 additions and 105 deletions

View File

@@ -1533,6 +1533,10 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use IO::Handle;
use List::Util qw( max first );
use ReadKeyMini qw( GetTerminalSize );
my (undef, $max_lines) = GetTerminalSize;
my $diskstat_colno_for;
BEGIN {
$diskstat_colno_for = {
@@ -1576,9 +1580,11 @@ sub new {
block_size => 512,
show_inactive => $o->get('show-inactive'),
sample_time => $o->get('sample-time') || 0,
automatic_headers => $o->get('automatic-headers'),
columns_regex => qr/$columns/,
devices_regex => $devices ? qr/$devices/ : undef,
interactive => 0,
force_header => 1,
%args,
@@ -1606,13 +1612,38 @@ sub new {
_nochange_skips => [],
_save_curr_as_prev => 1,
_print_header => 1,
};
return bless $self, $class;
}
sub active_device {
my ( $self, $dev ) = @_;
return $self->{_active_devices}->{$dev};
}
sub set_active_device {
my ($self, $dev, $val) = @_;
return $self->{_active_devices}->{$dev} = $val;
}
sub clear_active_devices {
my ( $self ) = @_;
return $self->{_active_devices} = {};
}
sub automatic_headers {
my ($self) = @_;
return $self->{automatic_headers};
}
sub set_automatic_headers {
my ($self, $new_val) = @_;
return $self->{automatic_headers} = $new_val;
}
sub curr_ts {
my ($self) = @_;
return $self->{_ts}->{curr} || 0;
@@ -1732,9 +1763,19 @@ sub add_ordered_dev {
}
sub force_header {
my ($self) = @_;
return $self->{force_header};
}
sub set_force_header {
my ($self, $new_val) = @_;
return $self->{force_header} = $new_val;
}
sub clear_state {
my ($self) = @_;
$self->{_print_header} = 1;
$self->set_force_header(1);
$self->clear_curr_stats();
$self->clear_prev_stats();
$self->clear_first_stats();
@@ -2159,22 +2200,39 @@ sub _print_device_if {
my $dev_re = $self->devices_regex();
if ( $dev_re ) {
$self->_mark_if_active($dev);
return $dev if $dev =~ $dev_re;
}
else {
if ( $self->show_inactive() || $self->active_device($dev) ) {
if ( $self->active_device($dev) ) {
return $dev;
}
elsif ( $self->show_inactive() ) {
$self->_mark_if_active($dev);
return $dev;
}
else {
return $dev if $self->_mark_if_active($dev);
}
}
push @{$self->{_nochange_skips}}, $dev;
return;
}
sub _mark_if_active {
my ($self, $dev) = @_;
return $dev if $self->active_device($dev);
my $curr = $self->stats_for($dev);
my $first = $self->first_stats_for($dev);
return unless $curr && $first;
if ( first { $curr->[$_] != $first->[$_] } READS..MS_WEIGHTED ) {
$self->set_active_device($dev, 1);
return $dev;
}
}
}
push @{$self->{_nochange_skips}}, $dev;
return;
}
@@ -2240,32 +2298,30 @@ sub _calc_deltas {
return $self->_calc_stats_for_deltas($elapsed);
}
sub force_print_header {
my ($self, @args) = @_;
my $orig = $self->force_header();
$self->force_header(1);
$self->print_header(@args);
$self->force_header($orig);
return;
}
sub print_header {
my ($self, $header, @args) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
printf $header . "\n", @args;
$Diskstats::printed_lines--;
$Diskstats::printed_lines ||= $max_lines;
}
}
sub active_device {
my ( $self, $dev ) = @_;
return $self->{_active_devices}->{$dev};
}
sub set_active_device {
my ($self, $dev, $val) = @_;
return $self->{_active_devices}->{$dev} = $val;
}
sub clear_active_devices {
my ( $self ) = @_;
return $self->{_active_devices} = {};
return;
}
sub print_rows {
my ($self, $format, $cols, $stat) = @_;
printf $format . "\n", @{ $stat }{ qw( line_ts dev ), @$cols };
$Diskstats::printed_lines--;
}
sub print_deltas {
@@ -2283,11 +2339,28 @@ sub print_deltas {
my $header_method = $args{header_callback} || "print_header";
my $rows_method = $args{rows_callback} || "print_rows";
$Diskstats::printed_lines ||= $max_lines;
$self->$header_method( $header, "#ts", "device" );
foreach my $stat ( $self->_calc_deltas() ) {
my @stats = $self->_calc_deltas();
while ( my @stats_chunk = splice @stats, 0, $Diskstats::printed_lines ) {
foreach my $stat ( @stats_chunk ) {
$self->$rows_method( $format, $cols, $stat );
}
if ( $Diskstats::printed_lines == 0 ) {
$Diskstats::printed_lines ||= $max_lines;
if ( $self->automatic_headers()
&& !$self->isa("DiskstatsGroupByAll") )
{
local $self->{force_header} = 1;
$self->$header_method( $header, "#ts", "device" );
}
}
}
}
sub compute_line_ts {
@@ -2473,7 +2546,6 @@ sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args);
$self->{_iterations} = 0;
$self->{_print_header} = 1;
return $self;
}
@@ -2502,12 +2574,12 @@ sub group_by {
header_callback => sub {
my ($self, @args) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
my $method = $args{header_callback}
|| "print_header";
$self->$method(@args);
}
$self->{_print_header} = undef;
$self->set_force_header(undef);
},
rows_callback => $args{rows_callback},
);
@@ -2546,10 +2618,10 @@ sub group_by {
sub clear_state {
my ($self, @args) = @_;
my $orig_print_h = $self->{_print_header};
my $orig_print_h = $self->{force_header};
$self->{_iterations} = 0;
$self->SUPER::clear_state(@args);
$self->{_print_header} = $orig_print_h;
$self->{force_header} = $orig_print_h;
}
sub compute_line_ts {
@@ -2618,7 +2690,6 @@ sub new {
my $self = $class->SUPER::new(%args);
$self->{_iterations} = 0;
$self->{_save_curr_as_prev} = 0;
$self->{_print_header} = 1;
return $self;
}
@@ -2658,10 +2729,10 @@ sub _sample_callback {
header_callback => sub {
my ( $self, $header, @args ) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
my $method = $args{header_callback} || "print_header";
$self->$method( $header, @args );
$self->{_print_header} = undef;
$self->set_force_header(undef);
}
},
rows_callback => sub {
@@ -2694,7 +2765,6 @@ sub clear_state {
my ( $self, @args ) = @_;
$self->{_iterations} = 0;
$self->{_save_curr_as_prev} = 0;
$self->{_print_header} = 1;
$self->SUPER::clear_state(@args);
}
@@ -2842,8 +2912,11 @@ my %actions = (
"Enter a disk/device pattern: " ),
'q' => sub { return 'last' },
'p' => sub {
my (@args) = @_;
print "Paused - press any key to continue\n";
pause(@_);
pause(@args);
$Diskstats::printed_lines--;
print_header(@args) unless $Diskstats::printed_lines;
return;
},
' ' => \&print_header,
@@ -2957,8 +3030,8 @@ sub run_interactive {
&& !grep { $input eq $_ } qw( A S D ), ' ', "\n" )
{
my $obj = $o->get("current_group_by_obj");
local $obj->{_print_header} = 1;
$obj->clear_state();
local $obj->{force_header} = 1;
group_by(
select_obj => $sel,
OptionParser => $o,
@@ -3046,8 +3119,7 @@ sub print_header {
my $obj = $o->get("current_group_by_obj");
my ($header) = $obj->design_print_formats();
local $obj->{_print_header} = 1;
return $obj->print_header($header, "#ts", "device");
return $obj->force_print_header($header, "#ts", "device");
}
sub group_by {
@@ -3089,7 +3161,7 @@ sub group_by {
header_callback => $header_callback,
);
$obj->set_interactive(1);
$obj->{_print_header} = 0;
$obj->set_force_header(0);
}
}
@@ -3107,7 +3179,7 @@ sub help {
$re ||= '(none)';
}
print <<"HELP";
my $help = <<"HELP";
You can control this program by key presses:
------------------- Key ------------------- ---- Current Setting ----
A, D, S) Set the group-by mode $mode
@@ -3120,7 +3192,17 @@ sub help {
q) Quit the program
------------------- Press any key to continue -----------------------
HELP
pause(@_);
print $help;
=begin IGNORE
my $lines = $help =~ tr/\n//;
while ( $lines-- ) {
$Diskstats::printed_lines--;
print_header(%args) unless $Diskstats::printed_lines;
}
=cut
pause(%args);
return;
}
@@ -3169,6 +3251,7 @@ sub get_blocking_input {
ReadKeyMini::cbreak();
STDIN->blocking(0);
return $new_opt;
}
@@ -3553,6 +3636,12 @@ When in interactive mode, wait N seconds before printing to the screen.
Show inactive devices.
=item --[no]automatic-headers
default: yes
Print the headers as often as needed to prevent it from scrolling out of view.
=item --help
Show help and exit.

View File

@@ -32,6 +32,10 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0;
use IO::Handle;
use List::Util qw( max first );
use ReadKeyMini qw( GetTerminalSize );
my (undef, $max_lines) = GetTerminalSize;
my $diskstat_colno_for;
BEGIN {
$diskstat_colno_for = {
@@ -79,9 +83,11 @@ sub new {
block_size => 512,
show_inactive => $o->get('show-inactive'),
sample_time => $o->get('sample-time') || 0,
automatic_headers => $o->get('automatic-headers'),
columns_regex => qr/$columns/,
devices_regex => $devices ? qr/$devices/ : undef,
interactive => 0,
force_header => 1,
%args,
@@ -110,7 +116,6 @@ sub new {
# Internal for now, but might need APIfying.
_save_curr_as_prev => 1,
_print_header => 1,
};
return bless $self, $class;
@@ -118,6 +123,32 @@ sub new {
# The next lot are accessors, plus some convenience functions.
sub active_device {
my ( $self, $dev ) = @_;
return $self->{_active_devices}->{$dev};
}
sub set_active_device {
my ($self, $dev, $val) = @_;
return $self->{_active_devices}->{$dev} = $val;
}
sub clear_active_devices {
my ( $self ) = @_;
return $self->{_active_devices} = {};
}
sub automatic_headers {
my ($self) = @_;
return $self->{automatic_headers};
}
sub set_automatic_headers {
my ($self, $new_val) = @_;
return $self->{automatic_headers} = $new_val;
}
sub curr_ts {
my ($self) = @_;
return $self->{_ts}->{curr} || 0;
@@ -242,9 +273,19 @@ sub add_ordered_dev {
# clear_stuff methods. Like the name says, they clear state stored inside
# the object.
sub force_header {
my ($self) = @_;
return $self->{force_header};
}
sub set_force_header {
my ($self, $new_val) = @_;
return $self->{force_header} = $new_val;
}
sub clear_state {
my ($self) = @_;
$self->{_print_header} = 1;
$self->set_force_header(1);
$self->clear_curr_stats();
$self->clear_prev_stats();
$self->clear_first_stats();
@@ -740,26 +781,44 @@ sub _print_device_if {
# device_regex was set explicitly, either through --devices-regex,
# or by using the d option in interactive mode, and not leaving
# it blank
$self->_mark_if_active($dev);
return $dev if $dev =~ $dev_re;
}
else {
if ( $self->show_inactive() || $self->active_device($dev) ) {
if ( $self->active_device($dev) ) {
# If --show-interactive is enabled, or we've seen
# the device be active at least once.
return $dev;
}
elsif ( $self->show_inactive() ) {
$self->_mark_if_active($dev);
return $dev;
}
else {
return $dev if $self->_mark_if_active($dev);
}
}
# Not active, add it to the list of skips for debugging.
push @{$self->{_nochange_skips}}, $dev;
return;
}
sub _mark_if_active {
my ($self, $dev) = @_;
return $dev if $self->active_device($dev);
my $curr = $self->stats_for($dev);
my $first = $self->first_stats_for($dev);
return unless $curr && $first;
# read 'any' instead of 'first'
if ( first { $curr->[$_] != $first->[$_] } READS..MS_WEIGHTED ) {
# It's different from the first one. Mark as active and return.
$self->set_active_device($dev, 1);
return $dev;
}
}
}
# Not active, add it to the list of skips for debugging.
push @{$self->{_nochange_skips}}, $dev;
return;
}
@@ -827,32 +886,31 @@ sub _calc_deltas {
return $self->_calc_stats_for_deltas($elapsed);
}
# Always print a header, disgreard the value of $self->force_header()
sub force_print_header {
my ($self, @args) = @_;
my $orig = $self->force_header();
$self->force_header(1);
$self->print_header(@args);
$self->force_header($orig);
return;
}
sub print_header {
my ($self, $header, @args) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
printf $header . "\n", @args;
$Diskstats::printed_lines--;
$Diskstats::printed_lines ||= $max_lines;
}
}
sub active_device {
my ( $self, $dev ) = @_;
return $self->{_active_devices}->{$dev};
}
sub set_active_device {
my ($self, $dev, $val) = @_;
return $self->{_active_devices}->{$dev} = $val;
}
sub clear_active_devices {
my ( $self ) = @_;
return $self->{_active_devices} = {};
return;
}
sub print_rows {
my ($self, $format, $cols, $stat) = @_;
printf $format . "\n", @{ $stat }{ qw( line_ts dev ), @$cols };
$Diskstats::printed_lines--;
}
sub print_deltas {
@@ -871,11 +929,34 @@ sub print_deltas {
my $header_method = $args{header_callback} || "print_header";
my $rows_method = $args{rows_callback} || "print_rows";
$Diskstats::printed_lines ||= $max_lines;
$self->$header_method( $header, "#ts", "device" );
foreach my $stat ( $self->_calc_deltas() ) {
my @stats = $self->_calc_deltas();
# Split the stats in chunks no greater than how many lines
# we have left until printing the next header.
while ( my @stats_chunk = splice @stats, 0, $Diskstats::printed_lines ) {
# Print the stats
foreach my $stat ( @stats_chunk ) {
$self->$rows_method( $format, $cols, $stat );
}
if ( $Diskstats::printed_lines == 0 ) {
# If zero, reset the counter
$Diskstats::printed_lines ||= $max_lines;
# If we are automagically printing headers and aren't in
# --group-by all,
if ( $self->automatic_headers()
&& !$self->isa("DiskstatsGroupByAll") )
{
local $self->{force_header} = 1;
$self->$header_method( $header, "#ts", "device" );
}
}
}
}
sub compute_line_ts {

View File

@@ -34,7 +34,6 @@ sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args);
$self->{_iterations} = 0;
$self->{_print_header} = 1;
return $self;
}
@@ -65,12 +64,12 @@ sub group_by {
header_callback => sub {
my ($self, @args) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
my $method = $args{header_callback}
|| "print_header";
$self->$method(@args);
}
$self->{_print_header} = undef;
$self->set_force_header(undef);
},
rows_callback => $args{rows_callback},
);
@@ -116,10 +115,10 @@ sub group_by {
sub clear_state {
my ($self, @args) = @_;
my $orig_print_h = $self->{_print_header};
my $orig_print_h = $self->{force_header};
$self->{_iterations} = 0;
$self->SUPER::clear_state(@args);
$self->{_print_header} = $orig_print_h;
$self->{force_header} = $orig_print_h;
}
sub compute_line_ts {

View File

@@ -35,7 +35,6 @@ sub new {
my $self = $class->SUPER::new(%args);
$self->{_iterations} = 0;
$self->{_save_curr_as_prev} = 0;
$self->{_print_header} = 1;
return $self;
}
@@ -86,10 +85,10 @@ sub _sample_callback {
header_callback => sub {
my ( $self, $header, @args ) = @_;
if ( $self->{_print_header} ) {
if ( $self->force_header() ) {
my $method = $args{header_callback} || "print_header";
$self->$method( $header, @args );
$self->{_print_header} = undef;
$self->set_force_header(undef);
}
},
rows_callback => sub {
@@ -122,7 +121,6 @@ sub clear_state {
my ( $self, @args ) = @_;
$self->{_iterations} = 0;
$self->{_save_curr_as_prev} = 0;
$self->{_print_header} = 1;
$self->SUPER::clear_state(@args);
}

View File

@@ -190,7 +190,7 @@ sub run_interactive {
my $obj = $o->get("current_group_by_obj");
# Force it to print the header
$obj->clear_state();
local $obj->{_print_header} = 1;
local $obj->{force_header} = 1;
group_by(
select_obj => $sel,
OptionParser => $o,
@@ -291,8 +291,7 @@ sub print_header {
my $obj = $o->get("current_group_by_obj");
my ($header) = $obj->design_print_formats();
local $obj->{_print_header} = 1;
return $obj->print_header($header, "#ts", "device");
return $obj->force_print_header($header, "#ts", "device");
}
sub group_by {
@@ -341,7 +340,7 @@ sub group_by {
header_callback => $header_callback,
);
$obj->set_interactive(1);
$obj->{_print_header} = 0;
$obj->set_force_header(0);
}
}
@@ -372,7 +371,8 @@ sub help {
q) Quit the program
------------------- Press any key to continue -----------------------
HELP
pause(@_);
pause(%args);
return;
}
@@ -421,6 +421,7 @@ sub get_blocking_input {
ReadKeyMini::cbreak();
STDIN->blocking(0);
return $new_opt;
}

View File

@@ -135,11 +135,8 @@ sub readkey {
# As per perlfaq8:
sub _GetTerminalSize {
if ( @_ ) {
die "My::Term::ReadKey doesn't implement GetTerminalSize with arguments";
}
eval { require 'sys/ioctl.ph' };
BEGIN {
eval { no warnings; local $^W; require 'sys/ioctl.ph' };
if ( !defined &TIOCGWINSZ ) {
*TIOCGWINSZ = sub () {
# Very few systems actually have ioctl.ph, thus it comes to this.
@@ -150,13 +147,37 @@ sub _GetTerminalSize {
: 0x40087468;
};
}
open( TTY, "+<", "/dev/tty" ) or die "No tty: $OS_ERROR";
my $winsize = '';
unless ( ioctl( TTY, &TIOCGWINSZ, $winsize ) ) {
die sprintf "$0: ioctl TIOCGWINSZ (%08x: $OS_ERROR)\n", &TIOCGWINSZ;
}
sub _GetTerminalSize {
if ( @_ ) {
die "My::Term::ReadKey doesn't implement GetTerminalSize with arguments";
}
my ( $row, $col, $xpixel, $ypixel ) = unpack( 'S4', $winsize );
return ( $col, $row, $xpixel, $ypixel );
my ( $rows, $cols );
if ( open( TTY, "+<", "/dev/tty" ) ) { # Got a tty
my $winsize = '';
if ( ioctl( TTY, &TIOCGWINSZ, $winsize ) ) {
( $rows, $cols, my ( $xpixel, $ypixel ) ) = unpack( 'S4', $winsize );
return ( $cols, $rows, $xpixel, $ypixel );
}
}
if ( $rows = `tput lines` ) {
chomp($rows);
chomp($cols = `tput cols`);
}
elsif ( my $stty = `stty -a` ) {
($rows, $cols) = $stty =~ /([0-9]+) rows; ([0-9]+) columns;/;
}
else {
($cols, $rows) = @ENV{qw( COLUMNS LINES )};
$cols ||= 80;
$rows ||= 24;
}
return ( $cols, $rows );
}
}

View File

@@ -322,12 +322,12 @@ is(
"compute_line_ts has a sane default",
);
$obj->{_print_header} = 0;
$obj->set_force_header(0);
is(
output( sub { $obj->print_header } ),
output( sub { $obj->print_header("asdasdas") } ),
"",
"INTERNAL: _print_header works"
"force_header works"
);
my $output = output(
@@ -472,6 +472,7 @@ for my $test (
$obj->set_columns_regex(qr/ \A (?!.*io_s$|\s*[qs]time$) /x);
$obj->set_show_inactive(1);
$obj->set_automatic_headers(0);
for my $filename ( map "diskstats-00$_.txt", 1..5 ) {
my $file = File::Spec->catfile( "t", "pt-diskstats", "samples", $filename );

View File

@@ -57,7 +57,9 @@ sub test_diskstats_file {
sub {
tie local *STDIN, TestInteractive => @commands;
pt_diskstats::main(
qw(--show-inactive --group-by), $groupby,
'--show-inactive',
'--no-automatic-headers',
'--group-by', $groupby,
'--columns-regex','cnc|rt|mb|busy|prg',
$file);
},
@@ -69,7 +71,6 @@ sub test_diskstats_file {
}
}
foreach my $file ( map "diskstats-00$_.txt", 1..5 ) {
test_diskstats_file(file => $file);
}