Private/Aggregation.ps1
|
#Requires -Version 5.1 function Read-SMBeatSampleFiles { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [datetime]$StartUtc, [datetime]$EndUtc ) $dir = Get-SMBeatSampleDirectory -DataRoot $DataRoot $records = New-Object System.Collections.Generic.List[object] if (-not (Test-Path -LiteralPath $dir)) { return @() } $useNative = Initialize-SMBeatSampleJsonNative $startDay = $StartUtc.Date.AddDays(-1) $endDay = $EndUtc.Date.AddDays(1) $files = @(Get-ChildItem -LiteralPath $dir -Filter '*.jsonl' -ErrorAction SilentlyContinue | Sort-Object Name) foreach ($file in $files) { $dayPart = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) $fileDay = [datetime]::MinValue if (-not [datetime]::TryParseExact($dayPart, 'yyyy-MM-dd', $script:SMBeatInvariant, [System.Globalization.DateTimeStyles]::AssumeUniversal, [ref]$fileDay)) { continue } if ($fileDay -lt $startDay -or $fileDay -gt $endDay) { continue } if ($useNative) { $batch = [SMBeat.Native.SampleJson]::ReadFile($file.FullName, $StartUtc, $EndUtc) foreach ($rec in $batch) { [void]$records.Add($rec) } continue } foreach ($line in [System.IO.File]::ReadLines($file.FullName, $script:SMBeatUtf8NoBom)) { if ([string]::IsNullOrWhiteSpace($line)) { continue } $obj = ConvertFrom-SMBeatJson -Json $line $ts = ConvertFrom-SMBeatUtcString -Value $obj.ts if ($ts -lt $StartUtc -or $ts -ge $EndUtc) { continue } [void]$records.Add($obj) } } return $records.ToArray() } function Read-SMBeatSampleWindows { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [datetime]$PrevStart, [datetime]$PrevEnd, [datetime]$WindowStart, [datetime]$WindowEnd ) $dir = Get-SMBeatSampleDirectory -DataRoot $DataRoot if ((Initialize-SMBeatSampleJsonNative) -and (Test-Path -LiteralPath $dir)) { $pair = [SMBeat.Native.SampleJson]::ReadDirectory($dir, $PrevStart, $PrevEnd, $WindowStart, $WindowEnd) return [PSCustomObject]@{ Previous = @($pair.Previous) Current = @($pair.Current) } } $spanStart = $PrevStart if ($WindowStart -lt $spanStart) { $spanStart = $WindowStart } $spanEnd = $WindowEnd if ($PrevEnd -gt $spanEnd) { $spanEnd = $PrevEnd } $all = @(Read-SMBeatSampleFiles -DataRoot $DataRoot -StartUtc $spanStart -EndUtc $spanEnd) $prev = New-Object System.Collections.Generic.List[object] $cur = New-Object System.Collections.Generic.List[object] foreach ($rec in $all) { $ts = Get-SMBeatRecordUtc -Record $rec if ($ts -ge $PrevStart -and $ts -lt $PrevEnd) { [void]$prev.Add($rec) } if ($ts -ge $WindowStart -and $ts -lt $WindowEnd) { [void]$cur.Add($rec) } } [PSCustomObject]@{ Previous = $prev.ToArray() Current = $cur.ToArray() } } function Get-SMBeatExpectedSampleCount { param( [datetime]$StartUtc, [datetime]$EndUtc, [int]$IntervalSec ) if ($IntervalSec -le 0) { $IntervalSec = 60 } $seconds = ($EndUtc - $StartUtc).TotalSeconds if ($seconds -le 0) { return 1 } [math]::Max(1, [int][math]::Floor($seconds / $IntervalSec)) } function Add-SMBeatAccumulator { param( [hashtable]$Map, [string]$Key, [int64]$Read, [int64]$Write, [int64]$Sent, [int64]$Received ) if (-not $Map.ContainsKey($Key)) { $Map[$Key] = @{ Read = [int64]0 Write = [int64]0 Sent = [int64]0 Received = [int64]0 } } $Map[$Key].Read += $Read $Map[$Key].Write += $Write $Map[$Key].Sent += $Sent $Map[$Key].Received += $Received } function Test-SMBeatVolumeNear { param( [int64]$Left, [int64]$Right, [double]$MinRatio = 0.8 ) $hi = $Left if ($Right -gt $hi) { $hi = $Right } $lo = $Left if ($Right -lt $lo) { $lo = $Right } if ($hi -le 0) { return $true } return (([double]$lo / [double]$hi) -ge $MinRatio) } function Get-SMBeatEtwRetrievedBytes { param( [hashtable]$ByUser, [int64]$ShareSent ) $userSent = [int64]0 if ($null -ne $ByUser) { foreach ($uk in @($ByUser.Keys)) { $userSent += [int64]$ByUser[$uk].Sent } } if ($userSent -gt $ShareSent) { return $userSent } return $ShareSent } function ConvertTo-SMBeatNamedTotals { param( [hashtable]$Map ) $list = New-Object System.Collections.Generic.List[object] foreach ($key in $Map.Keys) { $v = $Map[$key] $sentB = [int64]$v.Sent $recvB = [int64]$v.Received $list.Add([PSCustomObject]@{ Name = $key ReadBytes = [int64]$v.Read WriteBytes = [int64]$v.Write SentBytes = $sentB ReceivedBytes = $recvB RetrievedBytes = $sentB WrittenBytes = $recvB ReadGB = (ConvertTo-SMBeatGB -Bytes $v.Read) WriteGB = (ConvertTo-SMBeatGB -Bytes $v.Write) SentGB = (ConvertTo-SMBeatGB -Bytes $sentB) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $recvB) RetrievedGB = (ConvertTo-SMBeatGB -Bytes $sentB) WrittenGB = (ConvertTo-SMBeatGB -Bytes $recvB) }) | Out-Null } $list | Sort-Object -Property @{ Expression = { $outB = [int64]0 $inB = [int64]0 if ($_.SentBytes) { $outB = [int64]$_.SentBytes } if ($_.ReceivedBytes) { $inB = [int64]$_.ReceivedBytes } if ($outB -gt $inB) { $outB } else { $inB } } } -Descending } function New-SMBeatEmptyTotals { [PSCustomObject]@{ ReadBytes = [int64]0 WriteBytes = [int64]0 SentBytes = [int64]0 ReceivedBytes = [int64]0 NicBytesIn = [int64]0 NicBytesOut = [int64]0 ReadGB = 0 WriteGB = 0 SentGB = 0 ReceivedGB = 0 NicInGB = 0 NicOutGB = 0 RetrievedBytes = [int64]0 RetrievedGB = 0 WrittenBytes = [int64]0 WrittenGB = 0 SmbPerfSentBytes = [int64]0 SmbPerfReceivedBytes = [int64]0 SmbPerfSentGB = 0 SmbPerfReceivedGB = 0 } } function ConvertTo-SMBeatTotalsObject { param( [int64]$Read, [int64]$Write, [int64]$Sent, [int64]$Received, [int64]$NicIn, [int64]$NicOut, [int64]$Retrieved, [int64]$Written, [int64]$SmbPerfSent = 0, [int64]$SmbPerfReceived = 0 ) if (-not $PSBoundParameters.ContainsKey('Retrieved')) { $Retrieved = $NicOut } if (-not $PSBoundParameters.ContainsKey('Written')) { $Written = $Received } [PSCustomObject]@{ ReadBytes = $Read WriteBytes = $Write SentBytes = $Sent ReceivedBytes = $Received NicBytesIn = $NicIn NicBytesOut = $NicOut ReadGB = (ConvertTo-SMBeatGB -Bytes $Read) WriteGB = (ConvertTo-SMBeatGB -Bytes $Write) SentGB = (ConvertTo-SMBeatGB -Bytes $Sent) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $Received) NicInGB = (ConvertTo-SMBeatGB -Bytes $NicIn) NicOutGB = (ConvertTo-SMBeatGB -Bytes $NicOut) RetrievedBytes = $Retrieved RetrievedGB = (ConvertTo-SMBeatGB -Bytes $Retrieved) WrittenBytes = $Written WrittenGB = (ConvertTo-SMBeatGB -Bytes $Written) SmbPerfSentBytes = $SmbPerfSent SmbPerfReceivedBytes = $SmbPerfReceived SmbPerfSentGB = (ConvertTo-SMBeatGB -Bytes $SmbPerfSent) SmbPerfReceivedGB = (ConvertTo-SMBeatGB -Bytes $SmbPerfReceived) } } function Get-SMBeatPeakStats { param( [object[]]$Hourly, [int64]$RetrievedBytes, [int64]$WrittenBytes, [datetime]$StartUtc, [datetime]$EndUtc ) $peakStart = $null $peakBytes = [int64]0 $peakWriteStart = $null $peakWriteBytes = [int64]0 foreach ($row in @($Hourly)) { if ($null -eq $row) { continue } $b = [int64]0 if ($row.PSObject.Properties['RetrievedBytes'] -and $row.RetrievedBytes) { $b = [int64]$row.RetrievedBytes } if ($b -gt $peakBytes) { $peakBytes = $b $peakStart = $row.PeriodStart } $w = [int64]0 if ($row.PSObject.Properties['WrittenBytes'] -and $row.WrittenBytes) { $w = [int64]$row.WrittenBytes } elseif ($row.PSObject.Properties['ReceivedBytes'] -and $row.ReceivedBytes) { $w = [int64]$row.ReceivedBytes } if ($w -gt $peakWriteBytes) { $peakWriteBytes = $w $peakWriteStart = $row.PeriodStart } } $hours = ($EndUtc - $StartUtc).TotalHours if ($hours -le 0) { $hours = 1 } [PSCustomObject]@{ PeakStart = $peakStart PeakRetrievedBytes = $peakBytes PeakRetrievedGB = (ConvertTo-SMBeatGB -Bytes $peakBytes) AverageRetrievedGBPerHour = (ConvertTo-SMBeatGB -Bytes ([int64][math]::Round($RetrievedBytes / $hours))) PeakWrittenStart = $peakWriteStart PeakWrittenBytes = $peakWriteBytes PeakWrittenGB = (ConvertTo-SMBeatGB -Bytes $peakWriteBytes) AverageWrittenGBPerHour = (ConvertTo-SMBeatGB -Bytes ([int64][math]::Round($WrittenBytes / $hours))) } } function Resolve-SMBeatSeriesBucket { param( [hashtable]$Map, [datetime]$Start ) $key = $Start.ToString('o', $script:SMBeatInvariant) if (-not $Map.ContainsKey($key)) { $Map[$key] = @{ Start = $Start Read = [int64]0 Write = [int64]0 Sent = [int64]0 Received = [int64]0 ServerRead = [int64]0 ServerWrite = [int64]0 ServerSent = [int64]0 ServerRecv = [int64]0 HasShare = $false NicIn = [int64]0 NicOut = [int64]0 Tcp445Sent = [int64]0 Tcp445Recv = [int64]0 HasTcp445 = $false } } $Map[$key] } function Add-SMBeatSeriesDelta { param( [hashtable]$Bucket, $Record ) $kind = [string]$Record.kind if ($kind -eq 'server') { $Bucket.ServerRead += [int64]$Record.readBytesDelta $Bucket.ServerWrite += [int64]$Record.writeBytesDelta $Bucket.ServerSent += [int64]$Record.sentBytesDelta $Bucket.ServerRecv += [int64]$Record.receivedBytesDelta } elseif ($kind -eq 'share') { $Bucket.Read += [int64]$Record.readBytesDelta $Bucket.Write += [int64]$Record.writeBytesDelta $Bucket.Sent += [int64]$Record.sentBytesDelta $Bucket.Received += [int64]$Record.receivedBytesDelta $Bucket.HasShare = $true } elseif ($kind -eq 'tcp445') { $name = [string]$Record.name if ($name -eq '_listen445') { $Bucket.Tcp445Sent += [int64]$Record.sentBytesDelta $Bucket.Tcp445Recv += [int64]$Record.receivedBytesDelta $Bucket.HasTcp445 = $true } } elseif ($kind -eq 'nic') { $Bucket.NicIn += [int64]$Record.receivedBytesDelta $Bucket.NicOut += [int64]$Record.sentBytesDelta } } function ConvertTo-SMBeatSeriesRows { param( [hashtable]$Buckets, [string]$Granularity ) $result = New-Object System.Collections.Generic.List[object] foreach ($key in ($Buckets.Keys | Sort-Object)) { $b = $Buckets[$key] $end = Get-SMBeatBucketEnd -BucketStart $b.Start -Granularity $Granularity $useShare = [bool]$b.HasShare $readB = $b.ServerRead $writeB = $b.ServerWrite $sentB = $b.ServerSent $recvB = $b.ServerRecv if ($useShare) { $readB = $b.Read $writeB = $b.Write $sentB = $b.Sent $recvB = $b.Received } $retrievedB = $b.NicOut $writtenB = $recvB if ([bool]$b.HasTcp445) { $retrievedB = $b.Tcp445Sent $writtenB = $b.Tcp445Recv if ([bool]$b.HasShare -and $b.Received -gt $writtenB -and $b.Received -gt (10 * [math]::Max($writtenB, [int64]1))) { $writtenB = $b.Received } if ($b.NicOut -gt 1048576 -and $b.Tcp445Sent -gt $b.NicOut -and $b.Tcp445Sent -gt [int64]([double]$b.NicOut * 1.1)) { $etwRead = [int64]0 if ([bool]$b.HasShare) { $etwRead = [int64]$b.Sent } if ($etwRead -gt 1048576 -and (Test-SMBeatVolumeNear -Left $etwRead -Right $b.NicOut)) { $retrievedB = $etwRead } } } $result.Add([PSCustomObject]@{ Granularity = $Granularity PeriodStart = $b.Start PeriodEnd = $end ReadBytes = $readB WriteBytes = $writeB SentBytes = $sentB ReceivedBytes = $recvB NicBytesIn = $b.NicIn NicBytesOut = $b.NicOut RetrievedBytes = $retrievedB WrittenBytes = $writtenB ReadGB = (ConvertTo-SMBeatGB -Bytes $readB) WriteGB = (ConvertTo-SMBeatGB -Bytes $writeB) SentGB = (ConvertTo-SMBeatGB -Bytes $sentB) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $recvB) NicInGB = (ConvertTo-SMBeatGB -Bytes $b.NicIn) NicOutGB = (ConvertTo-SMBeatGB -Bytes $b.NicOut) RetrievedGB = (ConvertTo-SMBeatGB -Bytes $retrievedB) WrittenGB = (ConvertTo-SMBeatGB -Bytes $writtenB) }) | Out-Null } $result.ToArray() } function Get-SMBeatSeriesSet { param( [object[]]$Records, [datetime]$StartUtc, [datetime]$EndUtc, [TimeZoneInfo]$TimeZone, [string[]]$Granularity ) $wantHour = $Granularity -contains 'Hour' $wantDay = $Granularity -contains 'Day' $wantWeek = $Granularity -contains 'Week' $hourMap = @{} $dayMap = @{} $weekMap = @{} if ($null -eq $Records) { $Records = @() } if ($wantHour -or $wantDay -or $wantWeek) { foreach ($rec in $Records) { $ts = Get-SMBeatRecordUtc -Record $rec $local = Convert-SMBeatUtcToTimeZone -Utc $ts -TimeZone $TimeZone if ($wantHour) { $bStart = Get-SMBeatBucketStart -LocalTime $local -Granularity Hour $bucket = Resolve-SMBeatSeriesBucket -Map $hourMap -Start $bStart Add-SMBeatSeriesDelta -Bucket $bucket -Record $rec } if ($wantDay) { $bStart = Get-SMBeatBucketStart -LocalTime $local -Granularity Day $bucket = Resolve-SMBeatSeriesBucket -Map $dayMap -Start $bStart Add-SMBeatSeriesDelta -Bucket $bucket -Record $rec } if ($wantWeek) { $bStart = Get-SMBeatBucketStart -LocalTime $local -Granularity Week $bucket = Resolve-SMBeatSeriesBucket -Map $weekMap -Start $bStart Add-SMBeatSeriesDelta -Bucket $bucket -Record $rec } } } [PSCustomObject]@{ Hour = @(ConvertTo-SMBeatSeriesRows -Buckets $hourMap -Granularity Hour) Day = @(ConvertTo-SMBeatSeriesRows -Buckets $dayMap -Granularity Day) Week = @(ConvertTo-SMBeatSeriesRows -Buckets $weekMap -Granularity Week) } } function Get-SMBeatSeries { param( [object[]]$Records, [datetime]$StartUtc, [datetime]$EndUtc, [TimeZoneInfo]$TimeZone, [string]$Granularity ) $set = Get-SMBeatSeriesSet -Records $Records -StartUtc $StartUtc -EndUtc $EndUtc -TimeZone $TimeZone -Granularity @($Granularity) if ($Granularity -eq 'Day') { return @($set.Day) } if ($Granularity -eq 'Week') { return @($set.Week) } @($set.Hour) } function ConvertTo-SMBeatNativeRecordList { param( [AllowEmptyCollection()] [object[]]$Records ) $list = New-Object 'System.Collections.Generic.List[SMBeat.Native.SampleRecord]' if ($null -eq $Records -or $Records.Count -eq 0) { return ,$list } foreach ($rec in $Records) { if ($null -eq $rec) { continue } if ($rec -is [SMBeat.Native.SampleRecord]) { [void]$list.Add($rec) continue } $n = New-Object SMBeat.Native.SampleRecord $props = $rec.PSObject.Properties if ($props['ts'] -and $rec.ts) { $n.ts = [string]$rec.ts } $n.TsUtc = Get-SMBeatRecordUtc -Record $rec if ($props['server']) { $n.server = [string]$rec.server } if ($props['kind']) { $n.kind = [string]$rec.kind } if ($props['name']) { $n.name = [string]$rec.name } if ($props['client']) { $n.client = [string]$rec.client } if ($props['user']) { $n.user = [string]$rec.user } if ($props['readBytesDelta'] -and $rec.readBytesDelta) { $n.readBytesDelta = [int64]$rec.readBytesDelta } if ($props['writeBytesDelta'] -and $rec.writeBytesDelta) { $n.writeBytesDelta = [int64]$rec.writeBytesDelta } if ($props['sentBytesDelta'] -and $rec.sentBytesDelta) { $n.sentBytesDelta = [int64]$rec.sentBytesDelta } if ($props['receivedBytesDelta'] -and $rec.receivedBytesDelta) { $n.receivedBytesDelta = [int64]$rec.receivedBytesDelta } if ($props['sampleIntervalSec'] -and $rec.sampleIntervalSec) { $n.sampleIntervalSec = [int]$rec.sampleIntervalSec } [void]$list.Add($n) } return ,$list } function ConvertTo-SMBeatNamedTotalsFromNative { param($Items) $list = New-Object System.Collections.Generic.List[object] if ($null -eq $Items) { return @() } foreach ($i in @($Items)) { if ($null -eq $i) { continue } $readB = [int64]$i.Read $writeB = [int64]$i.Write $sentB = [int64]$i.Sent $recvB = [int64]$i.Received [void]$list.Add([PSCustomObject]@{ Name = [string]$i.Name ReadBytes = $readB WriteBytes = $writeB SentBytes = $sentB ReceivedBytes = $recvB RetrievedBytes = $sentB WrittenBytes = $recvB ReadGB = (ConvertTo-SMBeatGB -Bytes $readB) WriteGB = (ConvertTo-SMBeatGB -Bytes $writeB) SentGB = (ConvertTo-SMBeatGB -Bytes $sentB) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $recvB) RetrievedGB = (ConvertTo-SMBeatGB -Bytes $sentB) WrittenGB = (ConvertTo-SMBeatGB -Bytes $recvB) }) } $list.ToArray() } function ConvertTo-SMBeatSeriesRowsFromNative { param($Items) $list = New-Object System.Collections.Generic.List[object] if ($null -eq $Items) { return @() } foreach ($i in @($Items)) { if ($null -eq $i) { continue } $readB = [int64]$i.ReadBytes $writeB = [int64]$i.WriteBytes $sentB = [int64]$i.SentBytes $recvB = [int64]$i.ReceivedBytes $nicInB = [int64]$i.NicBytesIn $nicOutB = [int64]$i.NicBytesOut $retrievedB = [int64]$i.RetrievedBytes $writtenB = [int64]$i.WrittenBytes [void]$list.Add([PSCustomObject]@{ Granularity = [string]$i.Granularity PeriodStart = [datetime]$i.PeriodStart PeriodEnd = [datetime]$i.PeriodEnd ReadBytes = $readB WriteBytes = $writeB SentBytes = $sentB ReceivedBytes = $recvB NicBytesIn = $nicInB NicBytesOut = $nicOutB RetrievedBytes = $retrievedB WrittenBytes = $writtenB ReadGB = (ConvertTo-SMBeatGB -Bytes $readB) WriteGB = (ConvertTo-SMBeatGB -Bytes $writeB) SentGB = (ConvertTo-SMBeatGB -Bytes $sentB) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $recvB) NicInGB = (ConvertTo-SMBeatGB -Bytes $nicInB) NicOutGB = (ConvertTo-SMBeatGB -Bytes $nicOutB) RetrievedGB = (ConvertTo-SMBeatGB -Bytes $retrievedB) WrittenGB = (ConvertTo-SMBeatGB -Bytes $writtenB) }) } $list.ToArray() } function ConvertTo-SMBeatReportFromAggregate { param( $Aggregate, [object[]]$Records, [datetime]$StartUtc, [datetime]$EndUtc, [TimeZoneInfo]$TimeZone, [string[]]$Granularity, [int]$IntervalSec, $PreviousTotals, [switch]$TotalsOnly ) if ($null -eq $Records) { $Records = @() } $warnings = New-Object System.Collections.Generic.List[string] $actual = [int]$Aggregate.ListenCount if ($actual -eq 0) { $actual = [int]$Aggregate.ServerKindCount } $expected = Get-SMBeatExpectedSampleCount -StartUtc $StartUtc -EndUtc $EndUtc -IntervalSec $IntervalSec $coverage = 0 if ($expected -gt 0) { $coverage = [math]::Min(100, [math]::Round(100.0 * $actual / $expected, 1)) } if ($coverage -lt 90) { $warnings.Add(("Coverage {0}% - collector gaps are possible (expected ~{1} server samples, found {2})." -f $coverage, $expected, $actual)) | Out-Null } if ($Aggregate.WarnNicOut) { $warnings.Add('NIC Out is much larger than SMB Sent. On some hosts SMB share counters miss large reads; retrieved volume falls back to NIC Out.') | Out-Null } if ($Aggregate.WarnEstataRead) { $warnings.Add('TCP 445 outbound is larger than NIC Out and ETW retrieved. Headline uses ETW so it matches users and shares; ESTATA can overcount long reads.') | Out-Null } if ($Aggregate.WarnShareLow) { $warnings.Add('Share bytes are much smaller than TCP 445 retrieved. Sessions open before the collector started map to (unknown) until reconnect; ETW buffer loss is also possible.') | Out-Null } if ($Aggregate.WarnEtwWritten) { $warnings.Add('TCP 445 inbound is much smaller than ETW written. Short-lived writes are counted per user and share; ESTATA often misses them.') | Out-Null } $hourly = @() $daily = @() $weekly = @() $hourlyForPeak = @() if (-not $TotalsOnly) { if ($Granularity -contains 'Hour') { $hourly = @(ConvertTo-SMBeatSeriesRowsFromNative -Items $Aggregate.Hourly) } if ($Granularity -contains 'Day') { $daily = @(ConvertTo-SMBeatSeriesRowsFromNative -Items $Aggregate.Daily) } if ($Granularity -contains 'Week') { $weekly = @(ConvertTo-SMBeatSeriesRowsFromNative -Items $Aggregate.Weekly) } $hourlyForPeak = @(ConvertTo-SMBeatSeriesRowsFromNative -Items $Aggregate.HourlyForPeak) } $peak = Get-SMBeatPeakStats -Hourly $hourlyForPeak -RetrievedBytes ([int64]$Aggregate.RetrievedBytes) -WrittenBytes ([int64]$Aggregate.WrittenBytes) -StartUtc $StartUtc -EndUtc $EndUtc [PSCustomObject]@{ PSTypeName = 'Merlin.SMBeat.Report' StartUtc = $StartUtc EndUtc = $EndUtc TimeZone = $TimeZone.Id CoveragePct = $coverage IntervalSec = $IntervalSec Warnings = $warnings.ToArray() Totals = (ConvertTo-SMBeatTotalsObject -Read ([int64]$Aggregate.ReadBytes) -Write ([int64]$Aggregate.WriteBytes) -Sent ([int64]$Aggregate.SentBytes) -Received ([int64]$Aggregate.ReceivedBytes) -NicIn ([int64]$Aggregate.NicBytesIn) -NicOut ([int64]$Aggregate.NicBytesOut) -Retrieved ([int64]$Aggregate.RetrievedBytes) -Written ([int64]$Aggregate.WrittenBytes) -SmbPerfSent ([int64]$Aggregate.SmbPerfSentBytes) -SmbPerfReceived ([int64]$Aggregate.SmbPerfReceivedBytes)) ByServer = @(ConvertTo-SMBeatNamedTotalsFromNative -Items $Aggregate.ByServer) ByShare = @(ConvertTo-SMBeatNamedTotalsFromNative -Items $Aggregate.ByShare) BySmbPerf = @(ConvertTo-SMBeatNamedTotalsFromNative -Items $Aggregate.BySmbPerf) ByClient = @(ConvertTo-SMBeatNamedTotalsFromNative -Items $Aggregate.ByClient) ByUser = @(ConvertTo-SMBeatNamedTotalsFromNative -Items $Aggregate.ByUser) Hourly = $hourly Daily = $daily Weekly = $weekly PeakStart = $peak.PeakStart PeakRetrievedGB = $peak.PeakRetrievedGB AverageRetrievedGBPerHour = $peak.AverageRetrievedGBPerHour PeakWrittenStart = $peak.PeakWrittenStart PeakWrittenGB = $peak.PeakWrittenGB AverageWrittenGBPerHour = $peak.AverageWrittenGBPerHour PreviousTotals = $PreviousTotals SampleCount = $Records.Count ModuleVersion = (Get-SMBeatModuleVersion) GeneratedUtc = (Get-SMBeatUtcNow) } } function New-SMBeatReportFromSamples { param( [object[]]$Records, [datetime]$StartUtc, [datetime]$EndUtc, [TimeZoneInfo]$TimeZone, [string[]]$Granularity, [int]$IntervalSec, $PreviousTotals, [switch]$TotalsOnly, [switch]$UseManaged ) if ($null -eq $Records) { $Records = @() } if (-not $UseManaged -and (Initialize-SMBeatSampleJsonNative)) { $list = ConvertTo-SMBeatNativeRecordList -Records $Records $grans = @() if ($null -ne $Granularity) { $grans = [string[]]@($Granularity) } $tzId = 'UTC' if ($null -ne $TimeZone) { $tzId = [string]$TimeZone.Id } $arr = $list.ToArray() $agg = [SMBeat.Native.SampleAggregator]::Run($arr, $StartUtc, $EndUtc, $tzId, $grans, [int]$IntervalSec, [bool]$TotalsOnly) return (ConvertTo-SMBeatReportFromAggregate -Aggregate $agg -Records $Records -StartUtc $StartUtc -EndUtc $EndUtc -TimeZone $TimeZone -Granularity $Granularity -IntervalSec $IntervalSec -PreviousTotals $PreviousTotals -TotalsOnly:$TotalsOnly) } New-SMBeatReportFromSamplesManaged -Records $Records -StartUtc $StartUtc -EndUtc $EndUtc -TimeZone $TimeZone -Granularity $Granularity -IntervalSec $IntervalSec -PreviousTotals $PreviousTotals -TotalsOnly:$TotalsOnly } function New-SMBeatReportFromSamplesManaged { param( [object[]]$Records, [datetime]$StartUtc, [datetime]$EndUtc, [TimeZoneInfo]$TimeZone, [string[]]$Granularity, [int]$IntervalSec, $PreviousTotals, [switch]$TotalsOnly ) if ($null -eq $Records) { $Records = @() } $warnings = New-Object System.Collections.Generic.List[string] $serverRecords = @($Records | Where-Object { $_.kind -eq 'tcp445' -and $_.name -eq '_listen445' }) if ($serverRecords.Count -eq 0) { $serverRecords = @($Records | Where-Object { $_.kind -eq 'server' }) } $expected = Get-SMBeatExpectedSampleCount -StartUtc $StartUtc -EndUtc $EndUtc -IntervalSec $IntervalSec $actual = $serverRecords.Count $coverage = 0 if ($expected -gt 0) { $coverage = [math]::Min(100, [math]::Round(100.0 * $actual / $expected, 1)) } if ($coverage -lt 90) { $warnings.Add(("Coverage {0}% - collector gaps are possible (expected ~{1} server samples, found {2})." -f $coverage, $expected, $actual)) | Out-Null } $serverRead = [int64]0 $serverWrite = [int64]0 $serverSent = [int64]0 $serverRecv = [int64]0 $shareRead = [int64]0 $shareWrite = [int64]0 $shareSent = [int64]0 $shareRecv = [int64]0 $nicIn = [int64]0 $nicOut = [int64]0 $tcp445Sent = [int64]0 $tcp445Recv = [int64]0 $hasTcp445 = $false $byServerFromServer = @{} $byServerFromShares = @{} $byServerFromUsers = @{} $byServerFromTcp = @{} $byShare = @{} $byClient = @{} $byClientTcp = @{} $byClientEtw = @{} $byUser = @{} $bySmbPerf = @{} $smbPerfServerSent = [int64]0 $smbPerfServerRecv = [int64]0 $smbPerfShareSent = [int64]0 $smbPerfShareRecv = [int64]0 $hasSmbServer = $false foreach ($rec in $Records) { $r = [int64]0 $w = [int64]0 $s = [int64]0 $v = [int64]0 if ($rec.readBytesDelta) { $r = [int64]$rec.readBytesDelta } if ($rec.writeBytesDelta) { $w = [int64]$rec.writeBytesDelta } if ($rec.sentBytesDelta) { $s = [int64]$rec.sentBytesDelta } if ($rec.receivedBytesDelta) { $v = [int64]$rec.receivedBytesDelta } $kind = [string]$rec.kind $serverName = [string]$rec.server if ($kind -eq 'server') { $serverRead += $r $serverWrite += $w $serverSent += $s $serverRecv += $v Add-SMBeatAccumulator -Map $byServerFromServer -Key $serverName -Read $r -Write $w -Sent $s -Received $v } elseif ($kind -eq 'share') { $shareRead += $r $shareWrite += $w $shareSent += $s $shareRecv += $v Add-SMBeatAccumulator -Map $byShare -Key $rec.name -Read $r -Write $w -Sent $s -Received $v Add-SMBeatAccumulator -Map $byServerFromShares -Key $serverName -Read $r -Write $w -Sent $s -Received $v } elseif ($kind -eq 'session') { $clientKey = $rec.client if ([string]::IsNullOrWhiteSpace([string]$clientKey)) { $clientKey = [string]$rec.name } $userKey = $rec.user if ([string]::IsNullOrWhiteSpace([string]$userKey)) { $userKey = '(unknown)' } if (-not [string]::IsNullOrWhiteSpace([string]$clientKey)) { Add-SMBeatAccumulator -Map $byClient -Key ([string]$clientKey) -Read $r -Write $w -Sent $s -Received $v } Add-SMBeatAccumulator -Map $byUser -Key ([string]$userKey) -Read $r -Write $w -Sent $s -Received $v } elseif ($kind -eq 'client') { $clientKey = $rec.client if ([string]::IsNullOrWhiteSpace([string]$clientKey)) { $clientKey = [string]$rec.name } if (-not [string]::IsNullOrWhiteSpace([string]$clientKey)) { Add-SMBeatAccumulator -Map $byClientEtw -Key ([string]$clientKey) -Read $r -Write $w -Sent $s -Received $v } } elseif ($kind -eq 'user') { $userKey = $rec.user if ([string]::IsNullOrWhiteSpace([string]$userKey)) { $userKey = [string]$rec.name } if ([string]::IsNullOrWhiteSpace([string]$userKey)) { $userKey = '(unknown)' } Add-SMBeatAccumulator -Map $byUser -Key ([string]$userKey) -Read $r -Write $w -Sent $s -Received $v if (-not [string]::IsNullOrWhiteSpace($serverName)) { Add-SMBeatAccumulator -Map $byServerFromUsers -Key $serverName -Read $r -Write $w -Sent $s -Received $v } } elseif ($kind -eq 'tcp445') { $hasTcp445 = $true $name = [string]$rec.name if ($name -eq '_listen445') { $tcp445Sent += $s $tcp445Recv += $v Add-SMBeatAccumulator -Map $byServerFromTcp -Key $serverName -Read 0 -Write 0 -Sent $s -Received $v } else { $clientKey = $rec.client if ([string]::IsNullOrWhiteSpace([string]$clientKey)) { $clientKey = $name } Add-SMBeatAccumulator -Map $byClientTcp -Key ([string]$clientKey) -Read 0 -Write 0 -Sent $s -Received $v } } elseif ($kind -eq 'nic') { $nicIn += $v $nicOut += $s } elseif ($kind -eq 'smbperf') { $smbName = [string]$rec.name if ($smbName -eq '_server') { $hasSmbServer = $true $smbPerfServerSent += $s $smbPerfServerRecv += $v } else { Add-SMBeatAccumulator -Map $bySmbPerf -Key $smbName -Read $r -Write $w -Sent $s -Received $v $smbPerfShareSent += $s $smbPerfShareRecv += $v } } } $useShareTotals = ($shareSent + $shareRecv) -gt ($serverSent + $serverRecv) $read = $serverRead $write = $serverWrite $sent = $serverSent $recv = $serverRecv $byServer = $byServerFromServer if ($useShareTotals) { $read = $shareRead $write = $shareWrite $sent = $shareSent $recv = $shareRecv $byServer = $byServerFromShares } if ($hasTcp445) { $byServer = $byServerFromTcp if ($byClientTcp.Count -gt 0) { $byClient = $byClientTcp } } if ($byClientEtw.Count -gt 0) { if ($byClient.Count -eq 0) { $byClient = $byClientEtw } else { foreach ($ek in @($byClientEtw.Keys)) { $etw = $byClientEtw[$ek] if (-not $byClient.ContainsKey($ek)) { $byClient[$ek] = $etw continue } $cur = $byClient[$ek] $etwSent = [int64]$etw.Sent $curSent = [int64]$cur.Sent if ($etwSent -gt 1048576 -and $curSent -gt [int64]([double]$etwSent * 1.1)) { $cur.Sent = $etwSent } elseif ($etwSent -gt $curSent) { $cur.Sent = $etwSent } if ([int64]$etw.Received -gt [int64]$cur.Received) { $cur.Received = $etw.Received } if ([int64]$etw.Read -gt [int64]$cur.Read) { $cur.Read = $etw.Read } if ([int64]$etw.Write -gt [int64]$cur.Write) { $cur.Write = $etw.Write } } } } $retrieved = $nicOut $written = $recv if ($hasTcp445) { $retrieved = $tcp445Sent $written = $tcp445Recv } elseif ($nicOut -gt 104857600 -and $nicOut -gt (10 * [math]::Max($sent, [int64]1))) { $warnings.Add('NIC Out is much larger than SMB Sent. On some hosts SMB share counters miss large reads; retrieved volume falls back to NIC Out.') | Out-Null } $etwRetrieved = Get-SMBeatEtwRetrievedBytes -ByUser $byUser -ShareSent $shareSent $etwWritten = $shareRecv foreach ($uk in @($byUser.Keys)) { $uRecv = [int64]$byUser[$uk].Received if ($uRecv -gt $etwWritten) { $etwWritten = $uRecv } } $retrievedFromEtw = $false if ($hasTcp445 -and $nicOut -gt 1048576 -and $retrieved -gt $nicOut -and $retrieved -gt [int64]([double]$nicOut * 1.1)) { if ($etwRetrieved -gt 1048576 -and (Test-SMBeatVolumeNear -Left $etwRetrieved -Right $nicOut)) { $retrieved = $etwRetrieved $retrievedFromEtw = $true $warnings.Add('TCP 445 outbound is larger than NIC Out and ETW retrieved. Headline uses ETW so it matches users and shares; ESTATA can overcount long reads.') | Out-Null } } if ($hasTcp445 -and $retrieved -gt 10485760) { $shareFloor = [int64]([math]::Floor($retrieved * 0.1)) if ($shareSent -lt $shareFloor) { $warnings.Add('Share bytes are much smaller than TCP 445 retrieved. Sessions open before the collector started map to (unknown) until reconnect; ETW buffer loss is also possible.') | Out-Null } } if ($hasTcp445 -and $etwWritten -gt 1048576 -and $etwWritten -gt (10 * [math]::Max($written, [int64]1))) { $written = $etwWritten $warnings.Add('TCP 445 inbound is much smaller than ETW written. Short-lived writes are counted per user and share; ESTATA often misses them.') | Out-Null } foreach ($sk in @($byServer.Keys)) { $etwRecv = [int64]0 $etwSent = [int64]0 if ($byServerFromShares.ContainsKey($sk)) { $etwRecv = [int64]$byServerFromShares[$sk].Received $etwSent = [int64]$byServerFromShares[$sk].Sent } if ($byServerFromUsers.ContainsKey($sk)) { $uRecv = [int64]$byServerFromUsers[$sk].Received $uSent = [int64]$byServerFromUsers[$sk].Sent if ($uRecv -gt $etwRecv) { $etwRecv = $uRecv } if ($uSent -gt $etwSent) { $etwSent = $uSent } } $curRecv = [int64]$byServer[$sk].Received if ($etwRecv -gt 1048576 -and $etwRecv -gt (10 * [math]::Max($curRecv, [int64]1))) { $byServer[$sk].Received = $etwRecv } $curSent = [int64]$byServer[$sk].Sent if ($retrievedFromEtw -and $etwSent -gt 1048576) { $byServer[$sk].Sent = $etwSent } elseif ($etwSent -gt 1048576 -and $curSent -gt [int64]([double]$etwSent * 1.1) -and $nicOut -gt 1048576 -and (Test-SMBeatVolumeNear -Left $etwSent -Right $nicOut)) { $byServer[$sk].Sent = $etwSent } } if ($hasTcp445 -and $byServer.Count -eq 1) { foreach ($sk in @($byServer.Keys)) { $byServer[$sk].Sent = $retrieved $byServer[$sk].Received = $written } } $hourly = @() $daily = @() $weekly = @() $hourlyForPeak = @() if (-not $TotalsOnly) { $wanted = New-Object System.Collections.Generic.List[string] foreach ($g in @($Granularity)) { if ($g -and -not $wanted.Contains($g)) { [void]$wanted.Add($g) } } if (-not $wanted.Contains('Hour')) { [void]$wanted.Add('Hour') } $seriesSet = Get-SMBeatSeriesSet -Records $Records -StartUtc $StartUtc -EndUtc $EndUtc -TimeZone $TimeZone -Granularity $wanted.ToArray() if ($Granularity -contains 'Hour') { $hourly = @($seriesSet.Hour) } if ($Granularity -contains 'Day') { $daily = @($seriesSet.Day) } if ($Granularity -contains 'Week') { $weekly = @($seriesSet.Week) } $hourlyForPeak = @($seriesSet.Hour) } $smbPerfSent = $smbPerfShareSent $smbPerfRecv = $smbPerfShareRecv if ($hasSmbServer -and (($smbPerfServerSent + $smbPerfServerRecv) -ge ($smbPerfShareSent + $smbPerfShareRecv))) { $smbPerfSent = $smbPerfServerSent $smbPerfRecv = $smbPerfServerRecv } $peak = Get-SMBeatPeakStats -Hourly $hourlyForPeak -RetrievedBytes $retrieved -WrittenBytes $written -StartUtc $StartUtc -EndUtc $EndUtc [PSCustomObject]@{ PSTypeName = 'Merlin.SMBeat.Report' StartUtc = $StartUtc EndUtc = $EndUtc TimeZone = $TimeZone.Id CoveragePct = $coverage IntervalSec = $IntervalSec Warnings = $warnings.ToArray() Totals = (ConvertTo-SMBeatTotalsObject -Read $read -Write $write -Sent $sent -Received $recv -NicIn $nicIn -NicOut $nicOut -Retrieved $retrieved -Written $written -SmbPerfSent $smbPerfSent -SmbPerfReceived $smbPerfRecv) ByServer = @(ConvertTo-SMBeatNamedTotals -Map $byServer) ByShare = @(ConvertTo-SMBeatNamedTotals -Map $byShare) BySmbPerf = @(ConvertTo-SMBeatNamedTotals -Map $bySmbPerf) ByClient = @(ConvertTo-SMBeatNamedTotals -Map $byClient) ByUser = @(ConvertTo-SMBeatNamedTotals -Map $byUser) Hourly = $hourly Daily = $daily Weekly = $weekly PeakStart = $peak.PeakStart PeakRetrievedGB = $peak.PeakRetrievedGB AverageRetrievedGBPerHour = $peak.AverageRetrievedGBPerHour PeakWrittenStart = $peak.PeakWrittenStart PeakWrittenGB = $peak.PeakWrittenGB AverageWrittenGBPerHour = $peak.AverageWrittenGBPerHour PreviousTotals = $PreviousTotals SampleCount = $Records.Count ModuleVersion = (Get-SMBeatModuleVersion) GeneratedUtc = (Get-SMBeatUtcNow) } } function New-SMBeatReportFromDataRoot { param( [string]$DataRoot, [datetime]$WindowStart, [datetime]$WindowEnd, [datetime]$PrevStart, [datetime]$PrevEnd, [TimeZoneInfo]$TimeZone, [string[]]$Granularity ) $config = Read-SMBeatConfig -DataRoot $DataRoot $interval = [int]$config.IntervalSec if ($interval -le 0) { $interval = 60 } $windows = Read-SMBeatSampleWindows -DataRoot $DataRoot -PrevStart $PrevStart -PrevEnd $PrevEnd -WindowStart $WindowStart -WindowEnd $WindowEnd $records = @($windows.Current) $prevRecords = @($windows.Previous) $prevTotals = $null if ($prevRecords.Count -gt 0) { $prevReport = New-SMBeatReportFromSamples -Records $prevRecords -StartUtc $PrevStart -EndUtc $PrevEnd -TimeZone $TimeZone -Granularity @() -IntervalSec $interval -TotalsOnly $prevTotals = $prevReport.Totals } New-SMBeatReportFromSamples -Records $records -StartUtc $WindowStart -EndUtc $WindowEnd -TimeZone $TimeZone -Granularity $Granularity -IntervalSec $interval -PreviousTotals $prevTotals } function Resolve-SMBeatReportWindow { param( [datetime]$Start, [datetime]$End, [int]$LastHours, [int]$LastDays, [int]$LastWeeks ) $endUtc = Get-SMBeatUtcNow if ($PSBoundParameters.ContainsKey('End') -and $End) { $endUtc = $End.ToUniversalTime() } $startUtc = $null if ($PSBoundParameters.ContainsKey('Start') -and $Start) { $startUtc = $Start.ToUniversalTime() } elseif ($LastHours -gt 0) { $startUtc = $endUtc.AddHours(-1 * $LastHours) } elseif ($LastDays -gt 0) { $startUtc = $endUtc.AddDays(-1 * $LastDays) } elseif ($LastWeeks -gt 0) { $startUtc = $endUtc.AddDays(-7 * $LastWeeks) } else { $startUtc = $endUtc.AddDays(-7) } if ($startUtc -ge $endUtc) { throw 'Start must be earlier than End.' } [PSCustomObject]@{ StartUtc = $startUtc EndUtc = $endUtc } } function Merge-SMBeatNamedTotalLists { param( [object[]]$Lists ) $map = @{} foreach ($list in $Lists) { if ($null -eq $list) { continue } foreach ($row in @($list)) { Add-SMBeatAccumulator -Map $map -Key ([string]$row.Name) -Read ([int64]$row.ReadBytes) -Write ([int64]$row.WriteBytes) -Sent ([int64]$row.SentBytes) -Received ([int64]$row.ReceivedBytes) } } @(ConvertTo-SMBeatNamedTotals -Map $map) } function Merge-SMBeatSeriesLists { param( [object[]]$Lists, [string]$Granularity ) $map = @{} foreach ($list in $Lists) { if ($null -eq $list) { continue } foreach ($row in @($list)) { $key = $row.PeriodStart.ToString('o') if (-not $map.ContainsKey($key)) { $map[$key] = @{ Start = $row.PeriodStart End = $row.PeriodEnd Read = [int64]0 Write = [int64]0 Sent = [int64]0 Received = [int64]0 NicIn = [int64]0 NicOut = [int64]0 Retrieved = [int64]0 Written = [int64]0 } } $map[$key].Read += [int64]$row.ReadBytes $map[$key].Write += [int64]$row.WriteBytes if ($row.SentBytes) { $map[$key].Sent += [int64]$row.SentBytes } if ($row.ReceivedBytes) { $map[$key].Received += [int64]$row.ReceivedBytes } if ($row.NicBytesIn) { $map[$key].NicIn += [int64]$row.NicBytesIn } if ($row.NicBytesOut) { $map[$key].NicOut += [int64]$row.NicBytesOut } if ($row.RetrievedBytes) { $map[$key].Retrieved += [int64]$row.RetrievedBytes } elseif ($row.NicBytesOut) { $map[$key].Retrieved += [int64]$row.NicBytesOut } if ($row.PSObject.Properties['WrittenBytes'] -and $row.WrittenBytes) { $map[$key].Written += [int64]$row.WrittenBytes } elseif ($row.ReceivedBytes) { $map[$key].Written += [int64]$row.ReceivedBytes } } } $result = New-Object System.Collections.Generic.List[object] foreach ($key in ($map.Keys | Sort-Object)) { $b = $map[$key] $result.Add([PSCustomObject]@{ Granularity = $Granularity PeriodStart = $b.Start PeriodEnd = $b.End ReadBytes = $b.Read WriteBytes = $b.Write SentBytes = $b.Sent ReceivedBytes = $b.Received NicBytesIn = $b.NicIn NicBytesOut = $b.NicOut RetrievedBytes = $b.Retrieved WrittenBytes = $b.Written ReadGB = (ConvertTo-SMBeatGB -Bytes $b.Read) WriteGB = (ConvertTo-SMBeatGB -Bytes $b.Write) SentGB = (ConvertTo-SMBeatGB -Bytes $b.Sent) ReceivedGB = (ConvertTo-SMBeatGB -Bytes $b.Received) NicInGB = (ConvertTo-SMBeatGB -Bytes $b.NicIn) NicOutGB = (ConvertTo-SMBeatGB -Bytes $b.NicOut) RetrievedGB = (ConvertTo-SMBeatGB -Bytes $b.Retrieved) WrittenGB = (ConvertTo-SMBeatGB -Bytes $b.Written) }) | Out-Null } $result.ToArray() } function Merge-SMBeatReports { param( [Parameter(Mandatory = $true)] [object[]]$Reports, $PreviousTotals ) if ($null -eq $Reports -or $Reports.Count -eq 0) { return $null } if ($Reports.Count -eq 1) { $one = $Reports[0] if ($PreviousTotals) { $one.PreviousTotals = $PreviousTotals } return $one } $first = $Reports[0] $read = [int64]0 $write = [int64]0 $sent = [int64]0 $recv = [int64]0 $nicIn = [int64]0 $nicOut = [int64]0 $smbPerfSent = [int64]0 $smbPerfRecv = [int64]0 $retrieved = [int64]0 $written = [int64]0 $samples = 0 $covSum = 0.0 $warnings = New-Object System.Collections.Generic.List[string] $serverLists = New-Object System.Collections.Generic.List[object] $shareLists = New-Object System.Collections.Generic.List[object] $clientLists = New-Object System.Collections.Generic.List[object] $userLists = New-Object System.Collections.Generic.List[object] $smbPerfLists = New-Object System.Collections.Generic.List[object] $hourlyLists = New-Object System.Collections.Generic.List[object] $dailyLists = New-Object System.Collections.Generic.List[object] $weeklyLists = New-Object System.Collections.Generic.List[object] foreach ($r in $Reports) { $read += [int64]$r.Totals.ReadBytes $write += [int64]$r.Totals.WriteBytes $sent += [int64]$r.Totals.SentBytes $recv += [int64]$r.Totals.ReceivedBytes $nicIn += [int64]$r.Totals.NicBytesIn $nicOut += [int64]$r.Totals.NicBytesOut if ($r.Totals.PSObject.Properties['SmbPerfSentBytes'] -and $r.Totals.SmbPerfSentBytes) { $smbPerfSent += [int64]$r.Totals.SmbPerfSentBytes } if ($r.Totals.PSObject.Properties['SmbPerfReceivedBytes'] -and $r.Totals.SmbPerfReceivedBytes) { $smbPerfRecv += [int64]$r.Totals.SmbPerfReceivedBytes } if ($r.Totals.RetrievedBytes) { $retrieved += [int64]$r.Totals.RetrievedBytes } else { $retrieved += [int64]$r.Totals.NicBytesOut } if ($r.Totals.PSObject.Properties['WrittenBytes'] -and $r.Totals.WrittenBytes) { $written += [int64]$r.Totals.WrittenBytes } else { $written += [int64]$r.Totals.ReceivedBytes } $samples += [int]$r.SampleCount $covSum += [double]$r.CoveragePct foreach ($w in @($r.Warnings)) { if ($w) { $warnings.Add([string]$w) | Out-Null } } $serverLists.Add(@($r.ByServer)) | Out-Null $shareLists.Add(@($r.ByShare)) | Out-Null $clientLists.Add(@($r.ByClient)) | Out-Null $userLists.Add(@($r.ByUser)) | Out-Null if ($r.PSObject.Properties['BySmbPerf']) { $smbPerfLists.Add(@($r.BySmbPerf)) | Out-Null } $hourlyLists.Add(@($r.Hourly)) | Out-Null $dailyLists.Add(@($r.Daily)) | Out-Null $weeklyLists.Add(@($r.Weekly)) | Out-Null } $hourlyMerged = @(Merge-SMBeatSeriesLists -Lists $hourlyLists.ToArray() -Granularity Hour) $peak = Get-SMBeatPeakStats -Hourly $hourlyMerged -RetrievedBytes $retrieved -WrittenBytes $written -StartUtc $first.StartUtc -EndUtc $first.EndUtc [PSCustomObject]@{ PSTypeName = 'Merlin.SMBeat.Report' StartUtc = $first.StartUtc EndUtc = $first.EndUtc TimeZone = $first.TimeZone CoveragePct = [math]::Round($covSum / $Reports.Count, 1) IntervalSec = $first.IntervalSec Warnings = $warnings.ToArray() Totals = (ConvertTo-SMBeatTotalsObject -Read $read -Write $write -Sent $sent -Received $recv -NicIn $nicIn -NicOut $nicOut -Retrieved $retrieved -Written $written -SmbPerfSent $smbPerfSent -SmbPerfReceived $smbPerfRecv) ByServer = (Merge-SMBeatNamedTotalLists -Lists $serverLists.ToArray()) ByShare = (Merge-SMBeatNamedTotalLists -Lists $shareLists.ToArray()) BySmbPerf = (Merge-SMBeatNamedTotalLists -Lists $smbPerfLists.ToArray()) ByClient = (Merge-SMBeatNamedTotalLists -Lists $clientLists.ToArray()) ByUser = (Merge-SMBeatNamedTotalLists -Lists $userLists.ToArray()) Hourly = $hourlyMerged Daily = @(Merge-SMBeatSeriesLists -Lists $dailyLists.ToArray() -Granularity Day) Weekly = @(Merge-SMBeatSeriesLists -Lists $weeklyLists.ToArray() -Granularity Week) PeakStart = $peak.PeakStart PeakRetrievedGB = $peak.PeakRetrievedGB AverageRetrievedGBPerHour = $peak.AverageRetrievedGBPerHour PeakWrittenStart = $peak.PeakWrittenStart PeakWrittenGB = $peak.PeakWrittenGB AverageWrittenGBPerHour = $peak.AverageWrittenGBPerHour PreviousTotals = $PreviousTotals SampleCount = $samples ModuleVersion = (Get-SMBeatModuleVersion) GeneratedUtc = (Get-SMBeatUtcNow) } } |