#!@@PERL@@ -w
# -*- perl -*-

use strict;
use warnings;

=head1 NAME

memory - Plugin to monitor memory usage

=head1 CONFIGURATION

No configuration

=head2 Warning and Critical Values

Warning and critical values can be passed in environment variables as byte
values or they can be percentages using a % sign and the value will be
calculated dynamically by the plugin. Thus, to warn on swap at 50% the
configuration is:

	[memory]
	env.swap_warning 50%

Note that swap is calculated against the total swap amount (SwapTotal), and all
other fields are calculated against the total amount of memory (MemTotal).


=head1 AUTHORS

Original Author: Jimmy Olsen

=head2 CONTRIBUTORS

=over 4

=item Mike Fedyk

Slab, SwapCached, PageTables, VmallocUsed, Mapped, Active, Inactive,
2.4 Rmap & 2.6

=item Juha-Matti Tapio

Input on swap_cache and VMalloc Used on 64 bit machines.

=item Nicolai Langfeldt

Editor

=back

=head1 LICENSE

GPLv2

=head1 MAGIC MARKERS

 #%# family=auto
 #%# capabilities=autoconf

=cut

use Munin::Plugin;

if ($ARGV[0] and $ARGV[0] eq "autoconf") {
    if (-r "/proc/meminfo") {
	print "yes\n";
	exit 0;
    } else {
	print "no (/proc/meminfo not found)\n";
	exit 0;
    }
}

my %mems;
&fetch_meminfo;

