Private/Classes/DRMMThrottleStatus/DRMMThrottleStatus.psm1

<#
    Copyright (c) 2025-2026 Robert Faddes
    SPDX-License-Identifier: MPL-2.0
#>

using module '..\DRMMObject\DRMMObject.psm1'
<#
.SYNOPSIS
    Represents a single rate-limit bucket in the DRMM throttle system, covering read, write, or per-operation buckets.
.DESCRIPTION
    The DRMMThrottleBucket class models one rate-limit bucket from the combined view of API-reported
    and locally tracked throttle state. Each bucket has a Type (Read, Write, or Operation), a Name
    that identifies it (e.g. 'Read', 'Write', 'site-create', 'device-move'), the configured Limit,
    the API-reported Count, the locally tracked LocalCount, and a computed Utilisation ratio. This class
    is used as an element in the Buckets collection of DRMMThrottleStatus, providing a uniform structure
    for all bucket types so they can be filtered, sorted, and analysed consistently.
 
    The Datto RMM API tracks reads and writes as independent quotas:
    - accountCount / accountRateLimit → read (GET) operations only
    - accountWriteCount / accountWriteRateLimit → write (PUT/POST/DELETE) operations only
#>

class DRMMThrottleBucket : DRMMObject {

    # The bucket type: Read (global read/GET requests), Write (global write operations), or Operation (per-operation write buckets).
    [string]$Type
    # The name that identifies the bucket. For Read and Write buckets, this is the bucket type name. For Operation buckets, this is the operation name (e.g., site-create, device-move).
    [string]$Name
    # The configured rate limit for this bucket, indicating the maximum number of requests allowed within the rolling window.
    [int]$Limit
    # The current request count reported by the Datto RMM API for this bucket.
    [int]$ApiCount
    # The number of requests tracked locally in the sliding-window model for this bucket.
    [int]$LocalCount
    # The computed utilisation ratio for this bucket, calculated as the higher of API-reported utilisation or local-tracked utilisation. Ratio ranges from 0.0 (empty) to 1.0 or higher (over-limit).
    [double]$Utilisation

    DRMMThrottleBucket() : base() {

    }

    <#
    .SYNOPSIS
        Generates a summary string for the throttle bucket, including type, name, utilisation, and counts.
    .DESCRIPTION
        The GetSummary method returns a formatted string that summarises the bucket's current state,
        showing the type, name, utilisation percentage, and both API and local counts against the limit.
    #>

    [string] GetSummary() {

        $UtilPct = [math]::Round($this.Utilisation * 100, 2)

        return "$($this.Type)/$($this.Name): $UtilPct% (API=$($this.ApiCount), Local=$($this.LocalCount), Limit=$($this.Limit))"

    }
}

<#
.SYNOPSIS
    Represents the combined throttle and rate-limit status for a DRMM account, merging API-reported data with local tracking state.
.DESCRIPTION
    The DRMMThrottleStatus class provides a detailed snapshot of the current API rate-limit state,
    combining fresh data from the Datto RMM rate-status endpoint with the local sliding-window
    throttle model. It includes the active throttle profile, read and write utilisation (tracked
    independently), throttle and pause flags, current computed delays for each track, calibration
    metadata, configured thresholds, the rolling window size, and a collection of DRMMThrottleBucket
    objects representing every tracked bucket (read, write, and per-operation). This class is designed
    for monitoring, diagnostics, and long-running load test analysis, providing the complete throttle
    picture before any drift adjustment is applied.
 
    The Datto RMM API tracks reads and writes as independent quotas:
    - accountCount / accountRateLimit → read (GET) operations only
    - accountWriteCount / accountWriteRateLimit → write (PUT/POST/DELETE) operations only
#>

class DRMMThrottleStatus : DRMMObject {

