#!/usr/bin/perl -w
# vim:ts=4

#
# contrib/gif_lvmsar
#
# Copyright (C) 1999 Torsten Neumann
#
# gif_lvmsar is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# gif_lvmsar is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LVM; see the file COPYING.  If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#

my $VERSION="0.21";
use strict;
use Getopt::Long;

my $HOME        =   $ENV{'HOME'} || ".";
my $CONFIGFILE  =   "$HOME/.gif_lvmsar";

my $TMPDIR = $ENV{'TMPDIR'} || '/tmp';
my $TEMP_FILE = "$TMPDIR/gif_lvmsar.$$";

my $MAX_TIME_DIFF = 0;
my $DATE	=	"/bin/date";
my $GNUPLOT	=	"/usr/bin/gnuplot";
my $MOUNT	=	"/bin/mount";

sub usage {
	print <<EOF
usage $0: <options>
  -file,-f             logfile generated from lvmsadc
  -outputdir,-o        directory where to place generated gifs
  -format              picture format ( gif or pbm )
  -start,-s            start time
  -end,-e              end time
  -device,-lvol,-d     only generate pictures for device
  -min_y               minimal value displayed on y-axis
  -max_y               maximal value displayed on y-axis
  -gif_width,-width    width of the gif
  -gif_height,-height  height of the gif
  -readonly            only look at reading statistics
  -writeonly           only look at writing statistics
  -extendsonly         only generate graphik for accesses on logical extends
  -noextends           do not generate blockgraphik for logical extends
  -crondiff            time diff in your crontab for lvmsadc
  -path_to_gnuplot     path to gnuplot program
  -path_to_mount       path to mount program
  -path_to_date        path to date program
EOF
;
	exit (1);
}

sub clean_up {
	unlink $TEMP_FILE if ( defined $TEMP_FILE );
	exit 0;
}

$SIG{'HUP'} = \&clean_up;
$SIG{'INT'} = \&clean_up;
$SIG{'TERM'} = \&clean_up;

END {
        clean_up;
};

# Command Line Parameters

my ( $file, $start_str, $end_str, $device, $min_y, $max_y, 
     $gif_width, $gif_height, $readonly, $writeonly,
     $format, $outputdir, $noextends, $extendsonly );

my ( $start, $end, $g_start, $g_end );
my ( %VG, %DIFF, %R_EXTEND, %W_EXTEND, %MOUNTS );

