Repository
Munin (contrib)
Last change
2020-03-26
Graph Categories
Family
auto
Capabilities
Keywords
Language
Perl
License
GPL-2.0-only
Authors

xen-multi

Name

xen_multi - Munin multigraph plugin to monitor Xen domains activity

Applicable Systems

This plugin should work on any system running a Xen hypervisor and where xentop is installed. It also needs Munin 1.4.0 or higher, since it uses AREASTACK (available from 1.3.3) and multigraph (available from 1.4.0).

Configuration

xentop requires superuser privileges, so you need to include in your configuration:

[xen-multi]
user root
env.xenskip "<space separated module list>"

Modules: cput, cpup, mem, disk, net

Then restart munin-node and you’re done.

Interpretation

This plugin produces four different graphs: CPU usage, memory usage, disk IOs and network traffic.

In each graph, all Xen domains (including dom0) have their data stacked, giving an overall amount of resources used.

NOTE: xentop always reports 0 for dom0’s disk IOs and network traffic, but both graphs show its entry all the same, so each domain can keep its own color along the different graphs.

Cpu Time

This graph shows a percentage of the CPU time used by each domain.

Cpu Usage

This graph shows a percentage of the CPU percentage used by each domain.

Memory Usage

This graph shows the amount of memory (in bytes) used by each domain.

Disk Ios

This graph shows the number of disk read and write operations for each domain.

Network Traffic

This graph shows the amount of bits received and transmitted for each domain.

Acknowledgements

Michael Renner for the diskstats plugin which I borrowed some code from.

Version

0.90

Magic Markers

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

Author

Raphael HALIMI raphael.halimi@gmail.com Modified by Krisztian IVANCSO github-ivan@ivancso.net

License

GPLv2

#! /usr/bin/perl

# -*- perl -*-

=head1 NAME

xen_multi - Munin multigraph plugin to monitor Xen domains activity

=head1 APPLICABLE SYSTEMS

This plugin should work on any system running a Xen hypervisor and where xentop
is installed. It also needs Munin 1.4.0 or higher, since it uses AREASTACK
(available from 1.3.3) and multigraph (available from 1.4.0).

=head1 CONFIGURATION

xentop requires superuser privileges, so you need to include in your
configuration:

 [xen-multi]
 user root
 env.xenskip "<space separated module list>"

Modules: cput, cpup, mem, disk, net

Then restart munin-node and you're done.

=head1 INTERPRETATION

This plugin produces four different graphs: CPU usage, memory usage, disk IOs
and network traffic.

In each graph, all Xen domains (including dom0) have their data stacked, giving
an overall amount of resources used.

NOTE: xentop always reports 0 for dom0's disk IOs and network traffic, but
both graphs show its entry all the same, so each domain can keep its own color
along the different graphs.

=head2 CPU time

This graph shows a percentage of the CPU time used by each domain.

=head2 CPU usage

This graph shows a percentage of the CPU percentage used by each domain.

=head2 Memory usage

This graph shows the amount of memory (in bytes) used by each domain.

=head2 Disk IOs

This graph shows the number of disk read and write operations for each domain.

=head2 Network traffic

This graph shows the amount of bits received and transmitted for each domain.

=head1 ACKNOWLEDGEMENTS

Michael Renner for the C<diskstats> plugin which I borrowed some code from.

=head1 VERSION

0.90

=head1 MAGIC MARKERS

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

=head1 AUTHOR

Raphael HALIMI <raphael.halimi@gmail.com>
Modified by Krisztian IVANCSO <github-ivan@ivancso.net>

=head1 LICENSE

GPLv2

=cut

use strict;
use Munin::Plugin;

my $XEN_SKIP = $ENV{xenskip} || "";

# autoconf - quite simple
if ($ARGV[0] eq "autoconf") {
  if (`which xentop`) {
    print "yes\n";
  } else {
    print "no (xentop not found)\n";
  }
  exit 0;
}


#
# Common steps for both "config" and a normal run
#

#
# trim_label
#
# Trims a given label to it's non-wrapping size
# $type = 'pos' for normal graphs and 'neg' for mirror graphs
# pos: nnn.nnU_ times 4
# neg: nnn.nnU/nnn.nnU_ times 4

sub trim_label {
  my ($type, $label) = @_; my $data_characters;
  my ($graph_width, $graph_border_width, $padding_characters, $pixels_per_character) = (497,3,5,6);
  if ($type eq 'pos') {$data_characters = 32;} elsif ($type eq 'neg') {$data_characters = 64;} else {return $label;}

  my $available_characters = abs(($graph_width+$graph_border_width)/$pixels_per_character)-$padding_characters-$data_characters;

  # If the label is longer than the available space, we write as many
  # characters as we can on both left and right, and two dots in the middle
  if ( $available_characters < length $label ) {
    my $center = ($available_characters-2)/2;
    $label = substr($label,0,($center)) . '..' . substr($label,-$center);
  }

  return $label;
}

