#!/usr/bin/perl -w # NEMo Version Information $VERSION='1.0.0'; use Sys::Hostname; $TcpdumpFlags = "-vvv -n -tt -u -s 1500"; $TcpdumpFilter = "port 2049 and src or dst "; %NfsV3Ops = ("null", 0, "getattr", 1, "setattr", 2, "lookup", 3, "access", 4, "readlink", 5, "read", 6, "write", 7, "create", 8, "mkdir", 9, "symlink", 10, "mknod", 11, "remove", 12, "rmdir", 13, "rename", 14, "link", 15, "readdir", 16, "readdirplus", 17, "fsstat", 18, "fsinfo", 19, "pathconf", 20, "commit", 21); # Verify license &VerifyLicense; # Grab the input parameters &ParseInput; print "Starting NEMo Collector with arguments:\n"; print " Output directory : $OutputDir\n"; print " Max data size (MB) : $MaxDataSize\n"; print " Duration (min) : $Duration\n"; print " Network : $NetworkInterface\n"; print " Server : $ServerIPAddress\n"; print " Tcpdump v$TcpdumpVersion : $Tcpdump\n"; print " Nfsstat : $Nfsstat\n"; sleep 1; $TimeStr = time(); # convert max data size into bytes $MaxDataSize *= 1000000; # convert duration into seconds $Duration *= 60; $host = hostname; # Grab nfsstat pre-profile before launching tcpdump system("$Nfsstat > $OutputDir/nemo_nfsstat_$host\_$TimeStr\_$ServerIPAddress\_pre.log"); # direct STDERR (for both parent and child process to dedicated log file. open (STDERR, ">$OutputDir/nemo_tcpd_$host\_$TimeStr\_$ServerIPAddress.log") || die "Cant redirect stderr"; # record arguments to tcpdump log file print STDERR "Starting NEMo Collector with arguments:\n"; print STDERR " Output directory : $OutputDir\n"; print STDERR " Max data size (MB) : $MaxDataSize\n"; print STDERR " Duration (min) : $Duration\n"; print STDERR " Network : $NetworkInterface\n"; print STDERR " Server : $ServerIPAddress\n"; print STDERR " Tcpdump v$TcpdumpVersion : $Tcpdump\n"; print STDERR " Nfsstat : $Nfsstat\n"; print STDERR "\n\n\n"; print STDERR "Tcpdump raw output follows:\n"; # launch tcpdump $pid = open(TRACELOGOUT, "-|"); if ($pid == 0) #child { system("$Tcpdump -i $NetworkInterface $TcpdumpFlags $TcpdumpFilter $ServerIPAddress"); } else { #parent $i = 0; $size = 0; $total_size = 0; open (OUTFILE, ">$OutputDir/nemo_workload_$host\_$TimeStr\_$ServerIPAddress\_$i.log") || die "Cant open workload file"; #print "I am parent\n"; print "Launching tcpdump..\n"; $RunTime = 0; $PrevTime = time(); $Timeout = 60; $SIG{'ALRM'} = \&EvalTimeout; $SIG{'INT'} = \&CleanUp; alarm $Timeout; while ( ) { # tcpdump versions 3.8 and 3.9 appear to generate the same format # output under the specified options, so use the same tokenize # routine. if (($TcpdumpVersion eq 3.8) || ($TcpdumpVersion eq 3.9)) { &Tokenize_3_8($_); } # tcpdump versions 3.6 and 3.7 appear to generate the same format # output under the specified options, so use the same tokenize # routine. if (($TcpdumpVersion eq 3.6) || ($TcpdumpVersion eq 3.7)) { &Tokenize_3_6($_); } $line_size = length $_; if ($line_size > 0) { print OUTFILE $_; $size += $line_size; if ($size > 100000000) { #Passed the 100MB mark, switch files close (OUTFILE); $i++; $TimeStr = time(); open (OUTFILE, ">$OutputDir/nemo_workload_$host\_$TimeStr\_$ServerIPAddress\_$i.log") || die "Cant open workload file"; $size = 0; } $total_size += $line_size; if (($total_size) > $MaxDataSize) { last; } } } system("kill -SIGINT $pid"); &ReportEnd; close (OUTFILE); } # Collector usage sub Usage { print "Usage\n"; print "$0\n"; print "\t --ifc NetworkInterface\n"; print "\t --ip FilerIPAddr\n"; print "\t --tcpdump TcpdumpPath\n"; print "\t --nfsstat NfsstatPath\n"; print "\t [--dir OutputDir]\n"; print "\t [--duration Duration(Min)]\n"; print "\t [--maxdata MaxDataSize(MB)]\n"; print "\t [--help]\n"; print "\t [--version]\n"; print "\n\n"; print " --ifc: Interface to listen on. Ex. eth0 \n"; print " --ip: Filer IP address.\n"; print " --tcpdump: Pathname of local tcpdump utility. Ex. /usr/sbin/tcpdump \n"; print " --nfsstat: Pathname of local nfsstat utility. Ex. /usr/sbin/nfsstat \n"; print " --dir: Directory to which all output files are sent.\n"; print " Default is /tmp\n"; print " --duration: Max number of minutes to run collector\n"; print " Default is 60 min\n"; print " --maxdata: Maximum quantity of data gathered by collector\n"; print " Default is 100MB\n"; } # Version Information sub Version { local $_=$0; s/.*?\///; print "$_ $VERSION\n"; } # Verify license sub VerifyLicense { print "Do you accept the terms in the Gear6 software license agreement (Gear6_license.txt) (Yes/No): "; chomp ($LicenseAccept= ); if (($LicenseAccept ne "Yes") && ($LicenseAccept ne "yes")) { print "Unable to continue unless license terms are accepted.\n"; exit; } } # Parse input parameters sub ParseInput { # Set up defaults $OutputDir = "/tmp"; $MaxDataSize = 100; $Duration = 60; $ServerIPAddress = ""; $NetworkInterface = ""; $Tcpdump = ""; $Nfsstat = ""; # Read all available arguments print "\nProcessing arguments..\n"; $i = 0; while ($i < $#ARGV+1) { if ($ARGV[$i] eq "--ifc") { $NetworkInterface = $ARGV[$i+1]; $i = $i+2; next; } if ($ARGV[$i] eq "--ip") { $ServerIPAddress = $ARGV[$i+1]; $i = $i+2; next; } if ($ARGV[$i] eq "--maxdata") { $MaxDataSize = $ARGV[$i+1]; $i = $i+2; next; } if ($ARGV[$i] eq "--duration") { $Duration = $ARGV[$i+1]; $i = $i+2; next; } if ($ARGV[$i] eq "--tcpdump") { $Tcpdump = $ARGV[$i+1]; # Locate tcpdump and determine version &VerifyTcpdump; $i = $i+2; next; } if ($ARGV[$i] eq "--nfsstat") { $Nfsstat = $ARGV[$i+1]; # Locate nfsstat &VerifyNfsstat; $i = $i+2; next; } if ($ARGV[$i] eq "--dir") { $OutputDir = $ARGV[$i+1]; &VerifyOutputDirectory; $i = $i+2; next; } if ($ARGV[$i] eq "--version") { &Version; exit; } &Usage; exit; } #Query for any required arguments that are missing if ($ServerIPAddress eq "") { print "Filer IP address: "; chomp ($ServerIPAddress = ); if ($ServerIPAddress eq "") { print "Unable to continue without a filer IP address\n"; exit; } } if ($NetworkInterface eq "") { print "Network Interface (eth0): "; chomp ($NetworkInterface = ); if ($NetworkInterface eq "") { print "Using eth0 as default\n"; $NetworkInterface = "eth0"; } } if ($Tcpdump eq "") { print "tcpdump path (/usr/sbin/tcpdump): "; chomp ($Tcpdump = ); if ($Tcpdump eq "") { print "Using /usr/sbin/tcpdump as default\n"; $Tcpdump = "/usr/sbin/tcpdump"; } # Locate tcpdump and determine version &VerifyTcpdump; } if ($Nfsstat eq "") { print "nfsstat path (/usr/sbin/nfsstat): "; chomp ($Nfsstat = ); if ($Nfsstat eq "") { print "Using /usr/sbin/nfsstat as default\n"; $Nfsstat = "/usr/sbin/nfsstat"; } # Locate nfsstat &VerifyNfsstat; } } # Make sure the output directory is on a local file system sub VerifyOutputDirectory { if (!(-d $OutputDir)) { print "$OutputDir is not a directory\n\n"; &Usage; exit; } $stat_cmd = `which stat`; chomp($stat_cmd); if ( -e $stat_cmd) { $stat_output = `$stat_cmd -f $OutputDir`; if ($stat_output =~ /nfs/) { print "$OutputDir is on an NFS file system\nPlease provide a directory on a local file system\n"; exit; } } else { print "Unable to find stat command: Will not verify that output directory is on a local file system\n"; } } # Locate tcpdump and determine version sub VerifyTcpdump { if (!(-e $Tcpdump)) { print "Unable to locate tcpdump at $Tcpdump\n"; exit; } else { $vers = `$Tcpdump --version 2>&1`; $vers =~ /tcpdump version (\d.\d)/; $TcpdumpVersion = $1; # Crosscheck tcpdump version against supported versions if (($TcpdumpVersion eq 3.6) || ($TcpdumpVersion eq 3.7) || ($TcpdumpVersion eq 3.8) || ($TcpdumpVersion eq 3.9)) { print "Tcpdump version $TcpdumpVersion supported\n"; } else { print "Tcpdump version $TcpdumpVersion not supported\n"; exit; } } } # Locate nfsstat sub VerifyNfsstat { if (!(-e $Nfsstat)) { print "Unable to locate nfsstat at $Nfsstat\n"; exit; } } # Check to see if we've run for enough time sub EvalTimeout { $timeval = time(); $RunTime = $RunTime + $timeval - $PrevTime; $PrevTime = $timeval; #print "Timer fired: Runtime is $RunTime\n"; if ($RunTime >= $Duration) { system("kill -SIGINT $pid"); &ReportEnd; close (OUTFILE); exit; } else { alarm $Timeout; } } # Report time run and data gathered sub ReportEnd { # Grab Nfsstat post profile before ending $TimeStr = time(); system("$Nfsstat > $OutputDir/nemo_nfsstat_$host\_$TimeStr\_$ServerIPAddress\_post.log"); $RunTime = $RunTime/60.0; $total_size = $total_size/1000000.0; print "NEMo Collector: Run ending after $RunTime min with $total_size MB data\n"; close (OUTFILE); exit; } # End tcpdump run if we are interrupted.. sub CleanUp { system("kill -SIGINT $pid"); # Grab Nfsstat post profile before ending $TimeStr = time(); system("$Nfsstat > $OutputDir/nemo_nfsstat_$host\_$TimeStr\_$ServerIPAddress\_post.log"); $RunTime = $RunTime/60.0; $total_size = $total_size/1000000.0; print "NEMo Collector: interrupted .. Run exiting after $RunTime min with $total_size MB data\n"; close (OUTFILE); exit; } # Filter tcpdump output # Tokenize routine for tcpdump version 3.8 sub Tokenize_3_8 { # If this line contains no NFS data, remove it entirely if ($_[0] =~ /: \. \[tcp/) { #print STDERR "Removing line: $_[0]\n"; $_[0] = ""; return; } # If this is a tcpdump packet misinterpret, remove it also if ($_[0] =~ /proc-|bad-len/) { $_[0] = ""; return; } # Remove IP information from all remaining lines $_[0] =~ s/(IP \(.*\) )//; # Remove source and destination IP addresses $_[0] =~ s/( \d+\.\d+\.\d+\.\d+\.2049[ :][ >])//; $_[0] =~ s/(\d+\.\d+\.\d+\.\d+\.)(\d+)([ :][ >])/$2 /; # Remove filenames from lookup/remove/create. We will # keep the request itself, to include in request type # distribution during static analysis if ($_[0] =~ /lookup|remove|create/) { $_[0] =~ s/("[^\s]+")//; } # Replace NFS op names with op numbers if ($_[0] =~ /\d+.\d+ \d+ reply ok \d+ \w+/ ) { if ($_[0] =~ /getattr|setattr|read |write|remove/) { $_[0] =~ s/(\d+.\d+ \d+) reply ok (\d+) (\w+) /$1 $2 o $NfsV3Ops{$3} /; } else { #Remove additional reply text $_[0] =~ s/(\d+.\d+ \d+) reply ok (\d+) (\w+) (.*)/$1 $2 o $NfsV3Ops{$3} /; } } else { if ($_[0] =~ /\d+.\d+ \d+ reply ERR \d+/) { $_[0] =~ s/(\d+.\d+ \d+) reply ERR (\d+)/$1 $2 e/; } else { if ($_[0] =~ /getattr|setattr|read |write|remove/) { $_[0] =~ s/(\d+.\d+ \d+) (\d+) (\w+) /$1 $2 r $NfsV3Ops{$3} /; } else { #Remove additional arguments past file handle if (!($_[0] =~ s/(\d+.\d+ \d+) (\d+) (\w+) ([^\s]+) (.*)/$1 $2 r $NfsV3Ops{$3} $4/)) { $_[0] = ""; #Remove any lines that dont conform to at least #one of these formats } } } } # Remove redundant text from requests/replies $_[0] =~ s/\@ |ids |bytes |sz |nlink |rdev |fsid |fileid |a\/m\/ctime |bytes \@ |fh|max |verf //g; return; } # Filter tcpdump output # Tokenize routine for tcpdump version 3.6 sub Tokenize_3_6 { # If this line contains no NFS data, remove it entirely if ($_[0] =~ /: \. \[tcp/) { #print STDERR "Removing line: $_[0]\n"; $_[0] = ""; return; } # If this is a tcpdump packet misinterpret, remove it also if ($_[0] =~ /proc-|bad-len/) { $_[0] = ""; return; } # Remove extraneous information from all remaining lines $_[0] =~ s/( \(DF\) \(.*\))//; # Remove source and destination IP addresses $_[0] =~ s/( \d+\.\d+\.\d+\.\d+\.2049[ :][ >])//; $_[0] =~ s/(\d+\.\d+\.\d+\.\d+\.)(\d+)([ :][ >])/$2 /; # Remove filenames from lookup/remove/create. We will # keep the request itself, to include in request type # distribution during static analysis if ($_[0] =~ /lookup|remove|create/) { $_[0] =~ s/("[^\s]+")//; } # Replace NFS op names with op numbers if ($_[0] =~ /\d+.\d+ \d+ reply ok \d+ \w+/ ) { if ($_[0] =~ /getattr|setattr|read |write|remove/) { $_[0] =~ s/(\d+.\d+ \d+) reply ok (\d+) (\w+) /$1 $2 o $NfsV3Ops{$3} /; } else { #Remove additional reply text $_[0] =~ s/(\d+.\d+ \d+) reply ok (\d+) (\w+) (.*)/$1 $2 o $NfsV3Ops{$3} /; } } else { if ($_[0] =~ /\d+.\d+ \d+ reply ERR \d+/) { $_[0] =~ s/(\d+.\d+ \d+) reply ERR (\d+)/$1 $2 e/; } else { if ($_[0] =~ /getattr|setattr|read |write|remove/) { $_[0] =~ s/(\d+.\d+ \d+) (\d+) (\w+) /$1 $2 r $NfsV3Ops{$3} /; } else { #Remove additional arguments past file handle if (!($_[0] =~ s/(\d+.\d+ \d+) (\d+) (\w+) ([^\s]+) (.*)/$1 $2 r $NfsV3Ops{$3} $4/)) { $_[0] = ""; #Remove any lines that dont conform to at least #one of these formats } } } } # Remove redundant text from requests/replies $_[0] =~ s/\@ |ids |bytes |bytes|sz |nlink |rdev |fsid |fileid |a\/m\/ctime |bytes \@ |fh|Unknown|max |verf //g; return; }