#!/usr/bin/perl -w # Sun JVM minor GC statistics. Parses a verbose log of minor GC # stats. Reads the log file and sums time in seconds spent on GC and # number of times it is performed. These numbers are saved in a state # file. The next time the plugin is run it only reads from the point # it quit the last time and increases the sums based on the lines # found in the last portion of the log. Thus we obtain counters. The # counters are reset when the log is rotated. # The two numbers are graphed in _one_ graph. Where it has been used # until now these two numbers have been in the same order of # magnitude. # Configuration (common with the other sun_jvm_* plugins in this family): # [jvm_sun_*] # env.logfile /var/foo/java.log (default: /var/log/munin/java.log) # env.graphtitle (default: "Sun Java") # env.grname (default: "sun-jvm". Used for state file-name) # You need to configure your Sun JVM with these options: # -verbose:gc # -Xloggc:/var/log/app/jvm/gc.log # -XX:+PrintGCTimeStamps # -XX:+PrintGCDetails # History: # This plugin was developed by various people over some time - no logs # of this has been found. - In 2006 significant contributions was # financed by NRK (Norwegian Broadcasting Coproration) and performed # by Nicolai Langfeldt of Linpro AS in Oslo, Norway. # $Id: $ use strict; my $logfile = $ENV{logfile} || "/var/log/app/jvm/gc.log"; my $grtitle = $ENV{graphtitle} || 'Sun Java'; my $grname = $ENV{graphtitle} || 'sun-jvm'; my $statefile = "/var/lib/munin/plugin-state/plugin-java_sun_${grname}_minorgcs.state"; my $pos = 0; my $count = 0; my $seconds = 0; my $startsize; my $timespent; my $totaltimespent=0; if ( $ARGV[0] and $ARGV[0] eq "config" ) { print "graph_title $grtitle minor GCs pr minute\n"; print "graph_args --base 1000 -l 0 --rigid\n"; print "graph_scale no\n"; print "graph_category virtualization\n"; print "graph_period minute\n"; print "gcs.label Number of GCs\n"; print "gcs.type DERIVE\n"; print "gcs.min 0\n"; print "time.label Seconds spent on GC\n"; print "time.type DERIVE\n"; print "time.min 0\n"; exit 0; } if (-l $statefile) { die("$statefile is a symbolic link, refusing to touch it."); } if (! -f $logfile) { print "gcs.value U\n"; print "time.value U\n"; exit 0; } if (-f "$statefile") { open (IN, "$statefile") or exit 4; ($pos,$count,$timespent) = split(/:/,); close IN; } $startsize = (stat $logfile)[7]; if ($startsize < $pos) { # Log rotated $pos = 0; } ($pos, $count, $timespent) = parseFile ($logfile, $pos, $count, $timespent); print "gcs.value $count\n"; print "time.value ",int($timespent),"\n"; open (OUT, ">$statefile") or die "Could not open $statefile for reading: $!\n"; print OUT "$pos:$count:$timespent\n"; close OUT; sub parseFile { my ($fname, $start, $count, $timespent) = @_; my @secs; open (LOGFILE, $fname) or die "Could not open $fname: $!\n"; # Stat filehandle after open - avoids race with logrotater or # restart or whatever. my $stop = $startsize = (stat LOGFILE)[7]; if ($startsize < $start) { # Log rotated $start = 0; } seek (LOGFILE, $start, 0) or die "Could not seek to $start in $fname: $!\n"; while (tell (LOGFILE) < $stop) { my $line =; chomp ($line); # Log format: 35.037: [GC 35.038: [DefNew: 166209K->11364K(174080K), 0.2106410 secs] 175274K->26037K(1721472K), 0.2107680 secs] if ($line =~ /\[GC.+ (\d+\.\d+) secs\]/) { $count++; $timespent += $1; } } close(LOGFILE); return($stop,$count,$timespent); } # vim:syntax=perl