if ($ARGV[0] and $ARGV[0] eq "config") {
    print "graph_args --base 1024 -l 0 --upper-limit ",$mems{'MemTotal'},"\n";
    print "graph_vlabel Bytes\n";
    print "graph_title Memory usage\n";
    print "graph_category system\n";
    print "graph_info This graph shows what the machine uses memory for.\n";
    print "graph_order ",
      "apps ";
    print "page_tables " if exists $mems{'PageTables'};

    print "per_cpu " if exists $mems{'Percpu'};

    print "swap_cache " if exists $mems{'SwapCached'};

    #print "vmalloc_used " if exists $mems{'VmallocUsed'};

    print "slab " if exists $mems{'Slab'};

    print "shmem " if exists $mems{'Shmem'};

    print "cached ",
      "buffers ",
	"free ",
	  "swap ",
	    "\n";

    print "apps.label apps\n";
    print "apps.draw AREA\n";
    print "apps.info Memory used by user-space applications.\n";
    print "apps.colour COLOUR0\n";
    print "buffers.label buffers\n";
    print "buffers.draw STACK\n";
    print "buffers.info Block device (e.g. harddisk) cache. ",
      "Also where \"dirty\" blocks are stored until written.\n";
    print "buffers.colour COLOUR5\n";
    print "swap.label swap\n";
    print "swap.draw STACK\n";
    print "swap.info Swap space used.\n";
    # Fixed color for swap: red. Otherwise the above conditional fields could
    # lead to color shifts due to changes of the kernel or other environment
    # situations.
    print "swap.colour COLOUR7\n";
    print "cached.label cache\n";
    print "cached.draw STACK\n";
    print "cached.info Parked file data (file content) cache.\n";
    print "cached.colour COLOUR4\n";
    print "free.label unused\n";
    print "free.draw STACK\n";
    print "free.info Wasted memory. Memory that is not ",
      "used for anything at all.\n";
    print "free.colour COLOUR6\n";
    if (exists $mems{'Shmem'}) {
	print "shmem.label shmem\n";
	print "shmem.draw STACK\n";
	print "shmem.info Shared Memory (SYSV SHM segments, tmpfs).\n";
	print "shmem.colour COLOUR9\n";
    }
    if (exists $mems{'Slab'}) {
	print "slab.label slab_cache\n";
	print "slab.draw STACK\n";
	print "slab.info Memory used by the kernel (major users ",
	  " are caches like inode, dentry, etc).\n";
	print "slab.colour COLOUR3\n";
    }
    if (exists $mems{'SwapCached'}) {
	print "swap_cache.label swap_cache\n";
	print "swap_cache.draw STACK\n";
	print "swap_cache.info A piece of memory that keeps track of pages ",
	  "that have been fetched from swap but not yet been modified.\n";
	print "swap_cache.colour COLOUR2\n";
    }
    if (exists $mems{'PageTables'}) {
	print "page_tables.label page_tables\n";
	print "page_tables.draw STACK\n";
	print "page_tables.info Memory used to map between virtual and ",
	  "physical memory addresses.\n";
	print "page_tables.colour COLOUR1\n";
    }
    if (exists $mems{'Percpu'}) {
	print "per_cpu.label per_cpu\n";
	print "per_cpu.draw STACK\n";
	print "per_cpu.info Per CPU allocations\n";
	print "per_cpu.colour COLOUR20\n";
    }
    if (exists $mems{'VmallocUsed'}) {
	print "vmalloc_used.label vmalloc_used\n";
	# print "vmalloc_used.draw STACK\n";
	print "vmalloc_used.draw LINE2\n";
	print "vmalloc_used.info 'VMalloc' (kernel) memory used\n";
	print "vmalloc_used.colour COLOUR8\n";
    }
    if (exists $mems{'Committed_AS'}) {
	print "committed.label committed\n";
	print "committed.draw LINE2\n";

	# Linux machines frequently overcommit - this is not a error
	# condition or even worrying.  But sometimes overcommit shows
	# memory leaks so we want to graph it.

	# print "committed.warning ", $mems{'SwapTotal'} + $mems{'MemTotal'}, "\n";
	print "committed.info The amount of memory allocated to programs. ",
	  "Overcommitting is normal, but may indicate memory leaks.\n";
	print "committed.colour COLOUR10\n";
    }
    if (exists $mems{'Mapped'}) {
	print "mapped.label mapped\n";
	print "mapped.draw LINE2\n";
	print "mapped.info All mmap()ed pages.\n";
	print "mapped.colour COLOUR11\n";
    }
    if (exists $mems{'Active'}) {
	print "active.label active\n";
	print "active.draw LINE2\n";
	print "active.info Memory recently used. Not reclaimed unless ",
	  "absolutely necessary.\n";
	print "active.colour COLOUR12\n";
    }
    if (exists $mems{'ActiveAnon'}) {
	print "active_anon.label active_anon\n";
	print "active_anon.draw LINE1\n";
	print "active_anon.colour COLOUR13\n";
    }
    if (exists $mems{'ActiveCache'}) {
	print "active_cache.label active_cache\n";
	print "active_cache.draw LINE1\n";
	print "active_cache.colour COLOUR14\n";
    }
    if (exists $mems{'Inactive'}) {
	print "inactive.label inactive\n";
	print "inactive.draw LINE2\n";
	print "inactive.info Memory not currently used.\n";
	print "inactive.colour COLOUR15\n";
    }
    if (exists $mems{'Inact_dirty'}) {
	print "inact_dirty.label inactive_dirty\n";
	print "inact_dirty.draw LINE1\n";
	print "inact_dirty.info Memory not currently used, but in need of ",
	  "being written to disk.\n";
	print "inact_dirty.colour COLOUR16\n";
    }
    if (exists $mems{'Inact_laundry'}) {
	print "inact_laundry.label inactive_laundry\n";
	print "inact_laundry.draw LINE1\n";
	print "inact_laundry.colour COLOUR17\n";
    }
    if (exists $mems{'Inact_clean'}) {
	print "inact_clean.label inactive_clean\n";
	print "inact_clean.draw LINE1\n";
	print "inact_clean.info Memory not currently used.\n";
	print "inact_clean.colour COLOUR18\n";
    }
    if (exists $mems{'KSM'}) {
    print "ksm_sharing.label KSM sharing\n";
    print "ksm_sharing.draw LINE2\n";
    print "ksm_sharing.colour FFBFFF\n";
    print "ksm_sharing.info Memory saved by KSM sharing\n";
    print "ksm_sharing.colour COLOUR19\n";
    }
    for my $field (qw(apps buffers swap cached free slab swap_cache page_tables per_cpu vmalloc_used committed mapped active active_anon active_cache inactive inact_dirty inact_laundry inact_clean shmem)) {
    	my ($warning, $critical) = get_thresholds($field);
	my $total = $mems{MemTotal};
	$total = $mems{SwapTotal} if($field eq "swap");

	$warning = adjust_threshold($warning, $total);
	$critical = adjust_threshold($critical, $total);

	print "$field.warning $warning\n" if defined $warning;
	print "$field.critical $critical\n" if defined $critical;
    }
    exit 0;
}

# Any optional value needs to be initialized to zero if it's used in a calculation below
# and is has not been set by &fetch_meminfo

if (exists $mems{'Slab'}) {
    print "slab.value ", $mems{'Slab'}, "\n";
} else {
    $mems{'Slab'} = 0;
}

if (exists $mems{'SwapCached'}) {
    print "swap_cache.value ", $mems{'SwapCached'}, "\n";
} else {
    $mems{'SwapCached'} = 0;
}

if (exists $mems{'PageTables'}) {
    print "page_tables.value ", $mems{'PageTables'}, "\n";
} else {
    $mems{'PageTables'} = 0;
}
if (exists $mems{'Percpu'}) {
    print "per_cpu.value ", $mems{'Percpu'}, "\n";
} else {
    $mems{'Percpu'} = 0;
}

