Created
March 23, 2018 20:53
-
-
Save bsormagec/7fdaf12ba1ecbcb7c2c26cf350b399aa to your computer and use it in GitHub Desktop.
memcache-top
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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) | |
| # - Run a single time without clearing the screen, good for scripts and | |
| # the like: --oneoff | |
| # | |
| # 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, $oneoff); | |
| $version = "0.7"; | |
| ################################################################################ | |
| # 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 | |
| curr_items => 0, # Number of items | |
| 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; | |
| # Run a single time as a one-off without formatting: | |
| $oneoff = 0; | |
| # List of servers/ ports to query. | |
| @default_instances = ( | |
| 'memcached:11211', | |
| ); | |
| # Default port to connect to, if not specified | |
| $default_port = "11211"; | |
| # END CONFIGURATION | |
| ################################################################################ | |
| @keys = ('usage', 'cache_hit', 'curr_connections', 'time', 'curr_items', '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, | |
| 'oneoff' => \$oneoff, | |
| ); | |
| 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; } | |
| } | |
| if ( $oneoff ) { | |
| $usecolor = 0; | |
| } | |
| my $i = 1; | |
| my (%original); | |
| while ($i) { | |
| @out = (); | |
| unless ($oneoff) { 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\tITEMS\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}; | |
| $outstats{curr_items} = $stats{curr_items}; | |
| 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{curr_items}, $threshold{curr_items}, 1, 0, '', '%.0d'); | |
| 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{curr_items}/$count, $threshold{curr_items}, 1, 1, '', '%.0d'); | |
| 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{curr_items}, $threshold{curr_items}*$count, 1, 1, '', '%.0d'); | |
| 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"; | |
| unless ($oneoff) { push @out,"(ctrl-c to quit.)\n"; } | |
| sleep($sleep); | |
| print @out; | |
| $i++; | |
| if ($oneoff) { exit 0; } | |
| } | |
| ################################################################################ | |
| # 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"; | |
| } | |
| ################################################################################ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment