Private/Collector.ps1
|
#Requires -Version 5.1 function Get-SMBeatCimInstanceSafe { param( [Parameter(Mandatory = $true)] [string]$ClassName, $CimSession ) $params = @{ ClassName = $ClassName ErrorAction = 'Stop' } if ($CimSession) { $params['CimSession'] = $CimSession } try { Get-CimInstance @params } catch { Write-Verbose ("CIM class {0} not available: {1}" -f $ClassName, $_.Exception.Message) @() } } function Get-SMBeatRawLong { param( $Object, [string[]]$PropertyNames ) foreach ($name in $PropertyNames) { $prop = $Object.PSObject.Properties[$name] if ($prop -and $null -ne $prop.Value) { return [uint64]$prop.Value } } return [uint64]0 } function New-SMBeatCounterSnapshot { param( [Parameter(Mandatory = $true)] [string]$Kind, [Parameter(Mandatory = $true)] [string]$Name, [uint64]$ReadRaw, [uint64]$WriteRaw, [uint64]$SentRaw, [uint64]$ReceivedRaw, [string]$Client, [string]$User ) [PSCustomObject]@{ Kind = $Kind Name = $Name ReadRaw = $ReadRaw WriteRaw = $WriteRaw SentRaw = $SentRaw ReceivedRaw = $ReceivedRaw Client = $Client User = $User } } function Get-SMBeatNicSnapshots { param( $CimSession ) $items = New-Object System.Collections.Generic.List[object] $nicRows = @(Get-SMBeatCimInstanceSafe -ClassName 'Win32_PerfRawData_Tcpip_NetworkInterface' -CimSession $CimSession) foreach ($row in $nicRows) { $name = [string]$row.Name if (Test-SMBeatSkipInstance -Name $name -Kind 'nic') { continue } if ($name -like '*Loopback*' -or $name -like '*isatap*' -or $name -like '*Teredo*') { continue } $items.Add((New-SMBeatCounterSnapshot -Kind 'nic' -Name $name ` -ReadRaw 0 -WriteRaw 0 ` -SentRaw (Get-SMBeatRawLong -Object $row -PropertyNames @('BytesSentPersec')) ` -ReceivedRaw (Get-SMBeatRawLong -Object $row -PropertyNames @('BytesReceivedPersec')))) | Out-Null } return $items } function Get-SMBeatNicDeltaRecords { param( [Parameter(Mandatory = $true)] [hashtable]$NicPrev, [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec, $CimSession ) $emitted = New-Object System.Collections.Generic.List[object] $snaps = @(Get-SMBeatNicSnapshots -CimSession $CimSession) foreach ($snap in $snaps) { $key = 'nic:{0}' -f $snap.Name $sent = [uint64]$snap.SentRaw $recv = [uint64]$snap.ReceivedRaw if ($NicPrev.ContainsKey($key)) { $prev = $NicPrev[$key] $record = ConvertTo-SMBeatSampleRecord -Snapshot $snap -Server $Server -Utc $Utc -IntervalSec $IntervalSec ` -ReadDelta 0 -WriteDelta 0 ` -SentDelta (Get-SMBeatByteDelta -Current $sent -Previous ([uint64]$prev.SentRaw)) ` -ReceivedDelta (Get-SMBeatByteDelta -Current $recv -Previous ([uint64]$prev.ReceivedRaw)) $emitted.Add($record) | Out-Null } $NicPrev[$key] = @{ SentRaw = $sent ReceivedRaw = $recv } } return $emitted.ToArray() } function Save-SMBeatHeartbeat { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [datetime]$Utc, [int]$IntervalSec, [string]$ComputerName, [string]$LastError, [switch]$Success, [int]$EmittedCount, [int]$ProcessId ) if ($ProcessId -le 0) { $ProcessId = $PID } $now = ConvertTo-SMBeatUtcString -DateTime $Utc $existingJson = Read-SMBeatTextFile -Path (Get-SMBeatHeartbeatPath -DataRoot $DataRoot) $successCount = 0 $lastSuccess = $null if ($existingJson) { $existing = ConvertFrom-SMBeatJson -Json $existingJson if ($existing.SuccessCount) { $successCount = [int]$existing.SuccessCount } if ($existing.LastSuccessUtc) { $lastSuccess = $existing.LastSuccessUtc } } if ($Success) { $successCount++ $lastSuccess = $now } $obj = [PSCustomObject]@{ LastAttemptUtc = $now LastSuccessUtc = $lastSuccess LastError = $LastError IntervalSec = $IntervalSec ComputerName = $ComputerName SuccessCount = $successCount EmittedCount = $EmittedCount Success = [bool]$Success Pid = $ProcessId Mode = 'tcp445' } Write-SMBeatTextFile -Path (Get-SMBeatHeartbeatPath -DataRoot $DataRoot) -Text (ConvertTo-SMBeatJson -InputObject $obj) } function Save-SMBeatWatcherState { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [datetime]$Utc ) $obj = [PSCustomObject]@{ LastSampleUtc = (ConvertTo-SMBeatUtcString -DateTime $Utc) Pid = $PID Mode = 'tcp445' } Write-SMBeatTextFile -Path (Get-SMBeatStatePath -DataRoot $DataRoot) -Text (ConvertTo-SMBeatJson -InputObject $obj) } function Add-SMBeatSampleLines { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [Parameter(Mandatory = $true)] [datetime]$Utc, [AllowEmptyCollection()] [object[]]$Records ) if ($null -eq $Records -or $Records.Count -eq 0) { return } $path = Get-SMBeatSampleFilePath -DataRoot $DataRoot -Utc $Utc $sb = New-Object System.Text.StringBuilder foreach ($record in $Records) { $line = (ConvertTo-Json -InputObject $record -Depth 6 -Compress) [void]$sb.AppendLine($line) } Add-SMBeatTextFile -Path $path -Text $sb.ToString() } function Invoke-SMBeatRetention { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [int]$RetentionDays = 90 ) if ($RetentionDays -le 0) { return } $dir = Get-SMBeatSampleDirectory -DataRoot $DataRoot if (-not (Test-Path -LiteralPath $dir)) { return } $cutoff = (Get-SMBeatUtcNow).Date.AddDays(-1 * $RetentionDays) Get-ChildItem -LiteralPath $dir -Filter '*.jsonl' -ErrorAction SilentlyContinue | ForEach-Object { $dayPart = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) $parsed = [datetime]::MinValue if ([datetime]::TryParseExact($dayPart, 'yyyy-MM-dd', $script:SMBeatInvariant, [System.Globalization.DateTimeStyles]::None, [ref]$parsed)) { if ($parsed -lt $cutoff) { Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue } } } } function ConvertTo-SMBeatSampleRecord { param( [Parameter(Mandatory = $true)] $Snapshot, [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec, [int64]$ReadDelta, [int64]$WriteDelta, [int64]$SentDelta, [int64]$ReceivedDelta ) [PSCustomObject]@{ ts = (ConvertTo-SMBeatUtcString -DateTime $Utc) server = $Server kind = $Snapshot.Kind name = $Snapshot.Name client = $Snapshot.Client user = $Snapshot.User readBytesDelta = $ReadDelta writeBytesDelta = $WriteDelta sentBytesDelta = $SentDelta receivedBytesDelta = $ReceivedDelta sampleIntervalSec = $IntervalSec } } function Invoke-SMBeatSampleInternal { [CmdletBinding()] param( [string]$Path, [int]$WaitTimeoutSec = 15 ) $pack = Resolve-SMBeatWatcherDataRoot -Path $Path $dataRoot = $pack.DataRoot if (-not (Test-SMBeatWatcherAlive -Path $dataRoot)) { throw 'Collector must be registered (resident). Use Register-SMBeatCollector -Wait.' } Request-SMBeatWatcherFlush -DataRoot $dataRoot $deadline = (Get-SMBeatUtcNow).AddSeconds($WaitTimeoutSec) $flushPath = Get-SMBeatFlushRequestPath -DataRoot $dataRoot do { if (-not (Test-Path -LiteralPath $flushPath)) { return (Get-SMBeatStatusInternal -Path $dataRoot) } Start-Sleep -Milliseconds 250 } while ((Get-SMBeatUtcNow) -lt $deadline) throw 'Collector did not flush in time. Check Get-SMBeatStatus and collector.log.' } |