<# .NOTES Information on running PowerShell scripts can be found here: -http://ss64.com/ps/syntax-run.html -https://technet.microsoft.com/en-us/library/bb613481.aspx File Name: SnapMirrorAuditLogParser.ps1 .COMPONENT -NetApp PowerShell Toolkit 3.2.1 or newer: http://mysupport.netapp.com/NOW/download/tools/powershell_toolkit/ .SYNOPSIS This script parses Clustered Data ONTAP SnapMirror audit logs that are either retrieved direct from the cluster or from a downloaded copy of a log (i.e. from AutoSupport). Version 1.4 Changed the way of retrieving log files from a cluster to better sort by order of files written Version 1.3: Added a "concurrency" field to show how many other SnapMirror transfers were transferring during any part of the time that particular SnapMirror transfer was transferring Version 1.2: Changed throughput to calculate at "start to end" instead of "request to end" Version 1.1: Added row numbers Changed data organization to objects Gave option to export to Excel format Changed time stamp to full date/time stamp to correct calculations Version: 1.0 - Original release .DESCRIPTION The SnapMirror audit log provides a running log of SnapMirror relationships. It shows starting, restarting, and ending information for each SnapMirror relationship. This script reads in the log file(s) and parses the information into a usable CSV file. The output produces the following columns in the file: -Source (the source of the SnapMirror relationship) -Destination (the destination of the SnapMirror relationship -Type (request, start, or end) -KB Transferred (produced only for end type entries) -Time Stamp (the time stamp associated with the log file entry) .PARAMETER Cluster The cluster management LIF IP address or resolvable DNS name for the cluster to connect to. .PARAMETER Username Username to use to connect to the cluster. .PARAMETER Password Password used to connect to the cluster. This is in clear text. If not provided you will be prompted for the password during the script and it will be obfuscated. .PARAMETER OutputFile A specific output file name to use. Without this parameter the default output file is named after the cluster name/IP address or the name of the input file specified along with a date stamp. .PARAMETER NumberOfFilesToParse By default all log files on the cluster are parsed. Specifying this parameter only the number passed will be parsed. .PARAMETER StartWithFileNumber By default the script parses logs starting at the most recent working back chronologically. If this parameter is passed the only logs that will be parsed when that log is reached in the order being reviewed through to the rest of the logs. .PARAMETER UseLocalLogFile This parameter will produce an open file window to prompt for a file to read to parse. You can select a file you downloaded from AutoSupport or copied directly from a cluster. When specifying this parameter, pass no other parameters. .PARAMETER ExportToExcel Exports to Excel XLSX format instead of CSV. You must have installed the ImportExcel module found here: https://github.com/dfinke/ImportExcel .EXAMPLE .\SnapMirrorAuditLogParser.ps1 Running without any parameters will prompt for all necessary values .EXAMPLE .\SnapMirrorAuditLogParser.ps1 -Cluster NetApp1 -Username admin -Password MyPassword Connects to the cluster named NetApp1 with the provided credentials. .EXAMPLE .\SnapMirrorAuditLogParser.ps1 -OutputFile MyResults.csv -NumberOfFilesToParse 3 -StartWithFileNumber 2 Prompts for cluster information and outputs to a file named MyResults.csv. Starts parsing when the second file is reached on each node while parsing a total of 3 files on each node. .EXAMPLE .\SnapMirrorAuditLogParser.ps1 -UseLocalLogFile This will provide a prompt to provide a local SnapMirror audit log file to parse. .LINK http://community.netapp.com/t5/Microsoft-Cloud-and-Virtualization-Discussions/PowerShell-SnapMirror-Audit-Log-Parser-for-CDOT/m-p/117002 #> #region Parameters and Variables [CmdletBinding(PositionalBinding=$False)] Param( [Parameter(Mandatory=$False)] [string]$Cluster, [Parameter(Mandatory=$False)] [string]$Username, [Parameter(Mandatory=$False)] [string]$Password, [Parameter(Mandatory=$False)] [string]$OutputFile, [Parameter(Mandatory=$false,ValueFromPipeline=$false)] [Switch]$UseLocalLogFile = $false, [Parameter(Mandatory=$false,ValueFromPipeline=$false)] [Switch]$ExportToExcel = $false, [Parameter(Mandatory=$False)] [int]$StartWithFileNumber = 1, [Parameter(Mandatory=$False)] [int]$NumberOfFilesToParse = -1 ) #Full output of either local audit log or all audit logs from cluster $SnapMirrorAuditLogContent = @() #Output of reduced log will be source,destination,type,KB transferred(if end),time stamp $Global:SnapMirrorAuditLogReduced = @() #Hash table for each SnapMirror transfer $SnapMirrorTransferHash=@{} $CollectedOutput = @() $FullCollectedOutput = @() $RowCounter = 0 # Check toolkit version If (!$UseLocalLogFile) { try { If (-Not (Get-Module DataONTAP)) { Import-Module DataONTAP -EA 'STOP' -Verbose:$false } If ((Get-NaToolkitVersion).CompareTo([system.version]'3.2.1') -LT 0) { throw } } Catch [Exception] { Write-Warning "This script requires Data ONTAP PowerShell Toolkit 3.2.1 or higher." return; } } #endregion #region Functions Function Get-OpenFile($initialDirectory) { [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog $OpenFileDialog.initialDirectory = $initialDirectory $OpenFileDialog.filter = "All files (*.*)| *.*" $OpenFileDialog.ShowDialog() | Out-Null $OpenFileDialog.filename } Function Extract-SnapMirrorDetails ($SnapMirrorAuditResults) { ForEach ($Line in $SnapMirrorAuditResults) { $LineSplit = $Line -split '\s+' $SourceLocation = $Line.IndexOf("source=") $DestinationLocation = $Line.IndexOf("destination=") $TypeLocation = $Line.IndexOf("action=") $KBLocation = $Line.IndexOf("bytes_transferred=") If ($SourceLocation -gt 0) { $Temp1 = $Line.Substring($SourceLocation) $Temp1 = $Temp1 -split '\s+' $Temp1 = $Temp1[0] -split '=' $SourceValue = $Temp1[1] } else { $SourceValue = "failure" } If ($DestinationLocation -gt 0) { $Temp1 = $Line.Substring($DestinationLocation) $Temp1 = $Temp1 -split '\s+' $Temp1 = $Temp1[0] -split '=' $DestinationValue = $Temp1[1] } else { $DestinationValue = "failure" } If ($KBLocation -gt 0) { $Temp1 = $Line.Substring($KBLocation) $Temp1 = $Temp1 -split '\s+' $Temp1 = $Temp1[0] -split '=' $KBValue = $Temp1[1] } else { $KBValue = "failure" } If ($TypeLocation -gt 0) { $Temp1 = $Line.Substring($TypeLocation) $Temp1 = $Temp1 -split '\s+' $Temp1 = $Temp1[0] -split '=' $TypeValue = $Temp1[1] } else { $TypeValue = "failure" } If ($SourceValue -eq "failure" -and $DestinationValue -eq "failure" -and $KBValue -eq "failure" -and $TypeValue -eq "failure") { $DateTimeStamp = "failure" } else { $Month = ConvertMonth ($LineSplit[1]) $TimeArray = $LineSplit[3] -split '\:' $DateTimeStamp = Get-Date -Year $LineSplit[5] -Month $Month -Day $LineSplit[2] -Hour $TimeArray[0] -Minute $TimeArray[1] -Second $TimeArray[2] } $Global:SnapMirrorAuditLogReduced += ,@($SourceValue,$DestinationValue,$TypeValue,$KBValue,$DateTimeStamp) } } Function ConvertMonth ($AbbreviatedMonth) { switch ($AbbreviatedMonth.tolower()) { jan {Return [int]1} feb {Return [int]2} mar {Return [int]3} apr {Return [int]4} may {Return [int]5} jun {Return [int]6} jul {Return [int]7} aug {Return [int]8} sep {Return [int]9} oct {Return [int]10} nov {Return [int]11} dec {Return [int]12} default {Return 0} } } #endregion #region Main Body #Connect to the cluster If (!$UseLocalLogFile) { If ($Cluster.Length -eq 0) { $Cluster = Read-host "Enter the cluster management LIF DNS name or IP address" } $Cluster = $Cluster.Trim() If ($Username.Length -eq 0) { $Username = Read-Host "Enter username for connecting to the cluster" } If ($Password.Length -eq 0) { $SecurePassword = Read-Host "Enter the password for" $Username -AsSecureString } else { $SecurePassword = New-Object -TypeName System.Security.SecureString $Password.ToCharArray() | ForEach-Object {$SecurePassword.AppendChar($_)} } $Credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username, $SecurePassword Write-Host "Attempting connection to $Cluster" $ClusterConnection = Connect-NcController -name $Cluster -Credential $Credentials If (!$ClusterConnection) { Write-Host "Unable to connect to NetApp cluster, please ensure all supplied information is correct and try again" -ForegroundColor Yellow Exit } #Get basic cluster information $ClusterInformation = Get-NcCluster $Nodes = Get-NcNode $NodeInformation = Get-NcNode Write-Host "Working with cluster:" $ClusterInformation.ClusterName Write-Host "Which contains the following Nodes:" $NodeInformation.Node } #Read from a local copied (from AutoSupport or direct from cluster) SnapMirror audit log file If ($UseLocalLogFile) { Write-Host "Select a saved snapmirror_audit file" $SnapMirrorAuditLogFile = Get-OpenFile Write-Host "Reading saved snapmirror_audit file" $SnapMirrorAuditLogContent = Get-Content $SnapMirrorAuditLogFile } else { #Need to read in SnapMirror audit logs from each node ForEach ($Node in $Nodes) { $FileCounter = 0 Write-Host "" If ($NumberOfFilesToParse -gt -1) { If ($StartWithFileNumber -gt 1) { Write-Host "Finding up to" $NumberOfFilesToParse "SnapMirror audit log files from node" $Node "starting with file" $StartWithFileNumber } else { Write-Host "Finding up to" $NumberOfFilesToParse "SnapMirror audit log files from node" $Node } } else { If ($StartWithFileNumber -gt 1) { Write-Host "Finding all SnapMirror audit log files from node" $Node "starting with file" $StartWithFileNumber } else { Write-Host "Finding all SnapMirror audit log files from node" $Node } } Write-Host "Retrieving SnapMirror audit log files from node" $Node $SnapMirrorAuditLogFileList = @() $LogDirectoryListingSnapMirrorAuditLogs = @() #Find all logs under /etc/log $LogDirectoryListingCommand = "system node run -node " + $Node + ' -command "priv set diag;ls /etc/log;priv set admin"' $LogDirectoryListingFull = Invoke-NcSsh $LogDirectoryListingCommand $StringLogDirectoryListingFull = $LogDirectoryListingFull.ToString() $ArrayLogDirectoryListingFull = $StringLogDirectoryListingFull.Split("`n") #Put in alphabetical order $ArrayLogDirectoryListingFull = $ArrayLogDirectoryListingFull | Sort-Object #Find only the snapmirror_audit files in this listing without the .log extension ForEach ($FileName in $ArrayLogDirectoryListingFull) { #See the following KB for layout of SnapMirror audit log files https://kb.netapp.com/support/index?page=content&id=3014284&locale=en_US If ($FileName -match "snapmirror_audit.log") { $SnapMirrorAuditLogFileList += $FileName } } #The high numbered files are most recent $SnapMirrorAuditLogFileList = $SnapMirrorAuditLogFileList | Sort-Object -Descending $NumberSkipped = 0 ForEach ($FileName in $SnapMirrorAuditLogFileList) { #Start with the file number if specified If (($StartWithFileNumber-1) -gt 0) { If ($NumberSkipped -lt ($StartWithFileNumber-1)) { #Do not read the file Write-Host "Skipping file named" $FileName $NumberSkipped++ Continue } } If (($NumberOfFilesToParse -eq -1) -or ($FileCounter -lt $NumberOfFilesToParse)) { $LogDirectoryListingSnapMirrorAuditLogs += $FileName #Increment the counter $FileCounter++ } } ForEach ($SnapMirrorAuditLogFile in $LogDirectoryListingSnapMirrorAuditLogs) { Write-Host "Reading SnapMirror audit log file named" $SnapMirrorAuditLogFile $ReadFileCommand = "system node run -node " + $Node + ' -command rdfile /etc/log/' + $SnapMirrorAuditLogFile $ReadFileResults = Invoke-NcSsh $ReadFileCommand $StringReadFileResults = $ReadFileResults.ToString() $ArrayReadFileResults = $StringReadFileResults.Split("`n") ForEach ($SnapMirrorAuditMessage in $ArrayReadFileResults) { $SnapMirrorAuditLogContent += $SnapMirrorAuditMessage } } } } #Create a formatted output of the results with just the needed details Extract-SnapMirrorDetails ($SnapMirrorAuditLogContent) #Start to process the results Write-Host "Now processing gathered SnapMirror audit log entries" #Get or create an output file If ($OutputFile -eq "") { #Create a file name formatted as ClusterNameDateStamp.csv $DateStamp = get-date -uformat "%Y-%m-%d@%H-%M-%S" If (!$UseLocalLogFile) { If ($ExportToExcel) { $OutputFile = $ClusterInformation.ClusterName + "_" + $DateStamp + ".xlsx" } else { $OutputFile = $ClusterInformation.ClusterName + "_" + $DateStamp + ".csv" } } else { If ($ExportToExcel) { $OutputFile = $SnapMirrorAuditLogFile + "_" + $DateStamp + ".xlsx" } else { $OutputFile = $SnapMirrorAuditLogFile + "_" + $DateStamp + ".csv" } } } #Parse the collected data ForEach ($AuditEntry in $Global:SnapMirrorAuditLogReduced) { $ArrayAuditEntry = $AuditEntry -split '\,' If ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) -eq $null) { $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) = @{} } #There is no request line in CDOT, only starts If ($ArrayAuditEntry[2].StartsWith("Start")) { $RequestEntry = @{ "Request" = $ArrayAuditEntry[4] } If ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("Request")) { # do nothing as it has already been attempted to start once } else { $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) += $RequestEntry } $StartEntry = @{ "Start" = $ArrayAuditEntry[4] } If ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("Start")) { #This means SnapMirror was attempted to be started before, but failed to complete #for some reason, thus making the first start in the series the request $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Remove("Start") } $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) += $StartEntry } If ($ArrayAuditEntry[2].StartsWith("End")) { #Only need to proceed if SnapMirror completed in which this field would show KB entry If ($ArrayAuditEntry[3] -ne "failure") { [int64]$Bytes = $ArrayAuditEntry[3].TrimStart("(") #Convert bytes to KB $KB = ($Bytes/1024) $EndEntry = @{ "End" = $ArrayAuditEntry[4] } #Remove any previous entries for an end that didn't complete the transfer If ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("End")) { $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Remove("End") } else { $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) += $EndEntry } If (($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("Request")) -And ` ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("Start")) -And ` ($SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).ContainsKey("End"))) { $PowerShellObject = New-Object PSCustomObject $RowCounter++ $RequestTime = Get-Date $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Request $start_time = Get-Date $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Start $EndTime = Get-Date $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).End $RequestToEnd = ($EndTime - $RequestTime).TotalSeconds $StartToEnd = ($EndTime - $start_time).TotalSeconds $RequestToStart = ($start_time - $RequestTime).TotalSeconds $Throughput = [math]::Round($KB/$StartToEnd) $PowerShellObject | Add-Member -type NoteProperty -name Row -value $RowCounter $PowerShellObject | Add-Member -type NoteProperty -name Source -value $ArrayAuditEntry[0] $PowerShellObject | Add-Member -type NoteProperty -name Destination -value $ArrayAuditEntry[1] $PowerShellObject | Add-Member -type NoteProperty -name 'KB Transferred' -value $KB $PowerShellObject | Add-Member -type NoteProperty -name 'Request Time' -value $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Request $PowerShellObject | Add-Member -type NoteProperty -name 'Start Time' -value $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).Start $PowerShellObject | Add-Member -type NoteProperty -name 'End Time' -value $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]).End $PowerShellObject | Add-Member -type NoteProperty -name 'Request to End' -value $RequestToEnd $PowerShellObject | Add-Member -type NoteProperty -name 'Start to End' -value $StartToEnd $PowerShellObject | Add-Member -type NoteProperty -name 'Request to Start' -value $RequestToStart $PowerShellObject | Add-Member -type NoteProperty -name 'Throughput KB/s' -value $Throughput $CollectedOutput += $PowerShellObject #Null out the hash table to start fresh $SnapMirrorTransferHash.Item($ArrayAuditEntry[1]) = $null } } } } #Check for concurrency ForEach ($SnapMirrorTransfer in $CollectedOutput) { #Reset the counter to count only this running SnapMirror $ConcurrentSnapMirrorTransfers = 1 #Set the start and end times of the current SnapMirror transfer $MasterStartTime = $SnapMirrorTransfer.'Start Time' $MasterEndTime = $SnapMirrorTransfer.'End Time' #Find any other SnapMirror transfer that transfered any data between the start/end times of this transfer ForEach ($CheckSnapMirrorTransfer in $CollectedOutput) { $CheckStartTime = $CheckSnapMirrorTransfer.'Start Time' $CheckEndTime = $CheckSnapMirrorTransfer.'End Time' #CheckStart > MasterStart CheckEnd < MasterEnd CheckEnd > MasterStart #CheckStart < MasterStart CheckEnd < MasterEnd CheckEnd > MasterStart #CheckStart < MasterStart CheckEnd > MasterEnd CheckEnd > MasterStart #CheckStart > MasterStart CheckEnd > MasterEnd CheckStart < MasterEnd CheckEnd > MasterStart If ((($CheckStartTime -gt $MasterStartTime -and $CheckEndTime -lt $MasterEndTime) ` -or ($CheckStartTime -lt $MasterStartTime) ` -or ($CheckStartTime -gt $MasterStartTime -and $CheckEndTime -gt $MasterEndTime -and $CheckStartTime -lt $MasterEndTime)) ` -and ($CheckEndTime -gt $MasterStartTime)) { $ConcurrentSnapMirrorTransfers++ } } #Add this new concurrency number to the other fields $PowerShellObject = New-Object PSCustomObject $PowerShellObject | Add-Member -type NoteProperty -name Row -value $SnapMirrorTransfer.Row $PowerShellObject | Add-Member -type NoteProperty -name Source -value $SnapMirrorTransfer.Source $PowerShellObject | Add-Member -type NoteProperty -name Destination -value $SnapMirrorTransfer.Destination $PowerShellObject | Add-Member -type NoteProperty -name 'KB Transferred' -value $SnapMirrorTransfer.'KB Transferred' $PowerShellObject | Add-Member -type NoteProperty -name 'Request Time' -value $SnapMirrorTransfer.'Request Time' $PowerShellObject | Add-Member -type NoteProperty -name 'Start Time' -value $SnapMirrorTransfer.'Start Time' $PowerShellObject | Add-Member -type NoteProperty -name 'End Time' -value $SnapMirrorTransfer.'End Time' $PowerShellObject | Add-Member -type NoteProperty -name 'Request to End' -value $SnapMirrorTransfer.'Request to End' $PowerShellObject | Add-Member -type NoteProperty -name 'Start to End' -value $SnapMirrorTransfer.'Start to End' $PowerShellObject | Add-Member -type NoteProperty -name 'Request to Start' -value $SnapMirrorTransfer.'Request to Start' $PowerShellObject | Add-Member -type NoteProperty -name 'Throughput KB/s' -value $SnapMirrorTransfer.'Throughput KB/s' $PowerShellObject | Add-Member -type NoteProperty -name 'Concurrency' -value $ConcurrentSnapMirrorTransfers $FullCollectedOutput += $PowerShellObject } #Export to file Write-Host "Saving to output file named:" $OutputFile If ($ExportToExcel) { #Import the Excel module If (-Not (Get-Module ImportExcel)) { Import-Module ImportExcel } $FullCollectedOutput | Export-Excel -Path "$OutputFile" -WorkSheetname "SnapMirror Results" -BoldTopRow -AutoSize -FreezeTopRow -AutoFilter -Numberformat "#,##0" } else { $FullCollectedOutput | Export-Csv -Path $OutputFile -NoTypeInformation } Write-Host "Process complete" #endregion