201 lines
7.9 KiB
PowerShell
Executable file
201 lines
7.9 KiB
PowerShell
Executable file
<#
|
|
.SYNOPSIS
|
|
Wertet ein PSProfiler-Ergebnis (Measure-Script, Typ ScriptLineMeasurement) aus.
|
|
|
|
.DESCRIPTION
|
|
Angepasst an die tatsaechliche PSProfiler-Ausgabe:
|
|
LineNo [int] - Zeilennummer
|
|
ExecutionTime [TimeSpan] - Gesamtzeit dieser Zeile (Summe aller Treffer)
|
|
TimeLine.TimeSpans [List] - Einzelmessungen; deren Anzahl = HitCount
|
|
Line [string] - Quellcode-Text der Zeile
|
|
SourceScript [string] - Pfad der Quelldatei
|
|
Top [bool]
|
|
|
|
WICHTIG: Measure-Script instrumentiert NUR die uebergebene Datei. Zur Laufzeit
|
|
dot-gesourcte Dateien (Utility/Base/Work/R125) erscheinen NICHT mit eigenen
|
|
Zeilen - ihre Zeit wird der jeweiligen Aufrufzeile im Rahmenskript zugeschlagen.
|
|
Fuer Hotspots INNERHALB von R125 muss R125 separat gemessen werden
|
|
(siehe Measure-R125Direct.ps1).
|
|
|
|
.PARAMETER XmlPath
|
|
Pfad zur Ergebnis-XML (Export-Clixml).
|
|
|
|
.PARAMETER Result
|
|
Alternativ das Measure-Script-Ergebnisobjekt direkt.
|
|
|
|
.PARAMETER Top
|
|
Anzahl Top-Zeilen. Default 25.
|
|
|
|
.PARAMETER FocusFile
|
|
Optionales Dateinamen-Muster, auf das das Detail eingeschraenkt wird
|
|
(z.B. '*Work-R125.ps1', wenn R125 direkt gemessen wurde).
|
|
|
|
.EXAMPLE
|
|
./Analyze-ProfileResult.ps1 -XmlPath ./profile-R125-20260624-195753-run1.xml
|
|
#>
|
|
[CmdletBinding(DefaultParameterSetName = 'FromXml')]
|
|
param(
|
|
[Parameter(ParameterSetName = 'FromXml', Mandatory = $true, Position = 0)]
|
|
[string]$XmlPath,
|
|
|
|
[Parameter(ParameterSetName = 'FromObject', Mandatory = $true)]
|
|
[object]$Result,
|
|
|
|
[int]$Top = 25,
|
|
|
|
[string]$FocusFile = $null,
|
|
|
|
# Wird von den gemessenen Zeilennummern abgezogen, um auf die Original-
|
|
# Zeilen der Zieldatei zurueckzurechnen (z.B. wenn ein Mess-Header vorangestellt
|
|
# wurde). Wird automatisch aus der .meta.xml-Sidecar gelesen, falls vorhanden.
|
|
[int]$LineOffset = 0
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if ($PSCmdlet.ParameterSetName -eq 'FromXml') {
|
|
if (-not (Test-Path $XmlPath)) { throw "XML nicht gefunden: $XmlPath" }
|
|
$Result = Import-Clixml -Path $XmlPath
|
|
|
|
# Offset automatisch aus Sidecar lesen, falls nicht manuell gesetzt.
|
|
if ($LineOffset -eq 0) {
|
|
$metaPath = $XmlPath -replace '\.xml$', '.meta.xml'
|
|
if (Test-Path $metaPath) {
|
|
try {
|
|
$meta = Import-Clixml -Path $metaPath
|
|
if ($meta.HeaderOffset) {
|
|
$LineOffset = [int]$meta.HeaderOffset
|
|
Write-Host ("Sidecar gefunden: Zeilen-Offset {0} (Quelle: {1})" -f $LineOffset, $meta.SourceFile) -ForegroundColor DarkGray
|
|
}
|
|
} catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
# Flache Liste der ScriptLineMeasurement-Objekte
|
|
$rows = @($Result | Where-Object { $_.PSObject.Properties['LineNo'] })
|
|
if ($rows.Count -eq 0) {
|
|
throw "Keine ScriptLineMeasurement-Objekte gefunden (Feld 'LineNo' fehlt)."
|
|
}
|
|
|
|
# --- Normalisierung --------------------------------------------------------
|
|
# ExecutionTime ist ein TimeSpan -> ms. HitCount = Anzahl Eintraege in
|
|
# TimeLine.TimeSpans (Fallback: 1, wenn ExecutionTime > 0).
|
|
function Get-HitCount {
|
|
param($row)
|
|
$tl = $row.PSObject.Properties['TimeLine']
|
|
if ($tl -and $tl.Value) {
|
|
$ts = $tl.Value.PSObject.Properties['TimeSpans']
|
|
if ($ts -and $ts.Value) {
|
|
try { return @($ts.Value).Count } catch { }
|
|
}
|
|
}
|
|
if ($row.ExecutionTime -and $row.ExecutionTime.TotalMilliseconds -gt 0) { return 1 }
|
|
return 0
|
|
}
|
|
|
|
$norm = foreach ($row in $rows) {
|
|
$ms = 0.0
|
|
if ($row.ExecutionTime -is [TimeSpan]) { $ms = $row.ExecutionTime.TotalMilliseconds }
|
|
$rawLine = [int]$row.LineNo
|
|
$origLine = $rawLine - $LineOffset
|
|
[pscustomobject]@{
|
|
File = [string]$row.SourceScript
|
|
Leaf = if ($row.SourceScript) { Split-Path $row.SourceScript -Leaf } else { '<unknown>' }
|
|
LineNo = $origLine # auf Original-R125 zurueckgerechnet
|
|
RawLine = $rawLine # Zeile in der gemessenen (temporaeren) Datei
|
|
IsHeader = ($origLine -le 0)
|
|
Ms = [double]$ms
|
|
Hits = [int](Get-HitCount $row)
|
|
Code = ([string]$row.Line).Trim()
|
|
Top = [bool]$row.Top
|
|
}
|
|
}
|
|
|
|
# Header-Zeilen (Mess-Geruest) aus der Hotspot-Betrachtung nehmen, sofern ein
|
|
# Offset aktiv ist - sie gehoeren nicht zum Funktionskoerper.
|
|
if ($LineOffset -gt 0) {
|
|
$norm = $norm | Where-Object { -not $_.IsHeader }
|
|
}
|
|
|
|
if ($FocusFile) {
|
|
$norm = $norm | Where-Object { $_.Leaf -like $FocusFile -or $_.File -like $FocusFile }
|
|
}
|
|
|
|
$totalMs = ($norm | Measure-Object Ms -Sum).Sum
|
|
|
|
function Fmt-Dur {
|
|
param([double]$ms)
|
|
if ($ms -ge 60000) { return ('{0:n1} min' -f ($ms / 60000)) }
|
|
if ($ms -ge 1000) { return ('{0:n1} s' -f ($ms / 1000)) }
|
|
return ('{0:n1} ms' -f $ms)
|
|
}
|
|
|
|
# --- Uebersicht ------------------------------------------------------------
|
|
Write-Host ""
|
|
Write-Host "================ UEBERSICHT ================" -ForegroundColor Cyan
|
|
Write-Host ("Gemessene Zeilen : {0}" -f $norm.Count)
|
|
Write-Host ("Gesamtzeit : {0} ({1:n0} ms)" -f (Fmt-Dur $totalMs), $totalMs)
|
|
$files = @($norm | Select-Object -ExpandProperty Leaf -Unique)
|
|
Write-Host ("Quelldateien : {0}" -f ($files -join ', '))
|
|
if ($files.Count -eq 1) {
|
|
Write-Host ""
|
|
Write-Host "HINWEIS: Nur eine Quelldatei instrumentiert. Dot-gesourcte Dateien" -ForegroundColor Yellow
|
|
Write-Host " (R125 etc.) sind hier NICHT zeilenweise enthalten - ihre Zeit" -ForegroundColor Yellow
|
|
Write-Host " steckt in den Aufrufzeilen. Fuer R125-interne Hotspots R125" -ForegroundColor Yellow
|
|
Write-Host " direkt messen (Measure-R125Direct.ps1)." -ForegroundColor Yellow
|
|
}
|
|
|
|
# --- Ranking pro Datei (nur sinnvoll bei mehreren Dateien) -----------------
|
|
if ($files.Count -gt 1) {
|
|
Write-Host ""
|
|
Write-Host "================ RANKING PRO DATEI ================" -ForegroundColor Cyan
|
|
$norm | Group-Object Leaf | ForEach-Object {
|
|
$sum = ($_.Group | Measure-Object Ms -Sum).Sum
|
|
[pscustomobject]@{
|
|
Datei = $_.Name
|
|
Zeit = Fmt-Dur $sum
|
|
Ms = [math]::Round($sum, 1)
|
|
Anteil = ('{0:n1}%' -f ($(if ($totalMs) { 100 * $sum / $totalMs } else { 0 })))
|
|
Zeilen = $_.Count
|
|
}
|
|
} | Sort-Object Ms -Descending | Format-Table Datei, Zeit, Anteil, Zeilen -AutoSize | Out-Host
|
|
}
|
|
|
|
# --- Top-Hotspots nach Zeit ------------------------------------------------
|
|
Write-Host ""
|
|
Write-Host "================ TOP $Top NACH ZEIT ================" -ForegroundColor Cyan
|
|
$norm | Sort-Object Ms -Descending | Select-Object -First $Top |
|
|
ForEach-Object {
|
|
[pscustomobject]@{
|
|
Datei = $_.Leaf
|
|
Zeile = $_.LineNo
|
|
Zeit = Fmt-Dur $_.Ms
|
|
Anteil = ('{0:n1}%' -f ($(if ($totalMs) { 100 * $_.Ms / $totalMs } else { 0 })))
|
|
Hits = $_.Hits
|
|
Code = if ($_.Code.Length -gt 75) { $_.Code.Substring(0, 75) + '...' } else { $_.Code }
|
|
}
|
|
} | Format-Table -AutoSize | Out-Host
|
|
|
|
# --- Top-Hotspots nach HitCount (Wiederholungs-Hotspots) -------------------
|
|
$haveHits = ($norm | Where-Object { $_.Hits -gt 1 } | Measure-Object).Count -gt 0
|
|
if ($haveHits) {
|
|
Write-Host ""
|
|
Write-Host "================ TOP $Top NACH HITCOUNT ================" -ForegroundColor Cyan
|
|
Write-Host "(relevant fuer die grosse if/elseif-StartsWith-Kette in R125)" -ForegroundColor DarkGray
|
|
$norm | Sort-Object Hits -Descending | Select-Object -First $Top |
|
|
ForEach-Object {
|
|
[pscustomobject]@{
|
|
Datei = $_.Leaf
|
|
Zeile = $_.LineNo
|
|
Hits = $_.Hits
|
|
Zeit = Fmt-Dur $_.Ms
|
|
Code = if ($_.Code.Length -gt 75) { $_.Code.Substring(0, 75) + '...' } else { $_.Code }
|
|
}
|
|
} | Format-Table -AutoSize | Out-Host
|
|
}
|
|
else {
|
|
Write-Host ""
|
|
Write-Host "HINWEIS: Alle Zeilen haben HitCount<=1 (TimeSpans nicht pro Treffer" -ForegroundColor DarkGray
|
|
Write-Host " erfasst). HitCount-Ranking daher uebersprungen." -ForegroundColor DarkGray
|
|
}
|