sub read_config_file {
	if ( -f $CONFIGFILE ) {
		open CONF, $CONFIGFILE or die "can't open $CONFIGFILE: $!\n";
		while (<CONF>) {
			chomp;
			next if ( m/^#/ or m/^$/ );
			my ($var,$val) = split ( /=/ );
			if ( $var eq "file" ) {
				$file = $val;
			} elsif ( $var eq "start" ) {
				$start_str = $val;
			} elsif ( $var eq "end" ) {
				$end_str = $val;
			} elsif ( $var eq "device" or $var eq "lvol" ) {
				$device = $val;
			} elsif ( $var eq "outputdir" ) {
				$outputdir = $val;
			} elsif (  $var eq "min_y" ) {
				$min_y = $val;
			} elsif (  $var eq "max_y" ) {
				$max_y = $val;
			} elsif (  $var eq "gif_width" or $var eq "width" ) {
				$gif_width = $val;
			} elsif (  $var eq "gif_height" or $var eq "height" ) {
				$gif_height = $val;
			} elsif ( $var eq "readonly" ) {
				$readonly = 1;
			} elsif ( $var eq "writeonly" ) {
				$writeonly = 1;
			} elsif ( $var eq "noextends" ) {
				$noextends = 1;
			} elsif ( $var eq "extendsonly" ) {
				$extendsonly = 1;
			} elsif ( $var eq "crondiff" ) {
				$MAX_TIME_DIFF=$val;
			} elsif ( $var eq "path_to_gnuplot" ) {
				$GNUPLOT = $val;
			} elsif ( $var eq "path_to_mount" ) {
				$MOUNT = $val;
			} elsif ( $var eq "path_to_date" ) {
				$DATE = $val;
			} elsif ( $var eq "format" ) {
				$format = $val;
			} else {	
				die "illegal option \"$var\" in $CONFIGFILE\n";
			}
		}
		close CONF;
	}
}

sub convert_name {
	my ($result) = @_;
	($result) = split ( /\s+/, $result );
	$result =~ s#^/dev/##;
	$result =~ s#/#-#g;
	return $result;
}

sub get_mount_points {
	open MOUNTS, "$MOUNT|" or die "can't open pipe to $MOUNT: $!\n";
	while (<MOUNTS>) {
		chomp;
		my ( $partition,$mountpoint,$type) = (split ( /\s+/ ))[0,2,4];
		next if ( $type eq "nfs" or $type eq "iso9660" or $type eq "proc" or $type eq "devpts" );
		$partition = convert_name ( $partition );
		$MOUNTS{$partition} = $mountpoint;
	}
	close MOUNTS or die "can't close pipe to $MOUNT: $!\n";
}

sub get_mount_point {
	my ($lvol) = @_;
	if ( defined $MOUNTS{$lvol} ) {
		return $MOUNTS{$lvol};
	} else {
		return "not mounted";
	}
}

sub read_logfile {
	my $skip = defined $start ? 2 : 0;

    my ( $b,$r,$w );
	my ( $rs, $ws );
	my ( $td, $lt ) = ( 0, 0 );
	my ( %first, %R, %W );
	my $timestamp = 0;
	my $lvol;

	if ( $file eq "-" ) {
		*FILE=*STDIN;
	} else {
		open FILE, "$file" or die "can't open $file: $!\n";
	}
	while (<FILE>) {
		chomp;
		if ( /^[0-9]+$/ ) { # timestamp
			last if ( defined $end and $_ > $end );
			if ( defined $start ) {
				$skip = $start <= $_ ? 0 : 2;
			} else {
				$skip = 0;
			}
			$lt = $timestamp;
			$timestamp = $_;
			$td = $timestamp - $lt;
			next;
		}
		if ( /^\/dev/ ) {
			next if ( $skip == 2 );
			$lvol = convert_name ($_);
			if ( defined ($device) ) {
				$skip = $device eq $lvol ? 0 : 1;
			}
			$rs = 0;
			$ws = 0;
			$first{$lvol}++ if ( not $skip );
			next;
		}
		next if ( $skip );
		( $b, $r, $w ) = split ( /\s+/ );
		$rs += $r;
		$ws += $w;
		${$VG{$lvol}}{$timestamp} = [ $rs, $ws ];

		if ( ( not defined ($start) and $lt == 0 ) or 
                     ( defined ($start) and $first{$lvol} == 1 ) ) {
			${$R_EXTEND{$lvol}}[$b] = -$r;
			${$W_EXTEND{$lvol}}[$b] = -$w;
			next;
		}
		if ( ( $MAX_TIME_DIFF == 0 or $td >= $MAX_TIME_DIFF ) and $lt > 0 and $first{$lvol} > 1 ) {
			${$R_EXTEND{$lvol}}[$b] += $r;
			${$W_EXTEND{$lvol}}[$b] += $w;
			next;
		}
		${$R{$lvol}}[$b] = $r;
		${$W{$lvol}}[$b] = $w;
	}
	close FILE unless ( $file eq "-" );
	foreach my $key ( sort keys %R ) {
		for ( my $i = 0; $i <= $#{$R{$key}}; $i++ ) {
			${$R_EXTEND{$key}}[$i] += ${$R{$key}}[$i];
			${$W_EXTEND{$key}}[$i] += ${$W{$key}}[$i];
		}
	}
}

sub convert_date {
        my ($t) = @_;
        my $result;
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($t);
        $year += 1900;
        $mon++;
        $result = "$year-$mon-$mday-$hour-$min";
        return $result;
}

sub get_diff {
	my ( $lvol ) = @_;
	my ( $r,$w,$t );
	my ( $rd, $wd, $td );
	my ( $lr, $lw, $lt ) = ( 0, 0, 0 );
	my ( $rv, $wv );
	foreach $t ( sort keys %{$VG{$lvol}} ) {
		$r = @{${$VG{$lvol}}{$t}}[0];
		$w = @{${$VG{$lvol}}{$t}}[1];
		my $td = $t - $lt;
		my $rd = $r - $lr;
		my $wd = $w - $lw;
		if ( ( $MAX_TIME_DIFF == 0 or $td < $MAX_TIME_DIFF ) and $rd >= 0 and $wd >= 0 ) {
			$rv = $rd / $td;
			$wv = $wd / $td;
			${$DIFF{$lvol}}{$t} = [ $rv, $wv ];
		} else {
			if ( $lt > 0 ) {
				my $h = $lt+60;
				${$DIFF{$lvol}}{$h} = [ 0, 0 ];
				${$DIFF{$lvol}}{$t} = [ 0, 0 ];
			}
		}
		$lt = $t;
		$lr = $r;
		$lw = $w;
	}
}

sub print_diff {
	my ( $lvol, $file ) = @_;
	my $t_str;
	if ( not defined $g_start ) {
		$g_start = (sort keys %{$DIFF{$lvol}})[0] - $MAX_TIME_DIFF;
	}
	if ( not defined $g_end ) {
		$g_end = ( reverse sort keys %{$DIFF{$lvol}})[0] + $MAX_TIME_DIFF;
	}
	open FILE, ">$file" or die "can't open $file: $!\n";
	foreach my $t ( sort keys %{$DIFF{$lvol}} ) {
		$t_str = convert_date ($t);
		print FILE "$t_str @{${$DIFF{$lvol}}{$t}}[0] @{${$DIFF{$lvol}}{$t}}[1]\n";
	}
	close FILE;
}

sub plot_diff {
	my ( $lvol, $file ) = @_;
	return if ( not -s $file );
	my $lines = 0;
	my $useful = 0;
	my $g_start_str = convert_date ( $g_start );
	my $g_end_str   = convert_date ( $g_end );
	open WC, "$file" or die "can't open $file: $!\n";
	while (<WC>) {
		chomp;
		my ($t,$r,$w) = split ( /\s+/ );
		$lines++;
		$useful = 1 if ( $r or $w );
	}
	close (WC);
	return if ( $lines < 5 );
	return if ( not $useful );

	my $output_file = "$outputdir/$lvol.$format";
	my $title = $lvol;
	$title =~ s#-#/#;
	my $mounted = get_mount_point($lvol);

	open GNUPLOT, "|$GNUPLOT" or die "can't fork: $!\n";
	print GNUPLOT "set title \"/dev/$title mounted on $mounted\"\n";
	print GNUPLOT "set data style lines\n";
	print GNUPLOT "set xdata time\n";
	print GNUPLOT "set timefmt \"%Y-%m-%d-%H-%M\"\n";
	print GNUPLOT "set format x \"%H:%M\\n%m-%d\"\n";
	print GNUPLOT "set xrange [ \"$g_start_str\" to \"$g_end_str\" ]\n";
	print GNUPLOT "set grid\n";
	if ( $format eq "pbm" ) {
		print GNUPLOT "set terminal pbm small color\n";
		print GNUPLOT "set size $gif_width,$gif_height\n";
	} else {
		print GNUPLOT "set terminal gif size $gif_width,$gif_height\n";
	}
	print GNUPLOT "set yrange [ $min_y to $max_y ]\n" if ( defined $min_y or defined $max_y );
	print GNUPLOT "set output \"$output_file\"\n";
	if ( defined $readonly ) {
		print GNUPLOT "plot \"$file\" using 1:2 title \"reads\"\n";
	} elsif ( defined $writeonly ) {
		print GNUPLOT "plot \"$file\" using 1:3 title \"writes\"\n";
	} else {
		print GNUPLOT "plot \"$file\" using 1:2 title \"reads\", \"$TEMP_FILE\" using 1:3 title \"writes\"\n";
	}
	close GNUPLOT or die "closing pipe: $!\n";
}

sub plot_extend {
	my ($lvol, $flag) = @_;
	my $max = 0;
	my %H;

	return if ( defined $readonly and $flag eq "write" );
	return if ( defined $writeonly and $flag eq "read" );

	if ( $flag eq 'read' ) {
		%H = %R_EXTEND;
	} else {
		%H = %W_EXTEND;
	}

	my $output_file = "$outputdir/$flag-$lvol.$format";
	my $title = $lvol;
	$title =~ s#-#/#;
	my $mounted = get_mount_point($lvol);

	foreach my $b ( @{$H{$lvol}} ) {
		$max = $b > $max ? $b : $max;
	}
	return if ( not $max );
	$max += $max / 10;
		
	open GNUPLOT, "|$GNUPLOT";
	print GNUPLOT "set data style impulses\n";
	print GNUPLOT "set grid y\n";
	print GNUPLOT "set xrange [ -1 to $#{$H{$lvol}} +2 ]\n";
	print GNUPLOT "set yrange [ 0 to $max ]\n";
	if ( $format eq "pbm" ) {
		print GNUPLOT "set terminal pbm small color\n";
		print GNUPLOT "set size $gif_width,$gif_height\n";
	} else {
		print GNUPLOT "set terminal gif size $gif_width,$gif_height\n";
	}
	print GNUPLOT "set output \"$output_file\"\n";
	print GNUPLOT "set title \"/dev/$title [$flag] mounted on $mounted\"\n";
	print GNUPLOT "plot \"-\" title \"\"\n";

        foreach my $b ( @{$H{$lvol}} ) {
                print GNUPLOT "$b\n";
        }
	close GNUPLOT;
}

sub main {
	read_config_file();
	my $result = GetOptions (
			"file=s"	=>	\$file,
			"f=s"		=>      \$file,
			"start=s"	=>	\$start_str,
			"s=s"		=>	\$start_str,
			"end=s"		=>	\$end_str,
			"e=s"		=>      \$end_str,
			"device=s"	=>	\$device,
			"lvol=s"	=>	\$device,
			"d=s"		=>	\$device,
			"outputdir=s"   =>      \$outputdir,
			"o=s"		=>      \$outputdir,
			"min_y=i"       =>      \$min_y,
			"max_y=i"       =>      \$max_y,
			"gif_width=i"   =>      \$gif_width,
			"width=i"	=>      \$gif_width,
			"gif_height=i"  =>      \$gif_height,
			"height=i"	=>      \$gif_height,
			"readonly"      =>      \$readonly,
			"writeonly"     =>      \$writeonly,	
			"noextends"	=>	\$noextends,
			"extendsonly"	=>	\$extendsonly,
			"crondiff=s"		=>	\$MAX_TIME_DIFF,
			"path_to_gnuplot=s"	=>	\$GNUPLOT,
			"path_to_mount=s"	=>	\$MOUNT,
			"path_to_date=s"	=>	\$DATE,
			"format=s"			=>	\$format,
		);

	usage if ( not $result );
	usage if ( not defined $file );
	usage if ( not defined $outputdir );
	usage if ( defined $readonly and defined $writeonly );
	$gif_width ||= 640;
	$gif_height ||= 480;
	if ( defined $max_y ) {
		$min_y ||= 0;
	} else {
		usage if ( defined $min_y );
	}
	$format ||= "gif";
	die "outputformat must be 'gif' or 'pbm'\n"
		if ( $format ne "gif" and $format ne "pbm" );
	if ( $format eq "pbm" ) {
		$gif_width = $gif_width / 640 ;
		$gif_height = $gif_height / 480;
	}

	chomp ($start = `$DATE +%s -d "$start_str"`) if defined ( $start_str );
	$g_start = $start - 60 if defined ( $start );
	chomp ($end = `$DATE +%s -d "$end_str"`) if defined ( $end_str );
	$g_end = $end + 60 if defined ( $end );
	die "--start = $start must be less then --end = $end\n" if ( defined $start and defined $end and $start > $end );
	$device = convert_name ($device) if (defined $device);

	get_mount_points;
	read_logfile;
	if ( defined $device ) {
		if ( not defined $extendsonly ) {
			get_diff ( $device );
			print_diff ( $device, $TEMP_FILE );
			plot_diff ( $device, $TEMP_FILE );
		}
		if ( not defined $noextends ) {
			plot_extend ( $device, 'read' );
			plot_extend ( $device, 'write' );
		}
	} else {
		for my $k ( sort keys %VG ) {
			if ( not defined $extendsonly ) {
				get_diff ( $k );
				print_diff ( $k, $TEMP_FILE );
				plot_diff ( $k, $TEMP_FILE );
			}
			if ( not defined $noextends ) {
				plot_extend ( $k, 'read' );
				plot_extend ( $k, 'write' );
			}
		}
	}
}

main;