    # The name of the active throttle profile (e.g., DefaultProfile, ConservativeProfile).
    [string]$Profile
    # The unique identifier (UID) of the account from the API response.
    [string]$AccountUid
    # The rolling window size in seconds for the throttle model, as reported by the API.
    [int]$WindowSizeSeconds
    # The global read utilisation ratio (0.0 to 1.0+), calculated as the higher of API-reported or locally-tracked utilisation for read (GET) operations. Represents the portion of the account read rate limit currently in use.
    [double]$ReadUtilisation
    # The global write utilisation ratio (0.0 to 1.0+), calculated as the higher of API-reported or locally-tracked utilisation for write operations.
    [double]$WriteUtilisation
    # The account cut-off ratio from the API, representing the utilisation threshold at which the system enforces a hard pause to prevent exceeding the rate limit.
    [double]$AccountCutOffRatio
    # The configured utilisation threshold (e.g., 0.3 for 30%) at which soft throttling activates, introducing delays to reduce request rate.
    [double]$ThrottleUtilisationThreshold
    # The computed utilisation threshold at which hard pause activates, calculated as AccountCutOffRatio minus a safety margin (ThrottleCutOffOverhead).
    [double]$PauseThreshold
    # Boolean indicating whether soft throttling is currently active. True when either ReadUtilisation or WriteUtilisation exceeds ThrottleUtilisationThreshold.
    [bool]$Throttle
    # Boolean indicating whether hard pause is currently active. True when either ReadUtilisation or WriteUtilisation exceeds PauseThreshold, causing all API requests to be blocked.
    [bool]$Pause
    # The current computed delay in milliseconds for read (GET) requests. When throttling is active, this value increases proportionally with read utilisation to slow request rates.
    [double]$ReadDelayMs
    # The current computed delay in milliseconds for write (POST/PUT/DELETE) requests. When throttling is active, this value increases proportionally with write utilisation to slow request rates.
    [double]$WriteDelayMs
    # The configured multiplier (e.g., 500) used to calculate delay from utilisation: DelayMs = Utilisation * DelayMultiplier. Applied to both read and write tracks.
    [double]$DelayMultiplier
    # The UTC datetime of the last read-track throttle calibration, when local read state was synchronized with API-reported values.
    [Nullable[datetime]]$ReadLastCalibrationUtc
    # The UTC datetime of the last write-track throttle calibration, when local write state was synchronized with API-reported values.
    [Nullable[datetime]]$WriteLastCalibrationUtc
    # The number of local read request samples recorded at the time of the last read-track calibration, used to track read state stability.
    [int]$ReadSamplesAtLastCalibration
    # The number of local write request samples recorded at the time of the last write-track calibration, used to track write state stability.
    [int]$WriteSamplesAtLastCalibration
    # A collection of DRMMThrottleBucket objects representing all tracked rate-limit buckets: the global read bucket, the global write bucket, and all monitored per-operation write buckets (both API-reported and locally-tracked unidentified operations).
    [DRMMThrottleBucket[]]$Buckets

    DRMMThrottleStatus() : base() {

        $this.Buckets = @()

    }

    <#
    .SYNOPSIS
        Generates a summary string for the throttle status, including key utilisation metrics and flags.
    .DESCRIPTION
        The GetSummary method returns a formatted string summarising the overall throttle state,
        including read and write utilisation percentages, whether throttling or pausing is active,
        the current delays, and the number of tracked buckets.
    #>