# Global variables
my (%domains,$domain,@domainlist,$munindomain,$cpusecs,$cpupercent,$memk,$nettxk,$netrxk,$vbdrd,$vbdwr);

# There is a bug in xentop, its output is too high in rare cases.
# https://lists.xenproject.org/archives/html/xen-users/2019-04/msg00020.html
# Workaround to process 3 iterations after the first one (4 iterations) and
# choose the lowest cpusecs value line.

open (XENTOP,"xentop -b -f -i4 |") or die "Could not execute xentop, $!";

# Now we build a hash of hashes to store information
while (<XENTOP>) {
  # Some cleaning first
  s/^\s+//; chomp;
  # Skip the headers
  next if /^NAME/;

  # We define only what we need
  ($domain,undef,$cpusecs,$cpupercent,$memk,undef,undef,undef,undef,undef,$nettxk,$netrxk,undef,undef,$vbdrd,$vbdwr,undef,undef,undef) = split(/\s+/);

  # We have to store the sanitised domain name in a separate variable
  $domains{$domain}{'munindomain'} = clean_fieldname($domain);

  # We need the remaining data only for a normal run
  if ($ARGV[0] eq "") {
    # the cnt key counts the iterations
    # - skip the first one because xentop gives 0% cpu for the first iteration ( cnt > 1 )
    # - process if not processed yet ( !defined ) or if current cpusecs less than any earlier
    # normally the cpusecs is monotonous per domain as it grows so a smaller value means that the previous one was wrong

    $domains{$domain}{'cnt'}++;
    if ( $domains{$domain}{'cnt'} > 1 && ( !defined $domains{$domain}{'cpusecs'} || $cpusecs < $domains{$domain}{'cpusecs'} ) ) {
      $domains{$domain}{'cpusecs'} = $cpusecs;
      $domains{$domain}{'cpupercent'} = $cpupercent;
      $domains{$domain}{'mem'} = $memk;
      $domains{$domain}{'nettx'} = $nettxk;
      $domains{$domain}{'netrx'} = $netrxk;
      $domains{$domain}{'vbdrd'} = $vbdrd;
      $domains{$domain}{'vbdwr'} = $vbdwr;
    }
  }
}

@domainlist = sort(keys(%domains));

#
# config - quite simple, too
#

