Private/AWS/New-SilkTCOAWSRDSMetrics.ps1

function New-SilkTCOAWSRDSMetrics {
    param(
        [Parameter()]
        [int] $days = 1,
        [Parameter()]
        [int] $offsetDays = 1,
        [Parameter(Mandatory)]
        [array] $rdslist
    )

    $StartDate = (Get-Date).ToUniversalTime().AddDays(-($days + $offsetDays))
    $EndDate = (Get-Date).ToUniversalTime().AddDays(-$offsetDays)

    $requiredModules = @('AWS.Tools.RDS', 'AWS.Tools.CloudWatch')
    foreach ($module in $requiredModules) {
        if (-not (Get-Module -ListAvailable -Name $module)) {
            throw "Required module '$module' is not installed. Install it with: Install-Module $module"
        }
        if (-not (Get-Module -Name $module)) {
            Import-Module $module -ErrorAction Stop
        }
    }

    $thelist = @()
    $periodSeconds = ($EndDate - $StartDate).TotalSeconds

    # pull one AWS/RDS metric for a db, returns the single datapoint (or $null)
    function Get-RDSMetric {
        param(
            [string]   $DBId,
            [string]   $MetricName,
            [string[]] $Stats
        )
        try {
            $dim = New-Object Amazon.CloudWatch.Model.Dimension
            $dim.Name = 'DBInstanceIdentifier'
            $dim.Value = $DBId

            $m = Get-CWMetricStatistic -Namespace 'AWS/RDS' -MetricName $MetricName `
                -Dimension $dim -StartTime $StartDate -EndTime $EndDate `
                -Period ([int]$periodSeconds) -Statistic $Stats -ErrorAction SilentlyContinue

            if ($m.Datapoints -and $m.Datapoints.Count -gt 0) {
                return $m.Datapoints[0]
            }
        } catch {
            Write-Verbose "-> metric $MetricName not available for $DBId" -Verbose
        }
        return $null
    }

    foreach ($db in $rdslist) {
        $dbId = $db.DBInstanceIdentifier
        Write-Verbose "-> Gathering info for RDS - $dbId ($($db.Engine))" -Verbose

        # region/zone off the instance placement
        $zone = $db.AvailabilityZone
        $region = if ($zone) { $zone -replace '[a-z]$', '' } else { 'N/A' }

        # resource group equivalent from tags
        $rgTag = ($db.TagList | Where-Object { $_.Key -eq 'ResourceGroup' -or $_.Key -eq 'Project' -or $_.Key -eq 'Environment' }).Value
        $resourceGroup = if ($rgTag) { $rgTag } else { 'N/A' }

        # --- capacity comes straight off the db instance object ---
        $allocatedGB = $db.AllocatedStorage
        $maxAllocGB = if ($db.MaxAllocatedStorage) { $db.MaxAllocatedStorage } else { 'N/A' }
        $storageType = $db.StorageType
        $provIops = if ($db.Iops) { $db.Iops } else { 'N/A' }
        $storageThroughput = if ($db.StorageThroughput) { $db.StorageThroughput } else { 'N/A' }

        # --- used capacity + perf comes from cloudwatch ---
        # free space: avg gives typical, min gives the high water mark of used
        $freeDp = Get-RDSMetric -DBId $dbId -MetricName 'FreeStorageSpace' -Stats @('Average', 'Minimum')
        $freeAvgGB = if ($freeDp) { [Math]::Round($freeDp.Average / 1GB, 2) } else { $null }
        $freeMinGB = if ($freeDp) { [Math]::Round($freeDp.Minimum / 1GB, 2) } else { $null }
        $usedAvgGB = if ($null -ne $freeAvgGB) { [Math]::Round($allocatedGB - $freeAvgGB, 2) } else { $null }
        $usedPeakGB = if ($null -ne $freeMinGB) { [Math]::Round($allocatedGB - $freeMinGB, 2) } else { $null }

        # perf metrics - grab avg + max so we can size to peak
        $readIops = Get-RDSMetric -DBId $dbId -MetricName 'ReadIOPS' -Stats @('Average', 'Maximum')
        $writeIops = Get-RDSMetric -DBId $dbId -MetricName 'WriteIOPS' -Stats @('Average', 'Maximum')
        $readTput = Get-RDSMetric -DBId $dbId -MetricName 'ReadThroughput' -Stats @('Average', 'Maximum')
        $writeTput = Get-RDSMetric -DBId $dbId -MetricName 'WriteThroughput' -Stats @('Average', 'Maximum')
        $readLat = Get-RDSMetric -DBId $dbId -MetricName 'ReadLatency' -Stats @('Average', 'Maximum')
        $writeLat = Get-RDSMetric -DBId $dbId -MetricName 'WriteLatency' -Stats @('Average', 'Maximum')
        $queue = Get-RDSMetric -DBId $dbId -MetricName 'DiskQueueDepth' -Stats @('Average', 'Maximum')
        $cpu = Get-RDSMetric -DBId $dbId -MetricName 'CPUUtilization' -Stats @('Average', 'Maximum')
        $mem = Get-RDSMetric -DBId $dbId -MetricName 'FreeableMemory' -Stats @('Average')

        $o = New-Object psobject
        $o | Add-Member -MemberType NoteProperty -Name "DB name" -Value $dbId
        $o | Add-Member -MemberType NoteProperty -Name "DB Zone" -Value $zone
        $o | Add-Member -MemberType NoteProperty -Name "DB Class" -Value $db.DBInstanceClass
        $o | Add-Member -MemberType NoteProperty -Name "Engine" -Value $db.Engine
        $o | Add-Member -MemberType NoteProperty -Name "EngineVersion" -Value $db.EngineVersion
        $o | Add-Member -MemberType NoteProperty -Name "MultiAZ" -Value $db.MultiAZ
        $o | Add-Member -MemberType NoteProperty -Name "StorageType" -Value $storageType
        $o | Add-Member -MemberType NoteProperty -Name "AllocatedStorageGB" -Value $allocatedGB
        $o | Add-Member -MemberType NoteProperty -Name "MaxAllocatedStorageGB" -Value $maxAllocGB
        $o | Add-Member -MemberType NoteProperty -Name "ProvisionedIOPS" -Value $provIops
        $o | Add-Member -MemberType NoteProperty -Name "StorageThroughputMBps" -Value $storageThroughput
        $o | Add-Member -MemberType NoteProperty -Name "UsedStorageGB-avg" -Value $usedAvgGB
        $o | Add-Member -MemberType NoteProperty -Name "UsedStorageGB-peak" -Value $usedPeakGB
        $o | Add-Member -MemberType NoteProperty -Name "FreeStorageGB-avg" -Value $freeAvgGB
        $o | Add-Member -MemberType NoteProperty -Name "FreeStorageGB-min" -Value $freeMinGB
        $o | Add-Member -MemberType NoteProperty -Name "ReadIOPS-avg" -Value $(if ($readIops) { [Math]::Round($readIops.Average, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ReadIOPS-max" -Value $(if ($readIops) { [Math]::Round($readIops.Maximum, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteIOPS-avg" -Value $(if ($writeIops) { [Math]::Round($writeIops.Average, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteIOPS-max" -Value $(if ($writeIops) { [Math]::Round($writeIops.Maximum, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ReadThroughputMBps-avg" -Value $(if ($readTput) { [Math]::Round($readTput.Average / 1MB, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ReadThroughputMBps-max" -Value $(if ($readTput) { [Math]::Round($readTput.Maximum / 1MB, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteThroughputMBps-avg" -Value $(if ($writeTput) { [Math]::Round($writeTput.Average / 1MB, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteThroughputMBps-max" -Value $(if ($writeTput) { [Math]::Round($writeTput.Maximum / 1MB, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ReadLatencyMs-avg" -Value $(if ($readLat) { [Math]::Round($readLat.Average * 1000, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ReadLatencyMs-max" -Value $(if ($readLat) { [Math]::Round($readLat.Maximum * 1000, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteLatencyMs-avg" -Value $(if ($writeLat) { [Math]::Round($writeLat.Average * 1000, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "WriteLatencyMs-max" -Value $(if ($writeLat) { [Math]::Round($writeLat.Maximum * 1000, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "DiskQueueDepth-avg" -Value $(if ($queue) { [Math]::Round($queue.Average, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "DiskQueueDepth-max" -Value $(if ($queue) { [Math]::Round($queue.Maximum, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "CPUUtilization-avg" -Value $(if ($cpu) { [Math]::Round($cpu.Average, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "CPUUtilization-max" -Value $(if ($cpu) { [Math]::Round($cpu.Maximum, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "FreeableMemoryGB-avg" -Value $(if ($mem) { [Math]::Round($mem.Average / 1GB, 2) } else { $null })
        $o | Add-Member -MemberType NoteProperty -Name "ResourceGroup" -Value $resourceGroup
        $o | Add-Member -MemberType NoteProperty -Name "Region" -Value $region
        $o | Add-Member -MemberType NoteProperty -Name "Days" -Value $days
        $o | Add-Member -MemberType NoteProperty -Name "DBInstanceIdentifier" -Value $dbId

        $thelist += $o
    }

    return $thelist
}