if (exists $mems{'VmallocUsed'}) {
    print "vmalloc_used.value ", $mems{'VmallocUsed'}, "\n";
} else {
    $mems{'VmallocUsed'} = 0;
}

# Having default values for mandatory fields, even when they are not on the system
$mems{'MemFree'} ||= 0;
$mems{'Buffers'} ||= 0;
$mems{'Cached'} ||= 0;
$mems{'Slab'} ||= 0;
$mems{'PageTables'} ||= 0;
$mems{'Percpu'} ||= 0;
$mems{'SwapCached'} ||= 0;
$mems{'SwapFree'} ||= 0;
$mems{'SwapTotal'} ||= 0;

print "apps.value ", $mems{'MemTotal'}
	-$mems{'MemFree'}
	-$mems{'Buffers'}
	-$mems{'Cached'}
	-$mems{'Slab'}
	-$mems{'PageTables'}
	-$mems{'Percpu'}
        -$mems{'SwapCached'}
	,"\n";

print "free.value ", $mems{'MemFree'}, "\n";
print "buffers.value ", $mems{'Buffers'}, "\n";
print "cached.value ", $mems{'Cached'} - (defined($mems{'Shmem'}) ? $mems{'Shmem'} : 0), "\n";
print "swap.value ", $mems{'SwapTotal'} - $mems{'SwapFree'}, "\n";

print "shmem.value ", $mems{'Shmem'}, "\n"
  if exists $mems{'Shmem'};

print "committed.value ", $mems{'Committed_AS'}, "\n"
  if exists $mems{'Committed_AS'};

print "mapped.value ", $mems{'Mapped'}, "\n"
  if exists $mems{'Mapped'};

print "active.value ", $mems{'Active'}, "\n"
  if exists $mems{'Active'};

print "active_anon.value ", $mems{'ActiveAnon'}, "\n"
  if exists $mems{'ActiveAnon'};

print "active_cache.value ", $mems{'ActiveCache'}, "\n"
  if exists $mems{'ActiveCache'};

print "inactive.value ", $mems{'Inactive'}, "\n"
  if exists $mems{'Inactive'};

print "inact_dirty.value ", $mems{'Inact_dirty'}, "\n"
  if exists $mems{'Inact_dirty'};

print "inact_laundry.value ", $mems{'Inact_laundry'}, "\n"
  if exists $mems{'Inact_laundry'};

print "inact_clean.value ", $mems{'Inact_clean'}, "\n"
  if exists $mems{'Inact_clean'};

print "ksm_sharing.value ", $mems{'KSM'}, "\n"
  if exists $mems{'KSM'};

exit 0;

sub fetch_meminfo {
    open (IN, "/proc/meminfo") ||
      die "Could not open /proc/meminfo for reading: $!";
    while (<IN>) {
	if (/^(\w+):\s*(\d+)\s+kb/i) {
	    $mems{"$1"} = $2 * 1024;
	}
    }
    close (IN);

    # Only 2.6 and above has slab reported in meminfo, so read
    # slabinfo if it isn't in meminfo
    if (!$mems{Slab}) {
	&fetch_slabinfo;
    }
    # Support 2.4 Rmap VM based kernels
    if (!$mems{'Inactive'} && $mems{'Inact_dirty'} &&
      $mems{'Inact_laundry'} && $mems{'Inact_clean'}) {
	$mems{'Inactive'} = $mems{'Inact_dirty'} +
	  $mems{'Inact_laundry'} + $mems{'Inact_clean'};
    }
    &fetch_ksm;
}

sub fetch_slabinfo {
    # In 2.0 there is no slabinfo file, so return if the file doesn't open
    open (IN, "/proc/slabinfo") || return;
    my @slabinfo;
    my $tot_slab_pages = 0;
    my $slab_version = <IN>;
    if ($slab_version =~ /^slabinfo - version: 1.1/) {
	while (<IN>) {
	    if (!/^slabinfo/) {
		@slabinfo = split;
		$tot_slab_pages += $slabinfo[5];
	    }
	}
    }
    close (IN);

    $mems{'Slab'} = $tot_slab_pages * 4096 if $tot_slab_pages gt 0;
}

sub fetch_ksm {
    open (IN, "/sys/kernel/mm/ksm/run") || return;
    my $value = <IN>;
    if (defined $value) {
        chomp $value;
        return unless $value;
    }
    close (IN);

    open (IN, "/sys/kernel/mm/ksm/pages_sharing") || return;
    $value = <IN>;
    if (defined $value) {
        chomp $value;
        $mems{'KSM'} = $value * 4096;
    }
    close (IN);
}
