mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-12 14:18:32 +00:00
Tweaks requested by Baron, plus fixes.
Tweaks: * --headers command line option, which can be used to enable/disable the automatic headers, and the automatic empty lines between samples. * --show-timestamps, which changes the #ts line with a timestamp in the HH:MM:SS format. * Always use ReadKeyMini, even when we have Term::ReadKey. In the latter case, use the Term::ReadKey functions, but if they fail, try again with our own versions. Fix: Looks like I forgot to actually enable the "save the current, first and last" samples. This is now on. It should mean faster and better output when changing --group-by modes.
This commit is contained in:
227
bin/pt-diskstats
227
bin/pt-diskstats
@@ -1328,26 +1328,9 @@ sub _d {
|
|||||||
# End Transformers package
|
# End Transformers package
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
|
|
||||||
# This program is copyright 2010-2011 Percona Inc.
|
|
||||||
# Feedback and improvements are welcome.
|
|
||||||
#
|
|
||||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
|
||||||
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
|
||||||
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
#
|
|
||||||
# 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, version 2; OR the Perl Artistic License. On UNIX and similar
|
|
||||||
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
|
|
||||||
# licenses.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|
||||||
# Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
# ReadKeyMini
|
# ReadKeyMini
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
{
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
|
|
||||||
package ReadKeyMini;
|
package ReadKeyMini;
|
||||||
@@ -1379,7 +1362,18 @@ BEGIN {
|
|||||||
my $have_readkey = eval { require Term::ReadKey };
|
my $have_readkey = eval { require Term::ReadKey };
|
||||||
|
|
||||||
if ($have_readkey) {
|
if ($have_readkey) {
|
||||||
Term::ReadKey->import(@EXPORT_OK);
|
*ReadMode = sub {
|
||||||
|
eval { return Term::ReadKey::ReadMode( @_ ) };
|
||||||
|
if ( $@ ) {
|
||||||
|
return _ReadMode(@_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*GetTerminalSize = sub {
|
||||||
|
eval { return Term::ReadKey::GetTerminalSize( @_ ) };
|
||||||
|
if ( $@ ) {
|
||||||
|
return _GetTerminalSize(@_);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# If we don't have Term::ReadKey, fake it. We clobber our own glob,
|
# If we don't have Term::ReadKey, fake it. We clobber our own glob,
|
||||||
@@ -1466,11 +1460,8 @@ sub readkey {
|
|||||||
|
|
||||||
# As per perlfaq8:
|
# As per perlfaq8:
|
||||||
|
|
||||||
sub _GetTerminalSize {
|
BEGIN {
|
||||||
if ( @_ ) {
|
eval { no warnings; local $^W; require 'sys/ioctl.ph' };
|
||||||
die "My::Term::ReadKey doesn't implement GetTerminalSize with arguments";
|
|
||||||
}
|
|
||||||
eval { require 'sys/ioctl.ph' };
|
|
||||||
if ( !defined &TIOCGWINSZ ) {
|
if ( !defined &TIOCGWINSZ ) {
|
||||||
*TIOCGWINSZ = sub () {
|
*TIOCGWINSZ = sub () {
|
||||||
# Very few systems actually have ioctl.ph, thus it comes to this.
|
# Very few systems actually have ioctl.ph, thus it comes to this.
|
||||||
@@ -1481,17 +1472,41 @@ sub _GetTerminalSize {
|
|||||||
: 0x40087468;
|
: 0x40087468;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
open( TTY, "+<", "/dev/tty" ) or die "No tty: $OS_ERROR";
|
}
|
||||||
my $winsize = '';
|
|
||||||
unless ( ioctl( TTY, &TIOCGWINSZ, $winsize ) ) {
|
sub _GetTerminalSize {
|
||||||
die sprintf "$0: ioctl TIOCGWINSZ (%08x: $OS_ERROR)\n", &TIOCGWINSZ;
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
1;
|
1;
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
# End ReadKeyMini package
|
# End ReadKeyMini package
|
||||||
@@ -1535,7 +1550,8 @@ use List::Util qw( max first );
|
|||||||
|
|
||||||
use ReadKeyMini qw( GetTerminalSize );
|
use ReadKeyMini qw( GetTerminalSize );
|
||||||
|
|
||||||
my (undef, $max_lines) = GetTerminalSize;
|
my (undef, $max_lines) = GetTerminalSize();
|
||||||
|
$Diskstats::printed_lines = $max_lines;
|
||||||
|
|
||||||
my $diskstat_colno_for;
|
my $diskstat_colno_for;
|
||||||
BEGIN {
|
BEGIN {
|
||||||
@@ -1575,12 +1591,16 @@ sub new {
|
|||||||
my $columns = $o->get('columns-regex');
|
my $columns = $o->get('columns-regex');
|
||||||
my $devices = $o->get('devices-regex');
|
my $devices = $o->get('devices-regex');
|
||||||
|
|
||||||
|
my $headers = $o->get('headers');
|
||||||
|
|
||||||
my $self = {
|
my $self = {
|
||||||
filename => '/proc/diskstats',
|
filename => '/proc/diskstats',
|
||||||
block_size => 512,
|
block_size => 512,
|
||||||
show_inactive => $o->get('show-inactive'),
|
show_inactive => $o->get('show-inactive'),
|
||||||
sample_time => $o->get('sample-time') || 0,
|
sample_time => $o->get('sample-time') || 0,
|
||||||
automatic_headers => $o->get('automatic-headers'),
|
automatic_headers => $headers->{'scroll'},
|
||||||
|
space_samples => $headers->{'group'},
|
||||||
|
show_timestamps => $o->get('show-timestamps'),
|
||||||
columns_regex => qr/$columns/,
|
columns_regex => qr/$columns/,
|
||||||
devices_regex => $devices ? qr/$devices/ : undef,
|
devices_regex => $devices ? qr/$devices/ : undef,
|
||||||
interactive => 0,
|
interactive => 0,
|
||||||
@@ -1615,17 +1635,40 @@ sub new {
|
|||||||
_first_stats_for => {},
|
_first_stats_for => {},
|
||||||
_nochange_skips => [],
|
_nochange_skips => [],
|
||||||
|
|
||||||
|
_length_ts_column => 5,
|
||||||
|
|
||||||
_save_curr_as_prev => 1,
|
_save_curr_as_prev => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ( $self->{show_timestamps} ) {
|
||||||
|
$self->{_length_ts_column} = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Diskstats::last_was_header = 0;
|
||||||
|
|
||||||
return bless $self, $class;
|
return bless $self, $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub new_from_object {
|
|
||||||
my ($self, $class) = @_;
|
sub show_line_between_samples {
|
||||||
return bless $self, $class;
|
my ($self) = @_;
|
||||||
|
return $self->{space_samples};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub set_show_line_between_samples {
|
||||||
|
my ($self, $new_val) = @_;
|
||||||
|
return $self->{space_samples} = $new_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub show_timestamps {
|
||||||
|
my ($self) = @_;
|
||||||
|
return $self->{show_timestamps};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_show_timestamps {
|
||||||
|
my ($self, $new_val) = @_;
|
||||||
|
return $self->{show_timestamps} = $new_val;
|
||||||
|
}
|
||||||
|
|
||||||
sub active_device {
|
sub active_device {
|
||||||
my ( $self, $dev ) = @_;
|
my ( $self, $dev ) = @_;
|
||||||
@@ -1964,7 +2007,7 @@ sub design_print_formats {
|
|||||||
$dev_length ||= max 6, map length, $self->ordered_devs();
|
$dev_length ||= max 6, map length, $self->ordered_devs();
|
||||||
my ( $header, $format );
|
my ( $header, $format );
|
||||||
|
|
||||||
$header = $format = qq{%5s %-${dev_length}s };
|
$header = $format = qq{%+*s %-${dev_length}s };
|
||||||
|
|
||||||
if ( !$columns ) {
|
if ( !$columns ) {
|
||||||
@$columns = grep { $self->col_ok($_) } map { $_->[0] } @columns_in_order;
|
@$columns = grep { $self->col_ok($_) } map { $_->[0] } @columns_in_order;
|
||||||
@@ -2189,7 +2232,6 @@ sub _calc_misc_stats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$extra_stats{s_spent_doing_io} = $total_ms_spent_on_io / 1000;
|
$extra_stats{s_spent_doing_io} = $total_ms_spent_on_io / 1000;
|
||||||
|
|
||||||
$extra_stats{line_ts} = $self->compute_line_ts(
|
$extra_stats{line_ts} = $self->compute_line_ts(
|
||||||
first_ts => $self->first_ts(),
|
first_ts => $self->first_ts(),
|
||||||
curr_ts => $self->curr_ts(),
|
curr_ts => $self->curr_ts(),
|
||||||
@@ -2324,9 +2366,10 @@ sub force_print_header {
|
|||||||
sub print_header {
|
sub print_header {
|
||||||
my ($self, $header, @args) = @_;
|
my ($self, $header, @args) = @_;
|
||||||
if ( $self->force_header() ) {
|
if ( $self->force_header() ) {
|
||||||
printf $header . "\n", @args;
|
printf $header . "\n", $self->{_length_ts_column}, @args;
|
||||||
$Diskstats::printed_lines--;
|
$Diskstats::printed_lines--;
|
||||||
$Diskstats::printed_lines ||= $max_lines;
|
$Diskstats::printed_lines ||= $max_lines;
|
||||||
|
$Diskstats::last_was_header = 1;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2334,8 +2377,9 @@ sub print_header {
|
|||||||
sub print_rows {
|
sub print_rows {
|
||||||
my ($self, $format, $cols, $stat) = @_;
|
my ($self, $format, $cols, $stat) = @_;
|
||||||
|
|
||||||
printf $format . "\n", @{ $stat }{ qw( line_ts dev ), @$cols };
|
printf $format . "\n", $self->{_length_ts_column}, @{ $stat }{ qw( line_ts dev ), @$cols };
|
||||||
$Diskstats::printed_lines--;
|
$Diskstats::printed_lines--;
|
||||||
|
$Diskstats::last_was_header = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_deltas {
|
sub print_deltas {
|
||||||
@@ -2352,35 +2396,45 @@ sub print_deltas {
|
|||||||
|
|
||||||
my $header_method = $args{header_callback} || "print_header";
|
my $header_method = $args{header_callback} || "print_header";
|
||||||
my $rows_method = $args{rows_callback} || "print_rows";
|
my $rows_method = $args{rows_callback} || "print_rows";
|
||||||
|
|
||||||
$Diskstats::printed_lines ||= $max_lines;
|
|
||||||
|
|
||||||
$self->$header_method( $header, "#ts", "device" );
|
|
||||||
|
|
||||||
my @stats = $self->_calc_deltas();
|
my @stats = $self->_calc_deltas();
|
||||||
|
|
||||||
while ( my @stats_chunk = splice @stats, 0, $Diskstats::printed_lines ) {
|
if ( $self->{space_samples} && @stats && @stats > 1
|
||||||
foreach my $stat ( @stats_chunk ) {
|
&& !$Diskstats::last_was_header ) {
|
||||||
$self->$rows_method( $format, $cols, $stat );
|
print "\n";
|
||||||
}
|
$Diskstats::printed_lines--;
|
||||||
|
|
||||||
if ( $Diskstats::printed_lines == 0 ) {
|
|
||||||
$Diskstats::printed_lines ||= $max_lines;
|
|
||||||
|
|
||||||
if ( $self->automatic_headers()
|
|
||||||
&& !$self->isa("DiskstatsGroupByAll") )
|
|
||||||
{
|
|
||||||
$self->force_print_header( $header, "#ts", "device" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $self->automatic_headers()
|
||||||
|
&& $Diskstats::printed_lines <= @stats
|
||||||
|
&& !$self->isa("DiskstatsGroupByAll") ) {
|
||||||
|
$self->force_print_header( $header, "#ts", "device" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->$header_method( $header, "#ts", "device" );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $stat ( @stats ) {
|
||||||
|
$self->$rows_method( $format, $cols, $stat );
|
||||||
|
}
|
||||||
|
$Diskstats::printed_lines = $Diskstats::printed_lines <= 0
|
||||||
|
? $max_lines
|
||||||
|
: $Diskstats::printed_lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub compute_line_ts {
|
sub compute_line_ts {
|
||||||
my ( $self, %args ) = @_;
|
my ( $self, %args ) = @_;
|
||||||
return sprintf( "%5.1f", $args{first_ts} > 0
|
my $line_ts;
|
||||||
? $args{curr_ts} - $args{first_ts}
|
if ( $self->show_timestamps() ) {
|
||||||
: 0 );
|
$line_ts = scalar localtime($args{curr_ts});
|
||||||
|
$line_ts =~ s/.*(\d\d:\d\d:\d\d).*/$1/;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$line_ts = sprintf( "%5.1f", $args{first_ts} > 0
|
||||||
|
? $args{curr_ts} - $args{first_ts}
|
||||||
|
: 0 );
|
||||||
|
}
|
||||||
|
return $line_ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub compute_in_progress {
|
sub compute_in_progress {
|
||||||
@@ -2618,7 +2672,12 @@ sub clear_state {
|
|||||||
|
|
||||||
sub compute_line_ts {
|
sub compute_line_ts {
|
||||||
my ($self, %args) = @_;
|
my ($self, %args) = @_;
|
||||||
return "{" . ($self->{_iterations} - 1) . "}";
|
if ( $self->show_timestamps() ) {
|
||||||
|
return $self->SUPER::compute_line_ts(%args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "{" . ($self->{_iterations} - 1) . "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub delta_against {
|
sub delta_against {
|
||||||
@@ -2837,6 +2896,14 @@ sub _calc_stats_for_deltas {
|
|||||||
return \%stats;
|
return \%stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub compute_line_ts {
|
||||||
|
my ($self, %args) = @_;
|
||||||
|
if ( $self->show_timestamps() ) {
|
||||||
|
@args{ qw( first_ts curr_ts ) } = @args{ qw( curr_ts first_ts ) }
|
||||||
|
}
|
||||||
|
return $self->SUPER::compute_line_ts(%args);
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
}
|
}
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
@@ -3124,8 +3191,8 @@ sub gather_samples {
|
|||||||
$sample_interval);
|
$sample_interval);
|
||||||
PTDEBUG && _d("Child: Starting at [$time] "
|
PTDEBUG && _d("Child: Starting at [$time] "
|
||||||
. ($sleep < ($sample_interval * 0.2) ? '' : 'not ')
|
. ($sleep < ($sample_interval * 0.2) ? '' : 'not ')
|
||||||
. "going to sleep [$sleep]");
|
. "going to sleep");
|
||||||
Time::HiRes::sleep($sleep) if $sleep < ($sample_interval * 0.8);
|
Time::HiRes::sleep($sleep) if $sleep < ($sample_interval * 0.2);
|
||||||
|
|
||||||
open my $diskstats_fh, "<", "/proc/diskstats"
|
open my $diskstats_fh, "<", "/proc/diskstats"
|
||||||
or die "Cannot open /proc/diskstats: $OS_ERROR";
|
or die "Cannot open /proc/diskstats: $OS_ERROR";
|
||||||
@@ -3196,10 +3263,19 @@ sub group_by {
|
|||||||
|
|
||||||
if ( ref( $o->get("current_group_by_obj") ) ne $input_to_object{$input} ) {
|
if ( ref( $o->get("current_group_by_obj") ) ne $input_to_object{$input} ) {
|
||||||
$o->set("current_group_by_obj", undef);
|
$o->set("current_group_by_obj", undef);
|
||||||
$o->set( "current_group_by_obj", $input_to_object{$input}->new(OptionParser=>$o, interactive => 1) );
|
my $new_obj = $input_to_object{$input}->new(OptionParser=>$o, interactive => 1);
|
||||||
if ( !$args{redraw_all} ) {
|
$o->set( "current_group_by_obj", $new_obj );
|
||||||
print_header(%args);
|
|
||||||
}
|
$new_obj->{_stats_for} = $old_obj->{_stats_for};
|
||||||
|
$new_obj->set_curr_ts($old_obj->curr_ts());
|
||||||
|
|
||||||
|
$new_obj->{_prev_stats_for} = $old_obj->{_prev_stats_for};
|
||||||
|
$new_obj->set_prev_ts($old_obj->prev_ts());
|
||||||
|
|
||||||
|
$new_obj->{_first_stats_for} = $old_obj->{_first_stats_for};
|
||||||
|
$new_obj->set_first_ts($old_obj->first_ts());
|
||||||
|
|
||||||
|
print_header(%args) unless $args{redraw_all};
|
||||||
}
|
}
|
||||||
|
|
||||||
for my $obj ( $o->get("current_group_by_obj") ) {
|
for my $obj ( $o->get("current_group_by_obj") ) {
|
||||||
@@ -3693,6 +3769,16 @@ one disk device. In B<sample> mode, each line of output shows one sample of
|
|||||||
statistics. In B<all> mode, each line of output shows one sample and one disk
|
statistics. In B<all> mode, each line of output shows one sample and one disk
|
||||||
device.
|
device.
|
||||||
|
|
||||||
|
=item --headers
|
||||||
|
|
||||||
|
type: Hash; default: group,scroll
|
||||||
|
|
||||||
|
If 'group' is present, a blank line will be printed sepparating samples,
|
||||||
|
as long as there is more than one sample to show.
|
||||||
|
If 'scroll' is present, the tool will print the headers as often as needed
|
||||||
|
to prevent them from scrolling out of view; Note that you can press the
|
||||||
|
space bar, or the enter key, to reprint headers at will.
|
||||||
|
|
||||||
=item --sample-time
|
=item --sample-time
|
||||||
|
|
||||||
type: int; default: 1
|
type: int; default: 1
|
||||||
@@ -3722,12 +3808,9 @@ Also, how often the tool should sample /proc/diskstats.
|
|||||||
|
|
||||||
Show inactive devices.
|
Show inactive devices.
|
||||||
|
|
||||||
=item --[no]automatic-headers
|
=item --show-timestamps
|
||||||
|
|
||||||
default: yes
|
Show a timestamp in the form of 'HH:MM:SS' as the #ts line.
|
||||||
|
|
||||||
Print the headers as often as needed to prevent them from scrolling out of view.
|
|
||||||
You can press the space bar to reprint headers at will.
|
|
||||||
|
|
||||||
=item --help
|
=item --help
|
||||||
|
|
||||||
|
111
lib/Diskstats.pm
111
lib/Diskstats.pm
@@ -34,7 +34,8 @@ use List::Util qw( max first );
|
|||||||
|
|
||||||
use ReadKeyMini qw( GetTerminalSize );
|
use ReadKeyMini qw( GetTerminalSize );
|
||||||
|
|
||||||
my (undef, $max_lines) = GetTerminalSize;
|
my (undef, $max_lines) = GetTerminalSize();
|
||||||
|
$Diskstats::printed_lines = $max_lines;
|
||||||
|
|
||||||
my $diskstat_colno_for;
|
my $diskstat_colno_for;
|
||||||
BEGIN {
|
BEGIN {
|
||||||
@@ -77,13 +78,18 @@ sub new {
|
|||||||
my $columns = $o->get('columns-regex');
|
my $columns = $o->get('columns-regex');
|
||||||
my $devices = $o->get('devices-regex');
|
my $devices = $o->get('devices-regex');
|
||||||
|
|
||||||
|
# Header magic and so on.
|
||||||
|
my $headers = $o->get('headers');
|
||||||
|
|
||||||
my $self = {
|
my $self = {
|
||||||
# Defaults
|
# Defaults
|
||||||
filename => '/proc/diskstats',
|
filename => '/proc/diskstats',
|
||||||
block_size => 512,
|
block_size => 512,
|
||||||
show_inactive => $o->get('show-inactive'),
|
show_inactive => $o->get('show-inactive'),
|
||||||
sample_time => $o->get('sample-time') || 0,
|
sample_time => $o->get('sample-time') || 0,
|
||||||
automatic_headers => $o->get('automatic-headers'),
|
automatic_headers => $headers->{'scroll'},
|
||||||
|
space_samples => $headers->{'group'},
|
||||||
|
show_timestamps => $o->get('show-timestamps'),
|
||||||
columns_regex => qr/$columns/,
|
columns_regex => qr/$columns/,
|
||||||
devices_regex => $devices ? qr/$devices/ : undef,
|
devices_regex => $devices ? qr/$devices/ : undef,
|
||||||
interactive => 0,
|
interactive => 0,
|
||||||
@@ -118,20 +124,43 @@ sub new {
|
|||||||
_first_stats_for => {},
|
_first_stats_for => {},
|
||||||
_nochange_skips => [],
|
_nochange_skips => [],
|
||||||
|
|
||||||
|
_length_ts_column => 5,
|
||||||
|
|
||||||
# Internal for now, but might need APIfying.
|
# Internal for now, but might need APIfying.
|
||||||
_save_curr_as_prev => 1,
|
_save_curr_as_prev => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return bless $self, $class;
|
if ( $self->{show_timestamps} ) {
|
||||||
}
|
$self->{_length_ts_column} = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Diskstats::last_was_header = 0;
|
||||||
|
|
||||||
sub new_from_object {
|
|
||||||
my ($self, $class) = @_;
|
|
||||||
return bless $self, $class;
|
return bless $self, $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
# The next lot are accessors, plus some convenience functions.
|
# The next lot are accessors, plus some convenience functions.
|
||||||
|
|
||||||
|
sub show_line_between_samples {
|
||||||
|
my ($self) = @_;
|
||||||
|
return $self->{space_samples};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_show_line_between_samples {
|
||||||
|
my ($self, $new_val) = @_;
|
||||||
|
return $self->{space_samples} = $new_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub show_timestamps {
|
||||||
|
my ($self) = @_;
|
||||||
|
return $self->{show_timestamps};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_show_timestamps {
|
||||||
|
my ($self, $new_val) = @_;
|
||||||
|
return $self->{show_timestamps} = $new_val;
|
||||||
|
}
|
||||||
|
|
||||||
sub active_device {
|
sub active_device {
|
||||||
my ( $self, $dev ) = @_;
|
my ( $self, $dev ) = @_;
|
||||||
return $self->{_active_devices}->{$dev};
|
return $self->{_active_devices}->{$dev};
|
||||||
@@ -490,7 +519,7 @@ sub design_print_formats {
|
|||||||
|
|
||||||
# For each device, print out the following: The timestamp offset and
|
# For each device, print out the following: The timestamp offset and
|
||||||
# device name.
|
# device name.
|
||||||
$header = $format = qq{%5s %-${dev_length}s };
|
$header = $format = qq{%+*s %-${dev_length}s };
|
||||||
|
|
||||||
if ( !$columns ) {
|
if ( !$columns ) {
|
||||||
@$columns = grep { $self->col_ok($_) } map { $_->[0] } @columns_in_order;
|
@$columns = grep { $self->col_ok($_) } map { $_->[0] } @columns_in_order;
|
||||||
@@ -758,7 +787,6 @@ sub _calc_misc_stats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$extra_stats{s_spent_doing_io} = $total_ms_spent_on_io / 1000;
|
$extra_stats{s_spent_doing_io} = $total_ms_spent_on_io / 1000;
|
||||||
|
|
||||||
$extra_stats{line_ts} = $self->compute_line_ts(
|
$extra_stats{line_ts} = $self->compute_line_ts(
|
||||||
first_ts => $self->first_ts(),
|
first_ts => $self->first_ts(),
|
||||||
curr_ts => $self->curr_ts(),
|
curr_ts => $self->curr_ts(),
|
||||||
@@ -913,9 +941,10 @@ sub force_print_header {
|
|||||||
sub print_header {
|
sub print_header {
|
||||||
my ($self, $header, @args) = @_;
|
my ($self, $header, @args) = @_;
|
||||||
if ( $self->force_header() ) {
|
if ( $self->force_header() ) {
|
||||||
printf $header . "\n", @args;
|
printf $header . "\n", $self->{_length_ts_column}, @args;
|
||||||
$Diskstats::printed_lines--;
|
$Diskstats::printed_lines--;
|
||||||
$Diskstats::printed_lines ||= $max_lines;
|
$Diskstats::printed_lines ||= $max_lines;
|
||||||
|
$Diskstats::last_was_header = 1;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -923,8 +952,9 @@ sub print_header {
|
|||||||
sub print_rows {
|
sub print_rows {
|
||||||
my ($self, $format, $cols, $stat) = @_;
|
my ($self, $format, $cols, $stat) = @_;
|
||||||
|
|
||||||
printf $format . "\n", @{ $stat }{ qw( line_ts dev ), @$cols };
|
printf $format . "\n", $self->{_length_ts_column}, @{ $stat }{ qw( line_ts dev ), @$cols };
|
||||||
$Diskstats::printed_lines--;
|
$Diskstats::printed_lines--;
|
||||||
|
$Diskstats::last_was_header = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_deltas {
|
sub print_deltas {
|
||||||
@@ -942,41 +972,48 @@ sub print_deltas {
|
|||||||
|
|
||||||
my $header_method = $args{header_callback} || "print_header";
|
my $header_method = $args{header_callback} || "print_header";
|
||||||
my $rows_method = $args{rows_callback} || "print_rows";
|
my $rows_method = $args{rows_callback} || "print_rows";
|
||||||
|
|
||||||
$Diskstats::printed_lines ||= $max_lines;
|
|
||||||
|
|
||||||
$self->$header_method( $header, "#ts", "device" );
|
|
||||||
|
|
||||||
my @stats = $self->_calc_deltas();
|
my @stats = $self->_calc_deltas();
|
||||||
|
|
||||||
# Split the stats in chunks no greater than how many lines
|
if ( $self->{space_samples} && @stats && @stats > 1
|
||||||
# we have left until printing the next header.
|
&& !$Diskstats::last_was_header ) {
|
||||||
while ( my @stats_chunk = splice @stats, 0, $Diskstats::printed_lines ) {
|
# Print an empty line before the rows if we have more
|
||||||
# Print the stats
|
# than one thing to print.
|
||||||
foreach my $stat ( @stats_chunk ) {
|
print "\n";
|
||||||
$self->$rows_method( $format, $cols, $stat );
|
$Diskstats::printed_lines--;
|
||||||
}
|
|
||||||
|
|
||||||
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") )
|
|
||||||
{
|
|
||||||
$self->force_print_header( $header, "#ts", "device" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $self->automatic_headers()
|
||||||
|
&& $Diskstats::printed_lines <= @stats
|
||||||
|
&& !$self->isa("DiskstatsGroupByAll") ) {
|
||||||
|
$self->force_print_header( $header, "#ts", "device" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->$header_method( $header, "#ts", "device" );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print all of the rows
|
||||||
|
foreach my $stat ( @stats ) {
|
||||||
|
$self->$rows_method( $format, $cols, $stat );
|
||||||
|
}
|
||||||
|
$Diskstats::printed_lines = $Diskstats::printed_lines <= 0
|
||||||
|
? $max_lines
|
||||||
|
: $Diskstats::printed_lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub compute_line_ts {
|
sub compute_line_ts {
|
||||||
my ( $self, %args ) = @_;
|
my ( $self, %args ) = @_;
|
||||||
return sprintf( "%5.1f", $args{first_ts} > 0
|
my $line_ts;
|
||||||
? $args{curr_ts} - $args{first_ts}
|
if ( $self->show_timestamps() ) {
|
||||||
: 0 );
|
$line_ts = scalar localtime($args{curr_ts});
|
||||||
|
$line_ts =~ s/.*(\d\d:\d\d:\d\d).*/$1/;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$line_ts = sprintf( "%5.1f", $args{first_ts} > 0
|
||||||
|
? $args{curr_ts} - $args{first_ts}
|
||||||
|
: 0 );
|
||||||
|
}
|
||||||
|
return $line_ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub compute_in_progress {
|
sub compute_in_progress {
|
||||||
|
@@ -118,7 +118,12 @@ sub clear_state {
|
|||||||
|
|
||||||
sub compute_line_ts {
|
sub compute_line_ts {
|
||||||
my ($self, %args) = @_;
|
my ($self, %args) = @_;
|
||||||
return "{" . ($self->{_iterations} - 1) . "}";
|
if ( $self->show_timestamps() ) {
|
||||||
|
return $self->SUPER::compute_line_ts(%args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "{" . ($self->{_iterations} - 1) . "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub delta_against {
|
sub delta_against {
|
||||||
|
@@ -203,6 +203,14 @@ sub _calc_stats_for_deltas {
|
|||||||
return \%stats;
|
return \%stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub compute_line_ts {
|
||||||
|
my ($self, %args) = @_;
|
||||||
|
if ( $self->show_timestamps() ) {
|
||||||
|
@args{ qw( first_ts curr_ts ) } = @args{ qw( curr_ts first_ts ) }
|
||||||
|
}
|
||||||
|
return $self->SUPER::compute_line_ts(%args);
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -386,11 +386,25 @@ sub group_by {
|
|||||||
# object's ->new being smart about discarding unrecognized
|
# object's ->new being smart about discarding unrecognized
|
||||||
# values.
|
# values.
|
||||||
$o->set("current_group_by_obj", undef);
|
$o->set("current_group_by_obj", undef);
|
||||||
#my $new_obj = $old_obj->new_from_object($input_to_object{$input});
|
my $new_obj = $input_to_object{$input}->new(OptionParser=>$o, interactive => 1);
|
||||||
$o->set( "current_group_by_obj", $input_to_object{$input}->new(OptionParser=>$o, interactive => 1) );
|
$o->set( "current_group_by_obj", $new_obj );
|
||||||
if ( !$args{redraw_all} ) {
|
|
||||||
print_header(%args);
|
# Data shared between all the objects.
|
||||||
}
|
# Current
|
||||||
|
$new_obj->{_stats_for} = $old_obj->{_stats_for};
|
||||||
|
$new_obj->set_curr_ts($old_obj->curr_ts());
|
||||||
|
|
||||||
|
# Previous
|
||||||
|
$new_obj->{_prev_stats_for} = $old_obj->{_prev_stats_for};
|
||||||
|
$new_obj->set_prev_ts($old_obj->prev_ts());
|
||||||
|
|
||||||
|
# First
|
||||||
|
$new_obj->{_first_stats_for} = $old_obj->{_first_stats_for};
|
||||||
|
$new_obj->set_first_ts($old_obj->first_ts());
|
||||||
|
|
||||||
|
# If we can't redraw the entire file, because there isn't a file,
|
||||||
|
# just settle for reprinting the header.
|
||||||
|
print_header(%args) unless $args{redraw_all};
|
||||||
}
|
}
|
||||||
|
|
||||||
# Just aliasing this for a bit.
|
# Just aliasing this for a bit.
|
||||||
|
@@ -48,7 +48,18 @@ BEGIN {
|
|||||||
my $have_readkey = eval { require Term::ReadKey };
|
my $have_readkey = eval { require Term::ReadKey };
|
||||||
|
|
||||||
if ($have_readkey) {
|
if ($have_readkey) {
|
||||||
Term::ReadKey->import(@EXPORT_OK);
|
*ReadMode = sub {
|
||||||
|
eval { return Term::ReadKey::ReadMode( @_ ) };
|
||||||
|
if ( $@ ) {
|
||||||
|
return _ReadMode(@_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*GetTerminalSize = sub {
|
||||||
|
eval { return Term::ReadKey::GetTerminalSize( @_ ) };
|
||||||
|
if ( $@ ) {
|
||||||
|
return _GetTerminalSize(@_);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# If we don't have Term::ReadKey, fake it. We clobber our own glob,
|
# If we don't have Term::ReadKey, fake it. We clobber our own glob,
|
||||||
|
@@ -172,13 +172,13 @@ $obj->set_columns_regex(qr/cnc|rt|busy|prg|[mk]b|[dr]_s|mrg/);
|
|||||||
($header, $rows, $cols) = $obj->design_print_formats();
|
($header, $rows, $cols) = $obj->design_print_formats();
|
||||||
is(
|
is(
|
||||||
$header,
|
$header,
|
||||||
join(" ", q{%5s %-6s}, grep { $_ =~ $obj->columns_regex() } map { $_->[0] } @columns_in_order),
|
join(" ", q{%+*s %-6s}, grep { $_ =~ $obj->columns_regex() } map { $_->[0] } @columns_in_order),
|
||||||
"design_print_formats: sanity check for defaults"
|
"design_print_formats: sanity check for defaults"
|
||||||
);
|
);
|
||||||
|
|
||||||
$obj->set_columns_regex(qr/./);
|
$obj->set_columns_regex(qr/./);
|
||||||
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
||||||
my $all_columns_format = join(" ", q{%5s %-10s}, map { $_->[0] } @columns_in_order);
|
my $all_columns_format = join(" ", q{%+*s %-10s}, map { $_->[0] } @columns_in_order);
|
||||||
is(
|
is(
|
||||||
$header,
|
$header,
|
||||||
$all_columns_format,
|
$all_columns_format,
|
||||||
@@ -189,7 +189,7 @@ $obj->set_columns_regex(qr/(?!)/); # Will never match
|
|||||||
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
||||||
is(
|
is(
|
||||||
$header,
|
$header,
|
||||||
q{%5s %-10s },
|
q{%+*s %-10s },
|
||||||
"design_print_formats respects columns_regex"
|
"design_print_formats respects columns_regex"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ $obj->set_columns_regex(qr/./);
|
|||||||
);
|
);
|
||||||
is(
|
is(
|
||||||
$header,
|
$header,
|
||||||
q{%5s %-10s },
|
q{%+*s %-10s },
|
||||||
"...unless we pass an explicit column array"
|
"...unless we pass an explicit column array"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ $obj->set_columns_regex(qr/./);
|
|||||||
);
|
);
|
||||||
is(
|
is(
|
||||||
$header,
|
$header,
|
||||||
q{%5s %-10s busy},
|
q{%+*s %-10s busy},
|
||||||
"Header"
|
"Header"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ for my $method ( qw( delta_against delta_against_ts group_by ) ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is(
|
is(
|
||||||
Diskstats->compute_line_ts( first_ts => 0 ),
|
$obj->compute_line_ts( first_ts => 0 ),
|
||||||
sprintf( "%5.1f", 0 ),
|
sprintf( "%5.1f", 0 ),
|
||||||
"compute_line_ts has a sane default",
|
"compute_line_ts has a sane default",
|
||||||
);
|
);
|
||||||
@@ -474,7 +474,9 @@ for my $test (
|
|||||||
|
|
||||||
$obj->set_columns_regex(qr/ \A (?!.*io_s$|\s*[qs]time$) /x);
|
$obj->set_columns_regex(qr/ \A (?!.*io_s$|\s*[qs]time$) /x);
|
||||||
$obj->set_show_inactive(1);
|
$obj->set_show_inactive(1);
|
||||||
|
$obj->set_show_timestamps(0);
|
||||||
$obj->set_automatic_headers(0);
|
$obj->set_automatic_headers(0);
|
||||||
|
$obj->set_show_line_between_samples(0);
|
||||||
|
|
||||||
for my $filename ( map "diskstats-00$_.txt", 1..5 ) {
|
for my $filename ( map "diskstats-00$_.txt", 1..5 ) {
|
||||||
my $file = File::Spec->catfile( "t", "pt-diskstats", "samples", $filename );
|
my $file = File::Spec->catfile( "t", "pt-diskstats", "samples", $filename );
|
||||||
|
@@ -79,7 +79,7 @@ sub test_diskstats_file {
|
|||||||
? @{ $args{options} }
|
? @{ $args{options} }
|
||||||
: (
|
: (
|
||||||
'--show-inactive',
|
'--show-inactive',
|
||||||
'--no-automatic-headers',
|
'--headers', '',
|
||||||
'--columns-regex','cnc|rt|mb|busy|prg',
|
'--columns-regex','cnc|rt|mb|busy|prg',
|
||||||
);
|
);
|
||||||
die "$file does not exist" unless -f $file;
|
die "$file does not exist" unless -f $file;
|
||||||
@@ -117,7 +117,7 @@ test_diskstats_file(
|
|||||||
|
|
||||||
test_diskstats_file(
|
test_diskstats_file(
|
||||||
file => "small.txt",
|
file => "small.txt",
|
||||||
options => [ '--no-automatic-headers', '--columns-regex','time', ],
|
options => [ '--headers', '', '--columns-regex','time', ],
|
||||||
);
|
);
|
||||||
|
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
@@ -145,9 +145,8 @@ while (my $line = <$samples_fh>) {
|
|||||||
$count++ if $line =~ /^TS/;
|
$count++ if $line =~ /^TS/;
|
||||||
}
|
}
|
||||||
|
|
||||||
is(
|
ok(
|
||||||
$count,
|
($count == $iterations) || ($count == $iterations+1),
|
||||||
$iterations,
|
|
||||||
"--save-samples and --iterations work"
|
"--save-samples and --iterations work"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user