清理废弃模块并完善测试基础设施
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$InputPath,
|
||||
[string]$Scenario = "",
|
||||
[string]$ThresholdConfigPath = "",
|
||||
[string]$OutputPath = ""
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-AbsolutePath {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$BasePath
|
||||
)
|
||||
if ([System.IO.Path]::IsPathRooted($Path)) {
|
||||
return [System.IO.Path]::GetFullPath($Path)
|
||||
}
|
||||
return [System.IO.Path]::GetFullPath((Join-Path $BasePath $Path))
|
||||
}
|
||||
|
||||
function Convert-ToDouble {
|
||||
param([object]$Value)
|
||||
return [double]::Parse(
|
||||
[string]$Value,
|
||||
[System.Globalization.NumberStyles]::Float,
|
||||
[System.Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
|
||||
function Get-Percentile {
|
||||
param(
|
||||
[double[]]$Values,
|
||||
[double]$Percentile
|
||||
)
|
||||
if ($Values.Count -eq 0) { return 0.0 }
|
||||
$sorted = @($Values | Sort-Object)
|
||||
$index = [math]::Max(0, [math]::Ceiling($Percentile * $sorted.Count) - 1)
|
||||
return [double]$sorted[$index]
|
||||
}
|
||||
|
||||
function Get-PropertyValue {
|
||||
param(
|
||||
[object]$Object,
|
||||
[string]$Name
|
||||
)
|
||||
if ($null -eq $Object) { return $null }
|
||||
$property = $Object.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $null }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\..\.."))
|
||||
$resolvedInput = Resolve-AbsolutePath -Path $InputPath -BasePath $repoRoot
|
||||
if (-not (Test-Path -LiteralPath $resolvedInput -PathType Leaf)) {
|
||||
throw "Performance CSV was not found: $resolvedInput"
|
||||
}
|
||||
|
||||
$resolvedThresholds = if ([string]::IsNullOrWhiteSpace($ThresholdConfigPath)) {
|
||||
Join-Path $repoRoot "internal/qa/config/performance-thresholds.json"
|
||||
} else {
|
||||
Resolve-AbsolutePath -Path $ThresholdConfigPath -BasePath $repoRoot
|
||||
}
|
||||
$thresholdConfig = Get-Content -LiteralPath $resolvedThresholds -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($thresholdConfig.schemaVersion -ne 1) {
|
||||
throw "Unsupported performance threshold configuration version: $($thresholdConfig.schemaVersion)"
|
||||
}
|
||||
|
||||
$allRows = @(Import-Csv -LiteralPath $resolvedInput)
|
||||
$runningRows = @($allRows | Where-Object { $_.Status -eq "running" })
|
||||
if ($runningRows.Count -eq 0) {
|
||||
throw "Performance CSV contains no running samples: $resolvedInput"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Scenario)) {
|
||||
$scenarioProperty = $runningRows[0].PSObject.Properties["Scenario"]
|
||||
$Scenario = if ($null -ne $scenarioProperty -and -not [string]::IsNullOrWhiteSpace([string]$scenarioProperty.Value)) {
|
||||
[string]$scenarioProperty.Value
|
||||
} else {
|
||||
[System.IO.Path]::GetFileNameWithoutExtension($resolvedInput)
|
||||
}
|
||||
}
|
||||
|
||||
$cpuValues = [double[]]@($runningRows | ForEach-Object { Convert-ToDouble $_.CpuPercent })
|
||||
$workingSetValues = [double[]]@($runningRows | ForEach-Object { Convert-ToDouble $_.WorkingSetMB })
|
||||
$privateMemoryValues = [double[]]@($runningRows | ForEach-Object { Convert-ToDouble $_.PrivateMemoryMB })
|
||||
$handleValues = [double[]]@($runningRows | ForEach-Object { Convert-ToDouble $_.HandleCount })
|
||||
$threadValues = [double[]]@($runningRows | ForEach-Object { Convert-ToDouble $_.ThreadCount })
|
||||
|
||||
$metrics = [ordered]@{
|
||||
SampleCount = $runningRows.Count
|
||||
AverageCpuPercent = [math]::Round(($cpuValues | Measure-Object -Average).Average, 2)
|
||||
P95CpuPercent = [math]::Round((Get-Percentile -Values $cpuValues -Percentile 0.95), 2)
|
||||
PeakCpuPercent = [math]::Round(($cpuValues | Measure-Object -Maximum).Maximum, 2)
|
||||
StartWorkingSetMB = $workingSetValues[0]
|
||||
EndWorkingSetMB = $workingSetValues[-1]
|
||||
PeakWorkingSetMB = [math]::Round(($workingSetValues | Measure-Object -Maximum).Maximum, 2)
|
||||
WorkingSetGrowthMB = [math]::Round($workingSetValues[-1] - $workingSetValues[0], 2)
|
||||
StartPrivateMemoryMB = $privateMemoryValues[0]
|
||||
EndPrivateMemoryMB = $privateMemoryValues[-1]
|
||||
PeakPrivateMemoryMB = [math]::Round(($privateMemoryValues | Measure-Object -Maximum).Maximum, 2)
|
||||
PrivateMemoryGrowthMB = [math]::Round($privateMemoryValues[-1] - $privateMemoryValues[0], 2)
|
||||
HandleGrowth = [int]($handleValues[-1] - $handleValues[0])
|
||||
PeakHandleCount = [int](($handleValues | Measure-Object -Maximum).Maximum)
|
||||
ThreadGrowth = [int]($threadValues[-1] - $threadValues[0])
|
||||
PeakThreadCount = [int](($threadValues | Measure-Object -Maximum).Maximum)
|
||||
UnresponsiveSamples = @($runningRows | Where-Object { ([string]$_.Responding).ToLowerInvariant() -eq "false" }).Count
|
||||
UnexpectedExit = @($allRows | Where-Object { $_.Status -eq "exited" }).Count -gt 0
|
||||
}
|
||||
|
||||
$scenarioThresholds = Get-PropertyValue -Object $thresholdConfig.scenarios -Name $Scenario
|
||||
$effectiveThresholds = @{}
|
||||
foreach ($property in $thresholdConfig.defaults.PSObject.Properties) {
|
||||
$effectiveThresholds[$property.Name] = $property.Value
|
||||
}
|
||||
if ($null -ne $scenarioThresholds) {
|
||||
foreach ($property in $scenarioThresholds.PSObject.Properties) {
|
||||
$effectiveThresholds[$property.Name] = $property.Value
|
||||
}
|
||||
}
|
||||
|
||||
$checks = New-Object System.Collections.Generic.List[object]
|
||||
function Add-ThresholdCheck {
|
||||
param(
|
||||
[string]$Name,
|
||||
[double]$Actual,
|
||||
[string]$ThresholdName,
|
||||
[string]$Unit
|
||||
)
|
||||
$threshold = $effectiveThresholds[$ThresholdName]
|
||||
if ($null -eq $threshold) { return }
|
||||
$limit = Convert-ToDouble $threshold
|
||||
$checks.Add([pscustomobject]@{
|
||||
Name = $Name
|
||||
Passed = $Actual -le $limit
|
||||
Actual = "$Actual$Unit"
|
||||
Limit = "<= $limit$Unit"
|
||||
})
|
||||
}
|
||||
|
||||
$allowUnexpectedExit = [bool]$effectiveThresholds["allowUnexpectedExit"]
|
||||
$checks.Add([pscustomobject]@{
|
||||
Name = "Unexpected process exit"
|
||||
Passed = $allowUnexpectedExit -or -not $metrics.UnexpectedExit
|
||||
Actual = [string]$metrics.UnexpectedExit
|
||||
Limit = if ($allowUnexpectedExit) { "allowed" } else { "False" }
|
||||
})
|
||||
Add-ThresholdCheck -Name "Unresponsive samples" -Actual $metrics.UnresponsiveSamples -ThresholdName "maxUnresponsiveSamples" -Unit ""
|
||||
Add-ThresholdCheck -Name "Average CPU" -Actual $metrics.AverageCpuPercent -ThresholdName "maxAverageCpuPercent" -Unit "%"
|
||||
Add-ThresholdCheck -Name "P95 CPU" -Actual $metrics.P95CpuPercent -ThresholdName "maxP95CpuPercent" -Unit "%"
|
||||
Add-ThresholdCheck -Name "Private memory growth" -Actual $metrics.PrivateMemoryGrowthMB -ThresholdName "maxPrivateMemoryGrowthMB" -Unit " MB"
|
||||
Add-ThresholdCheck -Name "Working set growth" -Actual $metrics.WorkingSetGrowthMB -ThresholdName "maxWorkingSetGrowthMB" -Unit " MB"
|
||||
Add-ThresholdCheck -Name "Handle growth" -Actual $metrics.HandleGrowth -ThresholdName "maxHandleGrowth" -Unit ""
|
||||
Add-ThresholdCheck -Name "Thread growth" -Actual $metrics.ThreadGrowth -ThresholdName "maxThreadGrowth" -Unit ""
|
||||
|
||||
$failedChecks = @($checks | Where-Object { -not $_.Passed })
|
||||
$result = if ($failedChecks.Count -eq 0) { "PASS" } else { "FAIL" }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
|
||||
$safeScenario = $Scenario -replace '[^a-zA-Z0-9._-]', '_'
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$OutputPath = "internal/qa/out/performance/$timestamp-$safeScenario.md"
|
||||
}
|
||||
$resolvedOutput = Resolve-AbsolutePath -Path $OutputPath -BasePath $repoRoot
|
||||
$outputDirectory = Split-Path $resolvedOutput -Parent
|
||||
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||
}
|
||||
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
$lines.Add("# Performance Summary")
|
||||
$lines.Add("")
|
||||
$lines.Add("- Scenario: ``$Scenario``")
|
||||
$lines.Add("- Result: **$result**")
|
||||
$lines.Add("- Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')")
|
||||
$lines.Add("- Source CSV: ``$resolvedInput``")
|
||||
$lines.Add("")
|
||||
$lines.Add("## Metrics")
|
||||
$lines.Add("")
|
||||
$lines.Add("| Metric | Value |")
|
||||
$lines.Add("| --- | ---: |")
|
||||
foreach ($entry in $metrics.GetEnumerator()) {
|
||||
$lines.Add("| $($entry.Key) | $($entry.Value) |")
|
||||
}
|
||||
$lines.Add("")
|
||||
$lines.Add("## Threshold Checks")
|
||||
$lines.Add("")
|
||||
$lines.Add("| Check | Result | Actual | Limit |")
|
||||
$lines.Add("| --- | --- | ---: | ---: |")
|
||||
foreach ($check in $checks) {
|
||||
$status = if ($check.Passed) { "PASS" } else { "FAIL" }
|
||||
$lines.Add("| $($check.Name) | $status | $($check.Actual) | $($check.Limit) |")
|
||||
}
|
||||
$lines.Add("")
|
||||
$lines.Add("Resource thresholds left as ``null`` are reported but not used as release gates. Calibrate them after a reviewed Release baseline is available.")
|
||||
|
||||
Set-Content -LiteralPath $resolvedOutput -Value $lines -Encoding UTF8
|
||||
Write-Host "Performance report: $resolvedOutput"
|
||||
Write-Host "Result: $result"
|
||||
|
||||
if ($failedChecks.Count -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user