2
0
mirror of https://github.com/munin-monitoring/contrib.git synced 2018-11-08 00:59:34 +01:00
contrib-munin/plugins/other/memcached-multigraph
2011-12-18 15:10:29 +01:00

769 lines
24 KiB
Perl
Executable File

#!/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<MULTIGRAPH> This graphs the current commands being issued to the memcache machine. B<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<MULTIGRAPH> This graphs the current evictions on the node. B<Multigraph breaks this down to per slab.>
items => I<MULTIGRAPH> This graphs the current items and total items in the memcached node. B<Multigraph breaks this down to per slab.>
memory => I<MULTIGRAPH> This graphs the current and max memory allocation B<Multigraph breaks this down to per slab.>
The following example holds true for all graphing options in this plugin.
Example: ln -s /usr/share/munin/plugins/memcached_multi_ /etc/munin/plugins/memcached_multi_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<Last Evicted Item>.
The B<Timescale> variable formats certain graphs based on the following guidelines.
1 => Seconds
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
Matt West < https://code.google.com/p/memcached-munin-plugin/ >
=head1 LICENSE
GPLv2
=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 hit 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.
# 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' },
{ name => 'total_items', label => 'New Items', min => '0', type => 'DERIVE' },
],
};
$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) {
$value = "Seconds" . $origvalue;
} elsif ($timescale == 2) {
$value = "Minutes" . $origvalue;
} elsif (($timescale == 3) || ($timescale > 4) || (!defined($timescale))) {
$value = "Hours" . $origvalue;
} elsif ($timescale == 4) {
$value = "Days" . $origvalue;
}
} elsif ($configopt eq 'data') {
if ($timescale == 1) {
$value = sprintf("%02.2f", $origvalue / 1);
} elsif ($timescale == 2) {
$value = sprintf("%02.2f", $origvalue / 60);
} elsif (($timescale == 3) || ($timescale > 4) || (!defined($timescale))) {
$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;
}