Private/Get-VMMetricSeries.ps1
|
function Get-VMMetricSeries { <# .SYNOPSIS Pulls one or more Azure Monitor platform-metric time series for a VM in a single request per 30-day chunk, returning a map of metricName -> datapoints. .DESCRIPTION The only function that calls Get-AzMetric. It requests ALL the named metrics in one call (Get-AzMetric accepts an array), which halves the number of round-trips versus one call per metric. The window is paged in <=30-day slices because the metrics API silently caps a single response to ~31 days regardless of grain -- without chunking you lose everything older than ~31 days. Pass -AzContext to bind the call to an explicit Azure context (concurrency-safe under ForEach-Object -Parallel, where there is no shared default context). .PARAMETER ResourceId The full ARM resource id of the VM. .PARAMETER MetricName One or more platform metric names, e.g. 'Percentage CPU','Available Memory Bytes'. .PARAMETER StartTime UTC start of the window (inclusive). .PARAMETER EndTime UTC end of the window (exclusive). .PARAMETER TimeGrain Aggregation grain as a TimeSpan, default 00:05:00 (PT5M). .PARAMETER AzContext Optional Azure context object (from Get-AzContext) passed to Get-AzMetric as -DefaultProfile. Required when running in a parallel runspace. .PARAMETER MaxRetry Max retries on throttling (HTTP 429), default 5. .OUTPUTS [hashtable] metricName -> System.Collections.Generic.List[pscustomobject] of { TimeStamp, Average, Minimum, Maximum }. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [string] $ResourceId, [Parameter(Mandatory)] [string[]] $MetricName, [Parameter(Mandatory)] [datetime] $StartTime, [Parameter(Mandatory)] [datetime] $EndTime, [timespan] $TimeGrain = ([timespan]'00:05:00'), [object] $AzContext, [int] $MaxRetry = 5 ) $result = @{} foreach ($n in $MetricName) { $result[$n] = [System.Collections.Generic.List[pscustomobject]]::new() } $chunk = [timespan]::FromDays(30) $cursor = $StartTime while ($cursor -lt $EndTime) { $sliceEnd = $cursor + $chunk if ($sliceEnd -gt $EndTime) { $sliceEnd = $EndTime } $attempt = 0 while ($true) { try { $callArgs = @{ ResourceId = $ResourceId MetricName = $MetricName TimeGrain = $TimeGrain StartTime = $cursor EndTime = $sliceEnd AggregationType = 'Average' WarningAction = 'SilentlyContinue' ErrorAction = 'Stop' } if ($AzContext) { $callArgs['DefaultProfile'] = $AzContext } $metric = Get-AzMetric @callArgs foreach ($mObj in $metric) { $name = $mObj.Name.Value if (-not $result.ContainsKey($name)) { $result[$name] = [System.Collections.Generic.List[pscustomobject]]::new() } foreach ($dp in $mObj.Data) { if ($null -eq $dp.Average -and $null -eq $dp.Minimum -and $null -eq $dp.Maximum) { continue } $result[$name].Add([pscustomobject]@{ TimeStamp = [datetime] $dp.TimeStamp Average = $dp.Average Minimum = $dp.Minimum Maximum = $dp.Maximum }) } } break } catch { $status = $null if ($_.Exception.Response) { $status = [int]$_.Exception.Response.StatusCode } if ($status -eq 429 -and $attempt -lt $MaxRetry) { $attempt++ $retryAfter = 5 * $attempt $hdr = $_.Exception.Response.Headers if ($hdr -and $hdr['Retry-After']) { [int]::TryParse([string]$hdr['Retry-After'], [ref]$retryAfter) | Out-Null } Write-Warning "Throttled (429) for $ResourceId; retry $attempt/$MaxRetry after ${retryAfter}s." Start-Sleep -Seconds $retryAfter continue } throw } } $cursor = $sliceEnd } return $result } |