Sisyphus repository
Last update: 1 october 2023 | SRPMs: 18631 | Visits: 37503910
en ru br
ALT Linux repos
S:0.6-alt1.1

Group :: Monitoring
RPM: memcache-top

 Main   Changelog   Spec   Patches   Sources   Download   Gear   Bugs and FR  Repocop 

#!/usr/bin/perl -w

################################################################################
# memcache-top.pl
#
# "top" for memcache - watch the traffic and other stats in real-time. Yoikes.
#
# NOTES:
#
# If Getopt::Long is installed:
# - Specify instances w/ --instances (multiple times or comma separated)
# - Specify default port w/ --port (defaults to 11211)
# - Specify sleep time w/ --sleep (default 3)
# - Specify color output w/ --color (default) or --nocolor
# - Specify lifetime stats w/ --lifetime or --nolifetime (default)
# NOTE: lifetime stats break thresholds for evictions, bytes.
# - Specify read and write bytes w/ --bytes (default) or --nobytes
# - Specify get and set commands w/ --commands or --nocommands (default)
# - Specify cumulative numbers w/ --cumulative (don't use with lifetime)
#
# If Getopt::Long is not installed:
# - Specify sleep time by typing a number after the command.
# Specify instances in @default_instances.
# Specify thresholds by defining %threshold.
# Written against memcached v 1.2.3, but works fine w/ later versions.
#
# HISTORY:
#
# v0.3 - 2009-04-22 - ntang
# Minor cleanups. First release to google code:
# http://code.google.com/p/memcache-top/
# v0.4 - 2009-04-23 - ntang
# Added ability to specify color, sleep time, and servers on command line.
# Also added checks for Getopt::Long and Term::ANSIColor.
# Added $default_port = "11211" and padding for short server names. Server
# names over 23 characters inc. port will break the column lineups for now.
# v0.4b - 2009-04-23 - ntang
# Added total capacity, and changed "SERVER" to "INSTANCE" to be more clear.
# Server now is the hostname, instance is hostname + port. It will
# truncate the instance and/or server to fit inside the first column
# correctly. (Yay!) It'll also truncate long reads/ writes (or
# technically any number) to K or M or G if it exceeds certain limits for
# readability.
# v0.5 - 2009-04-24 - ntang
# Cleaned up instances vs. servers so it's internally consistent.
# Redid printing so that now it stores it all and only refreshes/ prints when
# it has the full set of data. Warning: major hackishness.
# Switched to per-second stats by default w/ lifetime stats available.
# v0.6 - 2009-04-28 - ntang
# I lied. One more change... the ability to specify read/write bytes, or
# get/set commands, or both. Bear in mind if you specify both you will
# exceed the width of a standard terminal! You've been warned. :P
# Also, some minor display changes, etc. etc.
#
################################################################################

use strict;
use IO::Socket;
use Time::HiRes 'time';

my (@default_instances, @instances, $remote, $sleep, %threshold, %laststats,
$usecolor, @keys, $default_port, $version, @out, $lifetime, $bytes,
$commands, $cumulative);

$version = "0.6";

################################################################################
# CONFIGURATION

# Set $usecolor to 1 to push @out,in, gasp, color.
$usecolor = 1;

# 'Alert' threshold values at which to color the text red.
%threshold = (
cache_hit => 60, # Cache hit ratio
usage => 90, # % space used
time => 5, # Number of ms to run the stats query
evictions => 0, # Number of evictions per second
curr_connections => 3500, # Number of current connections
bytes_read => 1000000, # Bytes read, per second
bytes_written => 1000000, # Bytes written, per second
limit_maxbytes => 0, # Total space allocated
bytes => 0, # Total space used
cmd_get => 1000, # Get commands
cmd_set => 1000, # Set commands
);

# Display lifetime stats instead of per-second stats
$lifetime = 0;

# Display read/write bytes
$bytes = 1;

# Display get/set commands
$commands = 0;

# Show cumulative stats (since start of run)
$cumulative = 0;

# Default time to sleep in-between refreshes.
$sleep = 3;

