Public/Get-DMPerformance.ps1

$script:DMPerformanceObjectTypeMap = [ordered]@{
    Disk         = 10
    LUN          = 11
    FileSystem   = 40
    EthernetPort = 213
    BondPort     = 235
    FCPort       = 212
    StoragePool  = 216
    Controller   = 207
    Host         = 21
    System       = 201
}

$script:DMDefaultPerformanceMetrics = @(
    'TotalIOPS', 'ReadIOPS', 'WriteIOPS',
    'BandwidthMBps', 'ReadBandwidthMBps', 'WriteBandwidthMBps',
    'AvgLatencyMs', 'ReadLatencyMs', 'WriteLatencyMs',
    'QueueLength'
)

$script:DMDefaultNasPerformanceMetrics = @(
    'Ops', 'ReadOps', 'WriteOps',
    'ReadBandwidthMBps', 'WriteBandwidthMBps',
    'AvgReadOpsResponseTimeUs', 'AvgWriteOpsResponseTimeUs'
)

function Get-DMPerformance {
    <#
    .SYNOPSIS
        Retrieves realtime performance samples for OceanStor objects.
 
    .DESCRIPTION
        Wraps the performance_data batch interface (POST, per doc section 4.14.1.1.2). Returns one
        OceanStor.PerformanceSample per requested object, with one property per requested metric.
 
    .PARAMETER WebSession
        Optional parameter to define the session to be use on the REST call. If not defined, the module's cached $script:CurrentOceanstorSession session will be used
 
    .PARAMETER ObjectType
        The type of object to retrieve performance data for.
 
    .PARAMETER ObjectId
        One or more object IDs to retrieve performance data for. Pipeline-able.
 
    .PARAMETER Metric
        One or more friendly metric names (see Get-DMPerformanceIndicatorMap). Defaults to a
        common IOPS/bandwidth/latency/queue-length set when omitted, or to a NAS
        OPS/bandwidth/response-time set when ObjectType is FileSystem.
 
    .INPUTS
        System.String
 
    .OUTPUTS
        System.Collections.ArrayList
 
    .EXAMPLE
        PS> Get-DMPerformance -ObjectType Controller -ObjectId '0A','0B'
 
    .EXAMPLE
        PS> Get-DMPerformance -ObjectType LUN -ObjectId '1' -Metric TotalIOPS,AvgLatencyMs
 
    .NOTES
        Filename: Get-DMPerformance.ps1
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.ArrayList])]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $false)]
        [pscustomobject]$WebSession,

        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateSet('LUN', 'FileSystem', 'Controller', 'StoragePool', 'Disk', 'EthernetPort', 'BondPort', 'FCPort', 'Host', 'System')]
        [string]$ObjectType,

        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$ObjectId,

        [Parameter(Mandatory = $false)]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                (Get-DMPerformanceIndicatorMap).Keys | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [ValidateScript({
                $validNames = (Get-DMPerformanceIndicatorMap).Keys
                foreach ($metricName in $_) {
                    if ($metricName -notin $validNames) {
                        throw "Unknown performance metric '$metricName'. Valid metrics: $($validNames -join ', ')"
                    }
                }
                return $true
            })]
        [string[]]$Metric
    )

    begin {
        $session = if ($WebSession) { $WebSession } else { $script:CurrentOceanstorSession }
        $indicatorMap = Get-DMPerformanceIndicatorMap
        $metricNames = @(
            if ($PSBoundParameters.ContainsKey('Metric') -and $Metric) { $Metric }
            elseif ($ObjectType -eq 'FileSystem') { $script:DMDefaultNasPerformanceMetrics }
            else { $script:DMDefaultPerformanceMetrics }
        )
        $indicatorIds = @($metricNames | ForEach-Object { $indicatorMap[$_].Id })
        $objectTypeId = $script:DMPerformanceObjectTypeMap[$ObjectType]
        $collectedIds = [System.Collections.Generic.List[string]]::new()
    }

    process {
        foreach ($id in $ObjectId) {
            $collectedIds.Add($id)
        }
    }

    end {
        if ($collectedIds.Count -eq 0) {
            return [System.Collections.ArrayList]::new()
        }

        $body = @{
            object_type = $objectTypeId
            object_list = @($collectedIds)
            indicators  = $indicatorIds
        }

        $maxAttempts = 2
        $attempt = 0
        $data = $null
        while ($attempt -lt $maxAttempts) {
            $attempt++
            try {
                $data = Invoke-DeviceManager -WebSession $session -Method 'POST' -Resource 'performance_data' -BodyData $body | Select-DMResponseData
                break
            }
            catch {
                $isConcurrencyError = "$($_.Exception.Message)" -match '(?i)(being invoked|already in use|concurrent|in progress)'
                if ($isConcurrencyError -and $attempt -lt $maxAttempts) {
                    Start-Sleep -Milliseconds 500
                    continue
                }
                throw
            }
        }

        $samples = [System.Collections.ArrayList]::new()

        foreach ($entry in @($data)) {
            if ($null -eq $entry) { continue }

            $responseIds = @($entry.indicators)
            $responseValues = @($entry.indicator_values)

            $metrics = [ordered]@{}
            for ($i = 0; $i -lt $metricNames.Count; $i++) {
                $metricName = $metricNames[$i]
                $meta = $indicatorMap[$metricName]
                $pos = -1
                for ($j = 0; $j -lt $responseIds.Count; $j++) {
                    if ("$($responseIds[$j])" -eq "$($meta.Id)") { $pos = $j; break }
                }
                $raw = if ($pos -ge 0) { $responseValues[$pos] } else { $null }

                if ($null -ne $raw) {
                    $rawDouble = [double]$raw
                    if ($rawDouble -eq -1) {
                        $raw = $null
                    }
                    elseif ($meta.ContainsKey('SourceUnit') -and $meta.SourceUnit -eq 'us' -and $meta.Unit -eq 'ms') {
                        $raw = $rawDouble / 1000.0
                    }
                    else {
                        $raw = $rawDouble
                    }
                }

                $metrics[$metricName] = $raw
            }

            $timestamp = [DateTimeOffset]::FromUnixTimeSeconds([long]$entry.timestamp).UtcDateTime

            $sample = New-DMPerformanceSample -ObjectType $ObjectType -ObjectId $entry.object_id -Timestamp $timestamp `
                -Metrics $metrics -RawIndicators $responseIds -RawValues $responseValues -Session $session

            [void]$samples.Add($sample)
        }

        return $samples
    }
}