if ($ARGV[0] eq "config") {
  if ($XEN_SKIP !~ /cput/) {
		print "multigraph xen_cpu_time\n";
		print "graph_title Xen domains CPU time\n";
		print "graph_args --base 1000 -l 0\n";
		print "graph_vlabel cpu seconds\n";
		print "graph_scale no\n";
		print "graph_category virtualization\n";
		print "graph_info This graph shows CPU time for each Xen domain.\n";
		for $domain (@domainlist) {
			print "$domains{$domain}{'munindomain'}_cpu_time.label ".trim_label('pos',$domain)."\n";
			print "$domains{$domain}{'munindomain'}_cpu_time.type DERIVE\n";
			print "$domains{$domain}{'munindomain'}_cpu_time.cdef $domains{$domain}{'munindomain'}_cpu_time,100,*\n";
			print "$domains{$domain}{'munindomain'}_cpu_time.min 0\n";
			print "$domains{$domain}{'munindomain'}_cpu_time.draw AREASTACK\n";
		}
  }

  if ($XEN_SKIP !~ /cpup/) {
		print "\nmultigraph xen_cpu\n";
		print "graph_title Xen domains CPU utilization\n";
		print "graph_args --base 1000 -l 0 --upper-limit 100\n";
		print "graph_vlabel %\n";
		print "graph_scale no\n";
		print "graph_category virtualization\n";
		print "graph_info This graph shows CPU utilization for each Xen domain.\n";
		for $domain (@domainlist) {
			print "$domains{$domain}{'munindomain'}_cpu.label ".trim_label('pos',$domain)."\n";
			print "$domains{$domain}{'munindomain'}_cpu.draw AREASTACK\n"
		}
  }

  if ($XEN_SKIP !~ /mem/) {
		print "\nmultigraph xen_mem\n";
		print "graph_title Xen domains memory usage\n";
		print "graph_args --base 1024 -l 0\n";
		print "graph_vlabel bytes\n";
		print "graph_category virtualization\n";
		print "graph_info This graph shows memory usage for each Xen domain.\n";
		for $domain (@domainlist) {
			print "$domains{$domain}{'munindomain'}_mem.label ".trim_label('pos',$domain)."\n";
			print "$domains{$domain}{'munindomain'}_mem.cdef $domains{$domain}{'munindomain'}_mem,1024,*\n";
			print "$domains{$domain}{'munindomain'}_mem.draw AREASTACK\n";
		}
  }

  if ($XEN_SKIP !~ /net/) {
		print "\nmultigraph xen_net\n";
		print "graph_title Xen domains network traffic\n";
		print "graph_args --base 1000\n";
		print "graph_vlabel bits per \${graph_period} in (-) / out (+)\n";
		print "graph_category virtualization\n";
		print "graph_info This graph shows network traffic for each Xen domain.\n";
		for $domain (@domainlist) {
			print "$domains{$domain}{'munindomain'}_netrx.label none\n";
			print "$domains{$domain}{'munindomain'}_netrx.cdef $domains{$domain}{'munindomain'}_netrx,8192,*\n";
			print "$domains{$domain}{'munindomain'}_netrx.type COUNTER\n";
			print "$domains{$domain}{'munindomain'}_netrx.graph no\n";
			print "$domains{$domain}{'munindomain'}_nettx.label ".trim_label('neg',$domain)."\n";
			print "$domains{$domain}{'munindomain'}_nettx.cdef $domains{$domain}{'munindomain'}_nettx,8192,*\n";
			print "$domains{$domain}{'munindomain'}_nettx.type COUNTER\n";
			print "$domains{$domain}{'munindomain'}_nettx.draw AREASTACK\n";
			print "$domains{$domain}{'munindomain'}_nettx.negative $domains{$domain}{'munindomain'}_netrx\n";
		}
  }

  if ($XEN_SKIP !~ /disk/) {
		print "\nmultigraph xen_disk\n";
		print "graph_title Xen domains disk IOs\n";
		print "graph_args --base 1000\n";
		print "graph_vlabel IOs per \${graph_period} read (-) / write (+)\n";
		print "graph_category virtualization\n";
		print "graph_info This graph shows disk IOs for each Xen domain.\n";
		for $domain (@domainlist) {
			print "$domains{$domain}{'munindomain'}_vbdrd.label none\n";
			print "$domains{$domain}{'munindomain'}_vbdrd.type COUNTER\n";
			print "$domains{$domain}{'munindomain'}_vbdrd.graph no\n";
			print "$domains{$domain}{'munindomain'}_vbdwr.label ".trim_label('neg',$domain)."\n";
			print "$domains{$domain}{'munindomain'}_vbdwr.type COUNTER\n";
			print "$domains{$domain}{'munindomain'}_vbdwr.draw AREASTACK\n";
			print "$domains{$domain}{'munindomain'}_vbdwr.negative $domains{$domain}{'munindomain'}_vbdrd\n";
		}
  }

  exit 0;
}


#
# Normal run
#

if ($XEN_SKIP !~ /cput/) {
	print "multigraph xen_cpu_time\n";
	for $domain (@domainlist) {
		print "$domains{$domain}{'munindomain'}_cpu_time.value $domains{$domain}{'cpusecs'}\n";
	}
}

if ($XEN_SKIP !~ /cpup/) {
	print "\nmultigraph xen_cpu\n";
	for $domain (@domainlist) {
		print "$domains{$domain}{'munindomain'}_cpu.value $domains{$domain}{'cpupercent'}\n";
	}
}

if ($XEN_SKIP !~ /mem/) {
	print "\nmultigraph xen_mem\n";
	for $domain (@domainlist) {
		print "$domains{$domain}{'munindomain'}_mem.value $domains{$domain}{'mem'}\n";
	}
}

if ($XEN_SKIP !~ /net/) {
	print "\nmultigraph xen_net\n";
	for $domain (@domainlist) {
		print "$domains{$domain}{'munindomain'}_nettx.value $domains{$domain}{'nettx'}\n";
		print "$domains{$domain}{'munindomain'}_netrx.value $domains{$domain}{'netrx'}\n";
	}
}

if ($XEN_SKIP !~ /disk/) {
	print "\nmultigraph xen_disk\n";
	for $domain (@domainlist) {
		print "$domains{$domain}{'munindomain'}_vbdrd.value $domains{$domain}{'vbdrd'}\n";
		print "$domains{$domain}{'munindomain'}_vbdwr.value $domains{$domain}{'vbdwr'}\n";
	}
}