#! /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 "" 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 plugin which I borrowed some code from. =head1 VERSION 0.90 =head1 MAGIC MARKERS #%# family=auto #%# capabilities=autoconf =head1 AUTHOR Raphael HALIMI Modified by Krisztian IVANCSO =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 () { # 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"; } }