diff --git a/plugins/other/memcached-multigraph b/plugins/other/memcached-multigraph new file mode 100755 index 00000000..39484788 --- /dev/null +++ b/plugins/other/memcached-multigraph @@ -0,0 +1,773 @@ +#!/usr/bin/perl +# +=head1 NAME + +Memcached - A Plugin to monitor Memcached Servers (Multigraph) + +=head1 MUNIN CONFIGURATION + +[memcached_*] + env.host 127.0.0.1 *default* + env.port 11211 *default* + env.timescale 3 *default* + +=head2 MUNIN ENVIRONMENT CONFIGURATION EXPLANATION + + host = host we are going to monitor + port = port we are connecting to, in order to gather stats + timescale = what time frame do we want to format our graphs too + +=head1 NODE CONFIGURATION + +Please make sure you can telnet to your memcache servers and issue the + following commands: stats, stats settings, stats items and stats slabs. + +Available Graphs contained in this Plugin + +bytes => This graphs the current network traffic in and out + +commands => I This graphs the current commands being issued to the memcache machine. Multigraph breaks this down to per slab. + +conns => This graphs the current, max connections as well as avg conns per sec avg conns per sec is derived from total_conns / uptime. + +evictions => I This graphs the current evictions on the node. B + +items => I This graphs the current items and total items in the memcached node. B + +memory => I This graphs the current and max memory allocation B + +The following example holds true for all graphing options in this plugin. + Example: ln -s /usr/share/munin/plugins/memcached_multi.pl /etc/munin/plugins/memcached_bytes + +=head1 ADDITIONAL INFORMATION + +You will find that some of the graphs have LEI on them. This was done in order to save room +on space for text and stands for B. + +The B variable formats certain graphs based on the following guidelines. This currently +only effects the Request for Last Evicted Item graph. + 1 => Seconds B + 2 => Minutes + 3 => Hours B<*Default*> + 4 => Days + +=head1 ACKNOWLEDGEMENTS + +The core of this plugin is based on the mysql_ plugin maintained by Kjell-Magne Ãierud + +Thanks to dormando as well for putting up with me ;) + +=head1 AUTHOR + +Matthew West + +=head1 LICENSE + +GPLv2 + +=head1 VERSION + +v1.21 - Tested against v1.4.5 Memcached Node + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf suggest + +=cut + +use strict; +use IO::Socket; +use Munin::Plugin; + +need_multigraph(); + +my $host = $ENV{host} || "127.0.0.1"; +my $port = $ENV{port} || 11211; + +my %stats; +# This hash contains the information contained in two memcache commands +# stats and stats settings. + +my %items; +# This gives us eviction rates and other error stats per slab +# We track this so we can see if something was evicted earlier than necessary + +my %chnks; +# This gives us the memory size and usage per slab +# We track this so we can see what slab is being used the most and has no free chunks +# so we can re-tune memcached to allocate more pages for the specified chunk size + +my $timescale = $ENV{timescale} || 3; +# This gives us the ability to control the timescale our graphs are displaying. +# As of right now, this only affects the graph for Last Request for Last Evicted Item (LEI) +# The default it set to divide by hours, if you want to get seconds set it to 1. +# Options: 1 = seconds, 2 = minutes, 3 = hours, 4 = days + +# So I was trying to figure out how to build this up, and looking at some good examples +# I decided to use the format, or for the most part, the format from the mysql_ munin plugin +# for Innodb by Kjell-Magne Ãierud, it just spoke ease of flexibility especially with multigraphs +# thanks btw ;) +# +# %graphs is a container for all of the graph definition information. In here is where you'll +# find the configuration information for munin's graphing procedure. +# Format: +# +# $graph{graph_name} => { +# config => { +# # You'll find keys and values stored here for graph manipulation +# }, +# datasrc => [ +# # Name: name given to data value +# # Attr: Attribute for given value +# { name => 'Name', (Attr) }, +# { ... }, +# ], +# } +my %graphs; + +$graphs{items} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Items in Memcached', + category => 'memcached', + title => 'Items', + info => 'This graph shows the number of items in use by memcached', + }, + datasrc => [ + { name => 'curr_items', label => 'Current Items', min => '0' }, + ], +}; + +$graphs{memory} = { + config => { + args => '--base 1024 --lower-limit 0', + vlabel => 'Bytes Used', + category => 'memcached', + title => 'Memory Usage', + info => 'This graph shows the memory consumption of memcached', + }, + datasrc => [ + { name => 'limit_maxbytes', draw => 'AREA', label => 'Maximum Bytes Allocated', min => '0' }, + { name => 'bytes', draw => 'AREA', label => 'Current Bytes Used', min => '0' }, + ], +}; + +$graphs{bytes} = { + config => { + args => '--base 1000', + vlabel => 'bits in (-) / out (+)', + title => 'Network Traffic', + category => 'memcached', + info => 'This graph shows the network traffic in (-) / out (+) of the machine', + order => 'bytes_read,bytes_written', + }, + datasrc => [ + { name => 'bytes_read', type => 'DERIVE', label => 'Network Traffic coming in (-)', graph => 'no', cdef => 'bytes_read,8,*', min => '0' }, + { name => 'bytes_written', type => 'DERIVE', label => 'Traffic in (-) / out (+)', negative => 'bytes_read', cdef => 'bytes_written,8,*', min => '0' }, + ], +}; + +$graphs{conns} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Connections per ${graph_period}', + category => 'memcached', + title => 'Connections', + info => 'This graph shows the number of connections being handled by memcached', + order => 'max_conns,curr_conns,avg_conns', + }, + datasrc => [ + { name => 'curr_conns', label => 'Current Connections', min => '0' }, + { name => 'max_conns', label => 'Max Connections', min => '0' }, + { name => 'avg_conns' , label => 'Avg Connections', min => '0' }, + ], +}; + +$graphs{commands} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Commands per ${graph_period}', + category => 'memcached', + title => 'Commands', + info => 'This graph shows the number of commands being handled by memcached', + }, + datasrc => [ + { name => 'cmd_get', type => 'DERIVE', label => 'Gets', info => 'Cumulative number of retrieval reqs', min => '0' }, + { name => 'cmd_set', type => 'DERIVE', label => 'Sets', info => 'Cumulative number of storage reqs', min => '0' }, + { name => 'get_hits', type => 'DERIVE', label => 'Get Hits', info => 'Number of keys that were requested and found', min => '0' }, + { name => 'get_misses', type => 'DERIVE', label => 'Get Misses', info => 'Number of keys there were requested and not found', min => '0' }, + { name => 'delete_hits', type => 'DERIVE', label => 'Delete Hits', info => 'Number of delete requests that resulted in a deletion of a key', min => '0' }, + { name => 'delete_misses', type => 'DERIVE', label => 'Delete Misses', info => 'Number of delete requests for missing key', min => '0' }, + { name => 'incr_hits', type => 'DERIVE', label => 'Increment Hits', info => 'Number of successful increment requests', min => '0' }, + { name => 'incr_misses', type => 'DERIVE', label => 'Increment Misses', info => 'Number of unsuccessful increment requests', min => '0' }, + { name => 'decr_hits', type => 'DERIVE', label => 'Decrement Hits', info => 'Number of successful decrement requests', min => '0' }, + { name => 'decr_misses', type => 'DERIVE', label => 'Decrement Misses', info => 'Number of unsuccessful decrement requests', min => '0' }, + ], +}; + +$graphs{evictions} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Evictions per ${graph_period}', + category => 'memcached', + title => 'Evictions', + info => 'This graph shows the number of evictions per second', + }, + datasrc => [ + { name => 'evictions', label => 'Evictions', info => 'Cumulative Evictions Across All Slabs', type => 'DERIVE', min => '0' }, + { name => 'evicted_nonzero', label => 'Evictions prior to Expire', info => 'Cumulative Evictions forced to expire prior to expiration', type => 'DERIVE', min => '0' }, + { name => 'reclaimed', label => 'Reclaimed Items', info => 'Cumulative Reclaimed Item Entries Across All Slabs', type => 'DERIVE', min => '0' }, + ], +}; + +$graphs{slabchnks} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Available Chunks for this Slab', + category => 'memcached', + title => 'Chunk Usage for Slab: ', + info => 'This graph shows you the chunk usage for this memory slab.', + }, + datasrc => [ + { name => 'total_chunks', label => 'Total Chunks Available', min => '0' }, + { name => 'used_chunks', label => 'Total Chunks in Use', min => '0' }, + { name => 'free_chunks', label => 'Total Chunks Not in Use (Free)', min => '0' }, + ], +}; + +$graphs{slabhits} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Hits per Slab per ${graph_period}', + category => 'memcached', + title => 'Hits for Slab: ', + info => 'This graph shows you the successful hit rate for this memory slab.', + }, + datasrc => [ + { name => 'get_hits', label => 'Get Requests', type => 'DERIVE', min => '0' }, + { name => 'cmd_set', label => 'Set Requests', type => 'DERIVE', min => '0' }, + { name => 'delete_hits', label => 'Delete Requests', type => 'DERIVE', min => '0' }, + { name => 'incr_hits', label => 'Increment Requests', type => 'DERIVE', min => '0' }, + { name => 'decr_hits', label => 'Decrement Requests', type => 'DERIVE', min => '0' }, + ], +}; + +$graphs{slabevics} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Evictions per Slab per ${graph_period}', + category => 'memcached', + title => 'Evictions for Slab: ', + info => 'This graph shows you the eviction rate for this memory slab.', + }, + datasrc => [ + { name => 'evicted', label => 'Total Evictions', type => 'DERIVE', min => '0' }, + { name => 'evicted_nonzero', label => 'Evictions from LRU Prior to Expire', type => 'DERIVE', min => '0' }, + { name => 'reclaimed', label => 'Reclaimed Expired Items', info => 'This is number of times items were stored in expired entry memory space', type => 'DERIVE', min => '0' }, + ], +}; + +$graphs{slabevictime} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => ' since Request for LEI', + category => 'memcached', + title => 'Eviction Request Time for Slab: ', + info => 'This graph shows you the time since we requested the last evicted item', + }, + datasrc => [ + { name => 'evicted_time', label => 'Eviction Time (LEI)', info => 'Time Since Request for Last Evicted Item', min => '0' }, + ], +}; + +$graphs{slabitems} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => 'Items per Slab', + category => 'memcached', + title => 'Items in Slab: ', + info => 'This graph shows you the number of items and reclaimed items per slab.', + }, + datasrc => [ + { name => 'number', label => 'Items', info => 'This is the amount of items stored in this slab', min => '0' }, + ], +}; + +$graphs{slabitemtime} = { + config => { + args => '--base 1000 --lower-limit 0', + vlabel => ' since item was stored', + category => 'memcached', + title => 'Age of Eldest Item in Slab: ', + info => 'This graph shows you the time of the eldest item in this slab', + }, + datasrc => [ + { name => 'age', label => 'Eldest Item\'s Age', min => '0' }, + ], +}; + +## +#### Config Check #### +## + +if (defined $ARGV[0] && $ARGV[0] eq 'config') { + + $0 =~ /memcached_multi_(.+)*/; + my $plugin = $1; + + die 'Unknown Plugin Specified: ' . ($plugin ? $plugin : '') unless $graphs{$plugin}; + + # We need to fetch the stats before we do any config, cause its needed for multigraph + fetch_stats(); + + # Now lets go ahead and print out our config. + do_config($plugin); + exit 0; +} + +## +#### Autoconf Check #### +## + +if (defined $ARGV[0] && $ARGV[0] eq 'autoconf') { + + my $s = IO::Socket::INET->new( + Proto => "tcp", + PeerAddr => $host, + PeerPort => $port, + ); + + if (defined($s)) { + print "yes\n"; + exit 0; + } else { + print "no (unable to connect to $host\[:$port\])\n"; + exit 0; + } +} + +## +#### Suggest Check #### +## + +if (defined $ARGV[0] && $ARGV[0] eq 'suggest') { + + my $s = IO::Socket::INET->new( + Proto => "tcp", + PeerAddr => $host, + PeerPort => $port, + ); + + if (defined($s)) { + my @rootplugins = ('bytes','conns','commands','evictions','items','memory'); + foreach my $plugin (@rootplugins) { + print "$plugin\n"; + } + exit 0; + } else { + print "no (unable to connect to $host\[:$port\])\n"; + exit 0; + } +} + +## +#### Well We aren't running (auto)config/suggest so lets print some stats #### +## + +fetch_output(); + +## +#### Subroutines for printing info gathered from memcached #### +## + +## +#### This subroutine performs the bulk processing for printing statistics. +## + +sub fetch_output { + + $0 =~ /memcached_multi_(.+)*/; + my $plugin = $1; + + die 'Unknown Plugin Specified: ' . ($plugin ? $plugin : '') unless $graphs{$plugin}; + + # Well we need to actually fetch the stats before we do anything to them. + fetch_stats(); + + # Now lets go ahead and print out our output. + my @subgraphs; + if ($plugin eq 'memory') { + @subgraphs = ('slabchnks'); + foreach my $slabid(sort{$a <=> $b} keys %chnks) { + print_submulti_output($slabid,$plugin,@subgraphs); + } + print_rootmulti_output($plugin); + } elsif ($plugin eq 'commands') { + @subgraphs = ('slabhits'); + foreach my $slabid(sort{$a <=> $b} keys %chnks) { + print_submulti_output($slabid,$plugin,@subgraphs); + } + print_rootmulti_output($plugin); + } elsif ($plugin eq 'evictions') { + @subgraphs = ('slabevics','slabevictime'); + foreach my $slabid (sort{$a <=> $b} keys %items) { + print_submulti_output($slabid,$plugin,@subgraphs); + } + print_rootmulti_output($plugin); + } elsif ($plugin eq 'items') { + @subgraphs = ('slabitems','slabitemtime'); + foreach my $slabid (sort{$a <=> $b} keys %items) { + print_submulti_output($slabid,$plugin,@subgraphs); + } + print_rootmulti_output($plugin); + } else { + print_root_output($plugin); + } + + return; +} + +## +#### This subroutine is for the root non-multigraph graphs which render on the main node page #### +## + +sub print_root_output { + my ($plugin) = (@_); + + my $graph = $graphs{$plugin}; + + print "graph memcached_$plugin\n"; + + if ($plugin ne 'conns') { + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key ne 'name'); + my $output = $stats{$value}; + print "$dsrc->{name}.value $output\n"; + } + } + } else { + my $output; + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + if ($value eq 'max_conns') { + $output = $stats{maxconns}; + } elsif ($value eq 'curr_conns') { + $output = $stats{curr_connections}; + } elsif ($value eq 'avg_conns') { + $output = sprintf("%02d", $stats{total_connections} / $stats{uptime}); + } else { + next; + } + print "$dsrc->{name}.value $output\n"; + } + } + } + + return; +} + +## +#### This subroutine is for the root multigraph graphs which render on the main node page #### +## + +sub print_rootmulti_output { + my ($plugin) = (@_); + + my $graph = $graphs{$plugin}; + + print "multigraph memcached_$plugin\n"; + + foreach my $dsrc (@{$graph->{datasrc}}) { + my $output = 0; + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key ne 'name'); + if (($plugin eq 'evictions') && ($value eq 'evicted_nonzero')) { + foreach my $slabid (sort{$a <=> $b} keys %items) { + $output += $items{$slabid}->{evicted_nonzero}; + } + } else { + $output = $stats{$value}; + } + print "$dsrc->{name}.value $output\n"; + } + } + + return; +} + +## +#### This subroutine is for the sub multigraph graphs created via the multigraph plugin #### +## + +sub print_submulti_output { + my ($slabid,$plugin,@subgraphs) = (@_); + my $currslab = undef; + + foreach my $sgraph (@subgraphs) { + + my $graph = $graphs{$sgraph}; + + print "multigraph memcached_$plugin.$sgraph\_$slabid\n"; + + if ($plugin eq 'evictions') { + $currslab = $items{$slabid}; + } elsif ($plugin eq 'memory') { + $currslab = $chnks{$slabid}; + } elsif ($plugin eq 'commands') { + $currslab = $chnks{$slabid}; + } elsif ($plugin eq 'items') { + $currslab = $items{$slabid}; + } else { + return; + } + + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key ne 'name'); + my $output = $currslab->{$value}; + if (($sgraph eq 'slabevictime') || ($sgraph eq 'slabitemtime')) { + $output = time_scale('data',$output); ; + } + print "$dsrc->{name}.value $output\n"; + } + } + } + + return; +} + +## +#### Subroutines for printing out config information for graphs #### +## + +## +#### This subroutine does the bulk printing the config info per graph #### +## + +sub do_config { + my ($plugin) = (@_); + my @subgraphs; + if ($plugin eq 'memory') { + @subgraphs = ('slabchnks'); + foreach my $slabid (sort{$a <=> $b} keys %chnks) { + print_submulti_config($slabid,$plugin,@subgraphs); + } + print_rootmulti_config($plugin); + } elsif ($plugin eq 'commands') { + @subgraphs = ('slabhits'); + foreach my $slabid (sort{$a <=> $b} keys %chnks) { + print_submulti_config($slabid,$plugin,@subgraphs); + } + print_rootmulti_config($plugin); + } elsif ($plugin eq 'evictions') { + @subgraphs = ('slabevics','slabevictime'); + foreach my $slabid (sort{$a <=> $b} keys %items) { + print_submulti_config($slabid,$plugin,@subgraphs); + } + print_rootmulti_config($plugin); + } elsif ($plugin eq 'items') { + @subgraphs = ('slabitems','slabitemtime'); + foreach my $slabid (sort{$a <=> $b} keys %items) { + print_submulti_config($slabid,$plugin,@subgraphs); + } + print_rootmulti_config($plugin); + } else { + print_root_config($plugin); + } + + return; +} + +## +#### This subroutine is for the config info for sub multigraph graphs created via the multigraph plugin #### +## + +sub print_submulti_config { + my ($slabid,$plugin,@subgraphs) = (@_); + my ($slabitems,$slabchnks) = undef; + + foreach my $sgraph (@subgraphs) { + + my $graph = $graphs{$sgraph}; + + my %graphconf = %{$graph->{config}}; + + print "multigraph memcached_$plugin.$sgraph\_$slabid\n"; + + while ( my ($key, $value) = each(%graphconf)) { + if ($key eq 'title') { + print "graph_$key $value" . "$slabid" . " ($chnks{$slabid}->{chunk_size} Bytes)\n"; + } elsif (($key eq 'vlabel') && (($sgraph eq 'slabevictime') || ($sgraph eq 'slabitemtime'))) { + $value = time_scale('config',$value); + print "graph_$key $value\n"; + } else { + print "graph_$key $value\n"; + } + } + + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key eq 'name'); + print "$dsrc->{name}.$key $value\n"; + } + } + } + + return; +} + +## +#### This subroutine is for the config info for root multigraph graphs which render on the main node page #### +## + +sub print_rootmulti_config { + my ($plugin) = (@_); + + die 'Unknown Plugin Specified: ' . ($plugin ? $plugin : '') unless $graphs{$plugin}; + + my $graph = $graphs{$plugin}; + + my %graphconf = %{$graph->{config}}; + + print "multigraph memcached_$plugin\n"; + + while ( my ($key, $value) = each(%graphconf)) { + print "graph_$key $value\n"; + } + + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key eq 'name'); + print "$dsrc->{name}.$key $value\n"; + } + } + + return; +} + +## +#### This subroutine is for the config info for non multigraph graphs which render on the main node page #### +## + +sub print_root_config { + my ($plugin) = (@_); + + die 'Unknown Plugin Specified: ' . ($plugin ? $plugin : '') unless $graphs{$plugin}; + + my $graph = $graphs{$plugin}; + + my %graphconf = %{$graph->{config}}; + + print "graph memcached_$plugin\n"; + + while ( my ($key, $value) = each(%graphconf)) { + print "graph_$key $value\n"; + } + + foreach my $dsrc (@{$graph->{datasrc}}) { + my %datasrc = %$dsrc; + while ( my ($key, $value) = each(%datasrc)) { + next if ($key eq 'name'); + print "$dsrc->{name}.$key $value\n"; + } + } + + return; +} + +## +#### This subroutine actually performs the data fetch for us #### +#### These commands do not lock up Memcache at all #### +## + +sub fetch_stats { + my $s = IO::Socket::INET->new( + Proto => "tcp", + PeerAddr => $host, + PeerPort => $port, + ); + + die "Error: Unable to Connect to $host\[:$port\]\n" unless $s; + + print $s "stats\r\n"; + + while (my $line = <$s>) { + if ($line =~ /STAT\s(.+?)\s(\d+)/) { + my ($skey,$svalue) = ($1,$2); + $stats{$skey} = $svalue; + } + last if $line =~ /^END/; + } + + print $s "stats settings\r\n"; + + while (my $line = <$s>) { + if ($line =~ /STAT\s(.+?)\s(\d+)/) { + my ($skey,$svalue) = ($1,$2); + $stats{$skey} = $svalue; + } + last if $line =~ /^END/; + } + + print $s "stats slabs\r\n"; + + while (my $line = <$s>) { + if ($line =~ /STAT\s(\d+):(.+)\s(\d+)/) { + my ($slabid,$slabkey,$slabvalue) = ($1,$2,$3); + $chnks{$slabid}->{$slabkey} = $slabvalue; + } + last if $line =~ /^END/; + } + + print $s "stats items\r\n"; + + while (my $line = <$s>) { + if ($line =~ /STAT\sitems:(\d+):(.+?)\s(\d+)/) { + my ($itemid,$itemkey,$itemvalue) = ($1,$2,$3); + $items{$itemid}->{$itemkey} = $itemvalue; + } + last if $line =~ /^END/; + } +} + +## +#### This subroutine is to help manage the time_scale settings for the graph +## + +sub time_scale { + my ($configopt,$origvalue) = (@_); + my $value; + + if ($configopt eq 'config') { + if (($timescale == 1) || ($timescale > 4) || (!defined($timescale))) { + $value = "Seconds" . $origvalue; + } elsif ($timescale == 2) { + $value = "Minutes" . $origvalue; + } elsif ($timescale == 3) { + $value = "Hours" . $origvalue; + } elsif ($timescale == 4) { + $value = "Days" . $origvalue; + } + } elsif ($configopt eq 'data') { + if (($timescale == 1) || ($timescale > 4) || (!defined($timescale))) { + $value = sprintf("%02.2f", $origvalue / 1); + } elsif ($timescale == 2) { + $value = sprintf("%02.2f", $origvalue / 60); + } elsif ($timescale == 3) { + $value = sprintf("%02.2f", $origvalue / 3600); + } elsif ($timescale == 4) { + $value = sprintf("%02.2f", $origvalue / 86400); + } + } else { + die "Unknown time_scale option given: either [config/data]\n"; + } + return $value; +}