# List of servers/ ports to query.
@default_instances = (
'127.0.0.1:11211',
);

# Default port to connect to, if not specified
$default_port = "11211";

# END CONFIGURATION
################################################################################

@keys = ('usage', 'cache_hit', 'curr_connections', 'time', 'cmd_get', 'cmd_set',
'bytes_read', 'bytes_written', 'evictions', 'limit_maxbytes', 'bytes');

if (@ARGV) {
eval { require Getopt::Long; };
if ($@) {
if ( $ARGV[0] =~ /^\d+$/ ) {
$sleep = $ARGV[0];
}
else {
die "USAGE: memcache-top.pl <optional number of seconds to sleep>\n";
}
}
else {
use Getopt::Long;
GetOptions (
'instances=s' => \@instances,
'sleep=i' => \$sleep,
'port=i' => \$default_port,
'color!' => \$usecolor,
'lifetime!' => \$lifetime,
'bytes!' => \$bytes,
'commands!' => \$commands,
'cumulative!' => \$cumulative,
);
if (@instances) {
@instances = split(/,/,join(',',@instances));
} else {
@instances = @default_instances;
}
}
}
else {
@instances = @default_instances;
}

if ( $lifetime && $cumulative ) {
$lifetime = 0;
}

if ( $usecolor ) {
eval { require Term::ANSIColor; };
if ($@) { $usecolor = 0; }
else { use Term::ANSIColor; }
}

my $i = 1;

my (%original);

while ($i) {

@out = ();

push @out,"\033[2J"; # This clears the screen, yo.

push @out,color 'bold' if $usecolor;
push @out,"\nmemcache-top v$version\t";
push @out,color 'reset' if $usecolor;
push @out,"(default port: " . sprintf("%5d",$default_port) . ", color: ";
push @out,"on," if $usecolor;
push @out,"off," unless $usecolor;
push @out," refresh: $sleep seconds)\n\n";
push @out,color 'bold' if $usecolor;
push @out,"INSTANCE\t\tUSAGE\tHIT %\tCONN\tTIME\t";
if ( $lifetime || $cumulative ) {
push @out,"EVICT\t";
push @out,"GETS\tSETS\t" if $commands;
push @out,"READ\tWRITE\t" if $bytes;
push @out,"\n";
} else {
push @out,"EVICT/s ";
push @out,"GETS/s\tSETS/s\t" if $commands;
push @out,"READ/s\tWRITE/s\t" if $bytes;
push @out,"\n";
}
push @out,color 'reset' if $usecolor;

my %tot;

foreach my $key (@keys) {
$tot{$key} = 0;
}

my $count = 0;

foreach my $instance (@instances) {

my ($port, $server);

my @split = split(/:/,$instance);
unless ( $split[1] ) {
$instance = $instance . ":" . $default_port;
$port = $default_port;
}
else {
$port = $split[1];
}

# Some exhaustive (exhausting?) logic to determine the ideal text to push @out,for
# the server name.
if ( length($instance) > 22 ) {
if ( $port ne $default_port ) {
$server = substr($split[0],0,17) . ":" . $port;
}
else {
if ( length($split[0]) < 18 ) {
$server = $instance;
}
else {
$server = substr($split[0],0,23);
}
}
}
elsif ( length($instance) < 8 ) {
$server = "$instance\t\t";
}
elsif ( length($instance) < 16 ) {
$server = "$instance\t";
}
else {
$server = $instance;
}

my $t0 = time();

$remote = IO::Socket::INET->new($instance);
unless ( defined($remote) ) {
push @out,color 'red' if $usecolor;
push @out,$instance . " is DOWN.\n";
$count++;
push @out,color 'reset' if $usecolor;
next;
}

$remote->autoflush(1);
$count++;

print $remote "stats\n";

my (%stats, %outstats);

foreach my $key (@keys) {
$outstats{$key} = 0;
}

LINE: while ( defined ( my $line = <$remote> ) ) {
last LINE if ( $line =~ /END/ );
chomp $line;
my @bits = split(' ',$line);
$stats{$bits[1]} = $bits[2];
next LINE;
}

close $remote;

my $t1 = time();
$outstats{time} = ($t1 - $t0) * 1000;

if ( $lifetime || $cumulative) {
foreach my $key ('cmd_get', 'cmd_set', 'get_hits', 'get_misses', 'evictions', 'bytes_read', 'bytes_written') {
if ( $cumulative ) {
if ( $i == 1 ) {
$original{$instance}{$key} = $stats{$key};
} else {
$outstats{$key} = $stats{$key} - $original{$instance}{$key};
}
} else {
$outstats{$key} = $stats{$key};
}
}
$outstats{cache_hit} = ( $stats{get_hits} / $stats{cmd_get} ) * 100;
} else {
foreach my $key ('cmd_get', 'cmd_set', 'get_hits', 'get_misses', 'evictions', 'bytes_read', 'bytes_written') {
if ( defined ( $laststats{$instance}{$key} ) ) {
$outstats{$key} = ($stats{$key} - $laststats{$instance}{$key}) / $sleep;
}
}
$outstats{cache_hit} = 0;
if ( defined($outstats{get_misses}) && $outstats{get_misses} > 0 ) {
$outstats{cache_hit} = ( $laststats{$instance}{get_hits} / $laststats{$instance}{cmd_get} ) * 100;
}
}

$outstats{limit_maxbytes} = $stats{limit_maxbytes};
$outstats{bytes} = $stats{bytes};
$outstats{usage} = ( $stats{bytes} / $stats{limit_maxbytes} * 100 );
$outstats{curr_connections} = $stats{curr_connections};

if ( $cumulative ) {
foreach my $key ('cmd_get', 'cmd_set', 'get_hits', 'get_misses', 'evictions', 'bytes_read', 'bytes_written') {
$threshold{$key} = $threshold{$key} * $i if $threshold{$key};
}
}

push @out,"$server\t";
threshold_print( $outstats{usage}, $threshold{usage}, 1, 0, '%', '%.1f');
threshold_print( $outstats{cache_hit}, $threshold{cache_hit}, 0, 0, '%', '%.1f');
threshold_print( $outstats{curr_connections}, $threshold{curr_connections}, 1, 0, '', '%.0d');
if ( $outstats{time} >= 1000 ) {
threshold_print( $outstats{time}/1000, $threshold{time}/1000, 1, 0, 's', '%.2f');
} else {
threshold_print( $outstats{time}, $threshold{time}, 1, 0, 'ms', '%.1f');
}
threshold_print( $outstats{evictions}, $threshold{evictions}, 1, 0, '', '%.1f');
if ( $commands ) {
threshold_print( $outstats{cmd_get}, $threshold{cmd_get}, 1, 0, '', '%.0f');
threshold_print( $outstats{cmd_set}, $threshold{cmd_set}, 1, 0, '', '%.0f');
}
if ( $bytes ) {
threshold_print( $outstats{bytes_read}, $threshold{bytes_read}, 1, 0, '', '%.0f');
threshold_print( $outstats{bytes_written}, $threshold{bytes_written}, 1, 0, '', '%.0f');
}
push @out,"\n";

foreach my $key (@keys) {
$tot{$key} = $tot{$key} + $outstats{$key};
}

unless ( $lifetime || $cumulative ) {
foreach my $key ('cmd_get', 'cmd_set', 'get_hits', 'get_misses', 'evictions', 'bytes_read', 'bytes_written') {
$laststats{$instance}{$key} = $stats{$key};
}
}

}

push @out,color 'bold' if $usecolor;
push @out,"\nAVERAGE:\t\t";
threshold_print( $tot{usage}/$count, $threshold{usage}, 1, 1, '%', '%.1f');
threshold_print( $tot{cache_hit}/$count, $threshold{cache_hit}, 0, 1, '%', '%.1f');
threshold_print( $tot{curr_connections}/$count, $threshold{curr_connections}, 1, 1, '', '%.0d');
if ( ( $tot{time}/$count ) >= 1000 ) {
threshold_print( ($tot{time}/$count)/1000, $threshold{time}/1000, 1, 1, 's', '%.2f');
} else {
threshold_print( $tot{time}/$count, $threshold{time}, 1, 1, 'ms', '%.1f');
}
threshold_print( $tot{evictions}/$count, $threshold{evictions}, 1, 1, '', '%.1f');
if ( $commands ) {
threshold_print( $tot{cmd_get}/$count, $threshold{cmd_get}, 1, 1, '', '%.0f');
threshold_print( $tot{cmd_set}/$count, $threshold{cmd_set}, 1, 1, '', '%.0f');
}
if ( $bytes ) {
threshold_print( $tot{bytes_read}/$count, $threshold{bytes_read}, 1, 1, '', '%.0f');
threshold_print( $tot{bytes_written}/$count, $threshold{bytes_written}, 1, 1, '', '%.0f');
}
push @out,"\n";
push @out,"\nTOTAL:\t\t";
threshold_print( $tot{bytes}, $threshold{bytes}, 0, 1, 'B/', '%.0f');
threshold_print( $tot{limit_maxbytes}, $threshold{limit_maxbytes}, 0, 1, "B\t", '%.0f');
threshold_print( $tot{curr_connections}, $threshold{curr_connections}*$count, 1, 1, '', '%.0d');
if ( $tot{time} >= 1000 ) {
threshold_print( $tot{time}/1000, ($threshold{time}*$count)/1000, 1, 1, 's', '%.2f');
} else {
threshold_print( $tot{time}, $threshold{time}*$count, 1, 1, 'ms', '%.1f');
}
threshold_print( $tot{evictions}, $threshold{evictions}*$count, 1, 1, '', '%.1f');
if ( $commands ) {
threshold_print( $tot{cmd_get}, $threshold{cmd_get}*$count, 1, 1, '', '%.0f');
threshold_print( $tot{cmd_set}, $threshold{cmd_set}*$count, 1, 1, '', '%.0f');
}
if ( $bytes ) {
threshold_print( $tot{bytes_read}, $threshold{bytes_read}*$count, 1, 1, '', '%.0f');
threshold_print( $tot{bytes_written}, $threshold{bytes_written}*$count, 1, 1, '', '%.0f');
}
push @out,color 'reset' if $usecolor;
push @out,"\n(ctrl-c to quit.)\n";
sleep($sleep);

print @out;
$i++;
}