    [string] GetSummary() {

        $ReadPct = [math]::Round($this.ReadUtilisation * 100, 2)
        $WritePct = [math]::Round($this.WriteUtilisation * 100, 2)

        return "Read=$($ReadPct)%, Write=$($WritePct)%, Throttle=$($this.Throttle), Pause=$($this.Pause), ReadDelay=$([math]::Round($this.ReadDelayMs, 2))ms, WriteDelay=$([math]::Round($this.WriteDelayMs, 2))ms, Buckets=$($this.Buckets.Count)"

    }
}
# SIG # Begin signature block
# MIIF+wYJKoZIhvcNAQcCoIIF7DCCBegCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD84v3RFeOLizNF
# 5X+TPKurWmBV0u8eAuhA/xrac8mzkqCCA04wggNKMIICMqADAgECAhB464iXHfI6
# gksEkDDTyrNsMA0GCSqGSIb3DQEBCwUAMD0xFjAUBgNVBAoMDVJvYmVydCBGYWRk
# ZXMxIzAhBgNVBAMMGkRhdHRvUk1NLkNvcmUgQ29kZSBTaWduaW5nMB4XDTI2MDMz
# MTAwMTMzMFoXDTI4MDMzMTAwMjMzMFowPTEWMBQGA1UECgwNUm9iZXJ0IEZhZGRl
# czEjMCEGA1UEAwwaRGF0dG9STU0uQ29yZSBDb2RlIFNpZ25pbmcwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQChn1EpMYQgl1RgWzQj2+wp2mvdfb3UsaBS
# nxEVGoQ0gj96tJ2MHAF7zsITdUjwaflKS1vE6wAlOg5EI1V79tJCMxzM0bFpOdR1
# L5F2HE/ovIAKNkHxFUF5qWU8vVeAsOViFQ4yhHpzLen0WLF6vhmc9eH23dLQy5fy
# tELZQEc2WbQFa4HMAitP/P9kHAu6CUx5s4woLIOyyR06jkr3l9vk0sxcbCxx7+dF
# RrsSLyPYPH+bUAB8+a0hs+6qCeteBuUfLvGzpMhpzKAsY82WZ3Rd9X38i32dYj+y
# dYx+nx+UEMDLjDJrZgnVa8as4RojqVLcEns5yb/XTjLxDc58VatdAgMBAAGjRjBE
# MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU
# H+B0vf97dYXqdUX1YMcWhFsY6fcwDQYJKoZIhvcNAQELBQADggEBAJmD4EEGNmcD
# 1JtFoRGxuLJaTHxDwBsjqcRQRE1VPZNGaiwIm8oSQdHVjQg0oIyK7SEb02cs6n6Y
# NZbwf7B7WZJ4aKYbcoLug1k1x9SoqwBmfElECeJTKXf6dkRRNmrAodpGCixR4wMH
# KXqwqP5F+5j7bdnQPiIVXuMesxc4tktz362ysph1bqKjDQSCBpwi0glEIH7bv5Ms
# Ey9Gl3fe+vYC5W06d2LYVebEfm9+7766hsOgpdDVgdtnN+e6uwIJjG/6PTG6TMDP
# y+pr5K6LyUVYJYcWWUTZRBqqwBHiLGekPbxrjEVfxUY32Pq4QfLzUH5hhUCAk4HN
# XpF9pOzFLMUxggIDMIIB/wIBATBRMD0xFjAUBgNVBAoMDVJvYmVydCBGYWRkZXMx
# IzAhBgNVBAMMGkRhdHRvUk1NLkNvcmUgQ29kZSBTaWduaW5nAhB464iXHfI6gksE
# kDDTyrNsMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC
# gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG
# CisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHU4E4bBX9IPuu6c0mY6re6LzhU+
# bBd9SptpqjF5hH6/MA0GCSqGSIb3DQEBAQUABIIBAEeKVvUvp9hhY/Y4Cw4M1hVz
# 95xiIavJDt71cSj479v+EMiIgBpE9QdY70d7Edsd4ngnNMWYC+8m+WHGajT3/3rg
# RRzHPgtqSTIipjj1TwP63Xs9q21FfmKmFlpUZXgm1SmP/f6RFyC6uDqXr619eSjL
# G5oB2w1D5/Z4kISSMMkgXaX77AYzFqLNYGdhyMEHXt9xuIP8t6CzEARsoj24hK7Q
# 9mboIjPwBVZWSVX6XpLYvMPapNRZYqcZ3W7Qdj1jT4XVCaT5c8KzoGHLd6pw6yev
# f0fhZD6b0quBztFxRZziPuxchafu9RG+cWAMEKO7trOkxx70cdOZTLn1HkDMHpc=
# SIG # End signature block