Mini Shell
Direktori : /opt/sharedrads/ |
|
Current File : //opt/sharedrads/recent-cp |
#!/usr/bin/perl
# encoding: utf-8
#
# author: Kyle Yetter <kyley@inmotionhosting.com>
# date: November 16, 2011
#
# TODO: Comment/document code more cleanly
=pod
=head1 recent-cp
recent-cp - Summarize cpu usage over the past hour
=head1 SYNOPSIS
recent-cp [-1|-5|-15|-60] [userna5]
=head1 DESCRIPTION
Summarize cpu usage over the past hour.
=head1 OPTIONS
=over 8
=item B<-1>, B<-5>, B<-15>, B<-60>
Extract the top users during the last 1 minute, 5 minutes, 15 minutes, or 60 minutes (respectively).
=item B<-n> I<COUNT>, B<--top>=I<COUNT>
Limit the results to the top I<COUNT> users/commands (Default: 10)
=item B<-p>, B<--percentages>
Show values only as percentages of the total for each interval
=item B<-s>, B<--secs>
Show only the cpu second counts for each interval and do not show the percentage of the total.
=item B<-o> I<HOURS>, B<--hours-ago>=I<HOURS>
Run the sampling starting I<HOURS> ago instead of right now. This can be a decimal number (e.g. 0.5 for a half hour ago).
=item B<-b>, B<--bleach>
Do not use ANSI color code escapes in the output.
=item B<--man>
Show full man page documentation for this program.
=item B<-h>, B<--help>
You know the drill
=item B<-v>, B<--version>
Ditto
=back
=head1 EXAMPLES
=head2 Getting Recent Top CPU Users
$ recent-cp
+----------+-----------+-----------+-----------+-----------+
| user | 1m | 5m | 15m | 60m |
+----------+-----------+-----------+-----------+-----------+
| p2fitn5 | 0.36s | 3.49s | 11.22s | 47.85s |
| gomanu5 | 0.00s | 3.84s | 19.60s | 55.53s |
| chilet5 | 0.76s | 4.45s | 12.37s | 43.86s |
| coloni6 | 0.43s | 4.79s | 22.77s | 186.82s |
| camera11 | 0.91s | 5.86s | 5.86s | 70.36s |
| staysu5 | 0.00s | 8.98s | 15.52s | 15.83s |
| battle8 | 1.12s | 10.68s | 17.18s | 46.07s |
| aztecs6 | 1.25s | 12.33s | 36.80s | 172.05s |
| root | 1.17s | 34.56s | 68.39s | 1004.76s |
| phenom5 | 0.00s | 45.20s | 52.23s | 104.42s |
| ohhowp5 | 6.78s | 81.60s | 190.70s | 719.87s |
+----------+-----------+-----------+-----------+-----------+
=head2 Getting CPU Usage By Command Name For a User
$ recent-cp root
+-----------------+-----------+-----------+-----------+-----------+
| command | 1m | 5m | 15m | 60m |
+-----------------+-----------+-----------+-----------+-----------+
| top | 0.00s | 0.11s | 0.34s | 1.40s |
| pgrep | 0.03s | 0.13s | 0.39s | 1.54s |
| dcpumon | 0.00s | 0.38s | 1.26s | 5.19s |
| ps | 0.00s | 0.49s | 1.71s | 7.96s |
| ls | 0.14s | 0.69s | 2.09s | 9.08s |
| python | 0.14s | 0.90s | 2.76s | 11.35s |
| couriertls | 0.07s | 1.11s | 3.91s | 18.62s |
| sa | 0.52s | 3.18s | 8.50s | 40.47s |
| ftp_clamscan.ph | 0.12s | 10.06s | 29.72s | 104.83s |
| lastcomm | 0.00s | 10.76s | 10.76s | 20.93s |
| perl-bin | 0.01s | 16.81s | 21.80s | 785.29s |
+-----------------+-----------+-----------+-----------+-----------+
=cut
use Getopt::Long;
use Date::Parse;
use Term::ANSIColor;
use Pod::Usage;
use POSIX qw( strftime );
use constant (
MAX_OFF_HITS => 200
);
our $VERSION = 1.1;
# the time at the start of execution of the script
our $now = time;
# will contain a username if the command distribution of the user is requested
our $target_user = undef;
# seconds ago in the past at which we will start the one hour window
our $offset = 0;
our $bleach = !-t STDOUT;
# the characters of the progress bar in sequence
our @progress_glyphs = qw( | / - \\ | / - \\ );
# tracks the current progress character to display
our $progress_glyph = 0;
# update the display every 200 steps
our $progress_frequency = 1000;
# increments each update and refreshes the display when @progress_frequency is reached
our $progress_steps = 0;
# the number of top users / commands to display in the output table
our $number_of_entries = 10;
# display percentage of CPU for each interval
our $show_percentage = 1;
# display cpu seconds for each interval
our $show_secs = 1;
# tracks the user/command totals over the last one minute
our %last_1;
# tracks the user/command totals over the last five minutes
our %last_5;
# tracks the user/command totals over the last fifteen minutes
our %last_15;
# tracks the user/command totals over the last hour
our %last_60;
our @maps = ( \%last_1, \%last_5, \%last_15, \%last_60 );
# the number of the data set to use to determine the top users to display
# five minutes by default
our $sort_index = 1;
our $lastcomm_options = '';
our %special_users = (
root => 1,
nobody => 1,
mailnull => 1,
mysql => 1
);
####################################################################################################
################################## Parse Options / Setup Variables #################################
####################################################################################################
GetOptions(
'1' => sub { $sort_index = 0; },
'5' => sub { $sort_index = 1; },
'15' => sub { $sort_index = 2; },
'60' => sub { $sort_index = 3; },
'n|top=i' => \$number_of_entries,
'h|help' => sub { pod2usage( 0 ); },
'man' => sub {
pod2usage({
-exitval => 0,
-verbose => 2,
-noperldoc => 1
});
},
'b|bleach' => \$bleach,
'p|percentages' => sub {
$show_percentage = 1;
$show_secs = 0;
},
's|secs' => sub {
$show_percentage = 0;
$show_secs = 1;
},
'o|hours-ago=f' => \$offset,
'v|version' => sub {
print( "$VERSION\n" );
exit( 0 );
}
);
$offset *= 3600;
our $thresh_1 = $now - 60 - $offset;
our $thresh_5 = $now - 300 - $offset;
our $thresh_15 = $now - 900 - $offset;
our $thresh_60 = $now - 3600 - $offset;
our $start = $now - $offset;
#
# contains the daily summary details extract from sa
#
our %sa_data;
sub window_size {
my @size = ( $ENV{'COLUMNS'} || 80, $ENV{'LINES'} || 22 );
my $tiocgwinsz = 0x5413;
eval {
my $data = '';
if ( ioctl( STDERR, $tiocgwinsz, $data ) >= 0 ) {
my ( $height, $width ) = unpack( "SSSS", $data );
$size[ 1 ] = $height if $height >= 0;
$size[ 0 ] = $width if $width >= 0;
}
};
return @size;
}
our ( $screen_width, undef ) = window_size();
sub shell_escape( $ ) {
my $token = shift;
if ( length( $token ) == 0 ) { return "''"; }
$token =~ s/([^A-Za-z0-9_\-\.,:\/@\n])/\\$1/g;
$token =~ s/\n/'\n'/g;
return $token;
}
sub c($$) {
my ( $str, $style ) = @_;
$str = colored( $str, $style ) unless $bleach;
return $str;
}
sub bleach($) {
my ( $colored ) = @_;
$colored =~ s(\033\[.*?m)()g;
return $colored;
}
sub clen($) {
return length( bleach( $_[ 0 ] ) );
}
sub ljust($$) {
my ( $string, $width ) = @_;
my $len = clen( $string );
return $string if $width <= $len;
my $padding = $width - $len;
return( $string . ( ' ' x $padding ) );
}
sub center($$) {
my ( $string, $width ) = @_;
my $len = clen( $string );
return $string if $width <= $len;
my $padding = $width - $len;
my $left = int( $padding / 2 );
my $right = $left + $padding % 2;
return( ( ' ' x $left ) . $string . ( ' ' x $right ) );
}
sub print_table {
local $\ = "\n";
my @rows = @_;
my @arrays;
my @titles;
my @header_row;
my $title = undef;
for my $item ( @rows ) {
if ( ref( $item ) eq 'ARRAY' ) {
push( @arrays, $item );
} elsif ( ref( \$item ) eq "SCALAR" ) {
push( @titles, $item );
}
}
unless ( @arrays ) {
$@ = "print_table: no rows provided";
return;
}
my $ncols = scalar( @{$arrays[ 0 ]} );
my @widths;
for my $c ( 0 .. ( $ncols - 1 ) ) {
my $w = 0;
for my $row ( @arrays ) {
my $col = $row->[$c];
my $col_width = clen( $col );
if ( $w < $col_width ) { $w = $col_width; }
}
$widths[ $c ] = $w;
}
my $inner_width = ( $ncols - 1 ) * 3;
$inner_width += $_ for @widths;
if ( @titles ) {
my $title_width = 0;
for my $t ( @titles ) {
my $l = clen( $t );
$title_width = $l if $l > $title_width;
}
if ( $title_width > $inner_width ) {
$widths[-1] += $title_width - $inner_width;
$inner_width = $title_width;
}
}
if ( ref( \$rows[0] ) eq "SCALAR" ) {
$title = shift( @rows );
print( '+-' . ( '-' x $inner_width ) . '-+' );
print( '| ' . center( $title, $inner_width ) . ' |' );
}
my $mask = '| ' . join( " | ", map { "%-${_}s" } @widths ) . " |";
my $border = '+-' . join( "-+-", map { '-' x $_ } @widths ) . "-+";
my $outline = '+-' . ( '-' x $inner_width ) . '-+';
my $after_title = 1;
print( $border );
my @title_row = @{ shift( @rows ) };
print( '| ' . join( ' | ', map { center( $title_row[ $_ ], $widths[ $_ ] ) } ( 0 ... $#title_row ) ) . ' |' );
print( $border );
$after_title = 0;
for my $row ( @rows ) {
if ( ref( $row ) eq 'ARRAY' ) {
print( $border ) if $after_title;
print( '| ' . ( join( ' | ', map { ljust( $row->[ $_ ], $widths[ $_ ] ); } (0 ... $#widths) ) ) . ' |' );
#print( sprintf( $mask, @{ $row } ) );
$after_title = 0;
} elsif ( ref( \$row ) eq 'SCALAR' ) {
if ( $row eq '-' ) {
print( $after_title ? $outline : $border );
$after_title = 0;
} else {
print( $after_title ? $outline : $border );
print( '| ' . center( $row, $inner_width ) . ' |' );
$after_title = 1;
}
}
}
print( $after_title ? $outline : $border );
}
sub update_progress( $ ) {
local $| = 1;
if ( $progress_steps == 0 ) {
my $label = shift;
my $line = sprintf( "%s %s", $progress_glyphs[ $progress_glyph ], $label );
my $n = length( $line );
if ( $n > $screen_width ) {
$line = substr( $line, 0, $screen_width - 4 ) . '...';
}
print STDERR "\r\e[2K"; # clears the line
print STDERR $line;
}
$progress_steps = ( $progress_steps + 1 ) % $progress_frequency;
$progress_glyph = ( $progress_glyph + 1 ) % $#progress_glyphs;
}
####################################################################################################
########################################### Script Action ##########################################
####################################################################################################
if ( $target_user = shift( @ARGV ) ) {
$lastcomm_options = "--user " . shell_escape( $target_user );
}
#
# [1] Pull the lastcomm output for the timeframe of interest. Stop and close the pipe
# once it seems that we have confidently left the timeframe, i.e. when $off_hits > 200.
#
open( LC, "lastcomm $lastcomm_options |" );
my $off_hits = 0;
while ( <LC> ) {
chomp;
update_progress( $_ );
if ( /^\s*(\S+).*?([\w\-]+)\s+\S+\s+(\d+(?:\.\d+)?)\s+secs\s+(.+)$/ ) {
my ( $name, $user, $secs, $time ) = ( $1, $2, $3, str2time( $4 ) );
if ( $time > $start ) { next; }
if ( $time > $thresh_60 ) {
$off_hits = 0;
} else {
$off_hits++;
if ( $off_hits > MAX_OFF_HITS ) { last; }
}
if ( $secs == 0.0 ) { $secs += 0.001; }
my $key = $target_user ? $name : $user;
if ( $time > $thresh_1 ) { $last_1{ $key } += $secs; }
if ( $time > $thresh_5 ) { $last_5{ $key } += $secs; }
if ( $time > $thresh_15 ) { $last_15{ $key } += $secs; }
if ( $time > $thresh_60 ) { $last_60{ $key } += $secs; }
}
}
close( LC );
# clear the progress line display to prepare for printing the table
{
local $| = 1;
print "\r\e[2K";
}
#
# calculate the total cp secs for each interval and store it in the
# @totals array
#
for my $map ( @maps ) {
my $total = 0;
$total += $_ for values( %$map );
push @totals, $total;
}
#
# Extract the top N users from the time range selected by $sort_index
#
our %sort_table = %{ $maps[ $sort_index ] };
our @top_listings;
if ( $target_user ) {
@top_listings = keys( %last_60 );
} else {
@top_listings = keys( %sort_table );
}
@top_listings = ( sort { ($sort_table{ $a } || 0) <=> ($sort_table{ $b } || 0) } @top_listings );
my $start_index = $#top_listings - $number_of_entries;
$start_index < 0 and $start_index = 0;
@top_listings = @top_listings[ $start_index ... $#top_listings ];
#
# Harvest daily summary details from sa for the selected top entries
#
if ( $target_user ) {
our @sa_users = ( $target_user );
} else {
our @sa_users = @top_listings;
}
{
my $target_rx = join( "|", map { lc( quotemeta ) } @sa_users );
$target_rx = qr<^($target_rx)\s+>i;
open( SA, "sa -acm |" );
# skip over the "total sum" line
my ( $rank, $matched ) = ( 0, 0 );
READ_SA:
while ( <SA> ) {
chomp;
if ( /^\s*\d+/ ) {
$rank = 0;
my $line_user = ':total';
my @fields = split;
$sa_data{$line_user} = {
rank => $rank,
cpu => $fields[ 4 ],
percentage => $fields[ 5 ]
};
} elsif ( /$target_rx/ ) {
$matched++;
my $line_user = $1;
my @fields = split;
my $cpu = 0.0;
$fields[ 5 ] =~ /(\d*(?:\.\d*)?)cp/i and $cpu += $1;
$sa_data{$line_user} = {
rank => $rank,
cpu => $cpu,
percentage => $fields[ 6 ]
};
if ( @sa_users == $matched ) { last READ_SA; }
}
$rank++;
}
close( SA );
};
my @stat_table;
if ( $target_user ) {
@stat_table = ( [ qw( command 1m 5m 15m 60m ) ] );
} else {
@stat_table = ( [ qw( user 1m 5m 15m 60m today ) ] );
}
my $sort_column = $stat_table[0]->[ $sort_index + 1 ];
$stat_table[ 0 ]->[ $sort_index + 1 ] = "\e[4m$sort_column\e[0m";
our $make_cell;
if ( $show_percentage && $show_secs ) {
$make_cell = sub {
my ( $usage, $total ) = @_;
my $percent = ( $total > 0 ) ? ( 100.0 * $usage / $total ) : 0;
return sprintf( '%8.2fs %5.1f%%', $usage, $percent );
};
} elsif ( $show_percentage ) {
$make_cell = sub {
my ( $usage, $total ) = @_;
my $percent = ( $total > 0 ) ? ( 100.0 * $usage / $total ) : 0;
return sprintf( '%5.1f%%', $percent );
};
} elsif ( $show_secs ) {
$make_cell = sub {
my ( $usage, $total ) = @_;
return sprintf( '%8.2fs', $usage );
};
}
#
# This is where the table is constructed
#
for my $top_listing ( @top_listings ) {
my @row = (
$top_listing,
map { $make_cell->( $maps[ $_ ]->{$top_listing}, $totals[ $_ ] ); } ( 0 ... $#maps )
);
unless ( $target_user ) {
my $sa = $sa_data{$top_listing};
my $cpu = $sa->{cpu};
push( @row, sprintf( '%8.1fcp %7s', $sa->{cpu}, $sa->{percentage} ) );
my $color = undef;
if ( $special_users{ $top_listing } ) {
$color = 'cyan';
} elsif ( $cpu > 40 ) {
$color = 'red';
} elsif ( $cpu > 30 ) {
$color = 'magenta';
} elsif ( $cpu > 20 ) {
$color = 'yellow';
}
if ( $cpu ) {
@row = map { c( $_, $color ) } @row;
}
}
push( @stat_table, \@row );
}
push @stat_table, '-';
my @total_row = ( "total", map { $make_cell->( $_, $_ ); } @totals );
unless( $target_user ) {
my $sa_total = $sa_data{':total'};
push( @total_row, sprintf( '%10s %7s', $sa_total->{cpu}, $sa_total->{percentage} ) );
}
push @stat_table, \@total_row;
print_table( @stat_table );
print( "s = processs user time in cpu seconds, cp = user time + system time in cpu minutes\n" );
unless ( $target_user || $bleach ) {
print( c( "user within 20cp-30cp for the day, ", 'yellow' ) );
print( c( "user within 30cp-40cp for the day", 'magenta' ) . "\n" );
print( c( "user over 40cp for the day, ", 'red' ) );
print( c( "special users (root, mailnull, mysql)", 'cyan' ) . "\n" );
}
Zerion Mini Shell 1.0