################################################################################
# threshold_print
# takes two variables, compares them (greater then if $gt == 1), and then prints
# it. It uses red as the default color for successful comparisons, but sets
# it to red bold if $bold == 1. $trail specifies trailing characters to print.
# $sprintf lets you specify the format for sprintf().
#
sub threshold_print {

my ($stat, $threshold, $gt, $bold, $trail, $sprintf) = @_;

my $color = 'red';
my $offcolor = 'reset';
if ( $bold ) {
$color = 'bold red';
$offcolor = 'reset bold';
}

if ( $gt ) {
if ( $stat > $threshold ) {
push @out, color $color if $usecolor;
}
} else {
if ( $stat < $threshold ) {
push @out, color $color if $usecolor;
}
}

if ( $stat > 999999999999 ) {
$stat = $stat / (1024*1024*1024*1024);
$trail = 'T' . $trail;
$sprintf = '%.1f';
} elsif ( $stat > 99999999 ) {
$stat = $stat / (1024*1024*1024);
$trail = "G" . $trail;
$sprintf = '%.1f';
} elsif ( $stat > 999999 ) {
$stat = $stat / (1024*1024);
$trail = 'M' . $trail;
$sprintf = '%.1f';
} elsif ( $stat > 9999 ) {
$stat = $stat/1024;
$trail = 'K' . $trail;
$sprintf = '%.1f';
}

push @out,sprintf($sprintf,$stat) . $trail;
push @out,color $offcolor if $usecolor;
push @out,"\t";
}
################################################################################
 
design & coding: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
current maintainer: Michael Shigorin