Zum Hauptinhalt springen
Diese Anleitung analysiert Performance-Metriken eines Backend-Dienstes, die über 30 Tage gesammelt wurden. Vier Metriken werden erfasst: Antwortzeit (ms), Fehler pro Stunde, Speicherverbrauch (MB) und Durchsatz (Requests/Sek.).

Datensatz

val responseTimeMs = doubleArrayOf(
    89.2, 95.1, 87.6, 102.3, 91.8, 88.4, 96.7, 103.5, 90.1, 94.3,
    88.9, 97.2, 105.8, 91.4, 93.6, 87.1, 99.0, 92.5, 96.1, 104.2,
    90.7, 88.3, 101.6, 93.9, 95.4, 89.8, 98.3, 106.1, 91.0, 94.7
)

val errorsPerHour = doubleArrayOf(
    2.0, 3.0, 1.0, 5.0, 2.0, 1.0, 4.0, 6.0, 2.0, 3.0,
    1.0, 4.0, 7.0, 2.0, 3.0, 1.0, 5.0, 2.0, 4.0, 6.0,
    2.0, 1.0, 5.0, 3.0, 3.0, 1.0, 4.0, 8.0, 2.0, 3.0
)

val memoryUsageMb = doubleArrayOf(
    512.3, 528.1, 505.7, 545.2, 519.6, 508.4, 534.8, 551.3, 515.0, 526.7,
    509.2, 537.1, 558.4, 517.8, 524.3, 503.1, 541.6, 520.9, 531.5, 549.7,
    514.2, 506.8, 543.9, 522.5, 529.0, 511.4, 539.3, 561.2, 516.3, 527.4
)

val throughputRps = doubleArrayOf(
    245.0, 238.0, 251.0, 225.0, 242.0, 249.0, 232.0, 218.0, 244.0, 236.0,
    250.0, 230.0, 212.0, 243.0, 237.0, 253.0, 227.0, 241.0, 233.0, 220.0,
    246.0, 252.0, 224.0, 239.0, 235.0, 248.0, 228.0, 210.0, 243.0, 234.0
)

Schritt 1: Zusammenfassende Statistiken

val rtSummary = responseTimeMs.describe()
val errSummary = errorsPerHour.describe()
val memSummary = memoryUsageMb.describe()
val tpSummary = throughputRps.describe()

rtSummary.mean; rtSummary.standardDeviation; rtSummary.min; rtSummary.max
errSummary.mean; errSummary.standardDeviation; errSummary.min; errSummary.max
memSummary.mean; memSummary.standardDeviation; memSummary.min; memSummary.max
tpSummary.mean; tpSummary.standardDeviation; tpSummary.min; tpSummary.max

Häufigkeitsverteilung

val rtBins = responseTimeMs.frequencyTable(binCount = 5)
rtBins.forEach { bin ->
    // bin.range, bin.count, bin.relativeFrequency
}

val errBins = errorsPerHour.frequencyTable(binSize = 2.0)
errBins.forEach { bin ->
    // bin.range, bin.count, bin.cumulativeFrequency
}

Schritt 2: Verteilungsform

responseTimeMs.skewness() // positive = right-skewed
responseTimeMs.kurtosis() // positive excess = heavier tails than Normal

errorsPerHour.skewness()
errorsPerHour.kurtosis()

memoryUsageMb.skewness()
throughputRps.skewness()

Normalitätstests

shapiroWilkTest(responseTimeMs).pValue
shapiroWilkTest(errorsPerHour).pValue
shapiroWilkTest(memoryUsageMb).pValue
shapiroWilkTest(throughputRps).pValue

Anpassung einer Kandidatenverteilung

val rtFit = NormalDistribution(
    mu = responseTimeMs.mean(),
    sigma = responseTimeMs.standardDeviation()
)
kolmogorovSmirnovTest(responseTimeMs, rtFit).pValue

val memFit = NormalDistribution(
    mu = memoryUsageMb.mean(),
    sigma = memoryUsageMb.standardDeviation()
)
kolmogorovSmirnovTest(memoryUsageMb, memFit).pValue

Schritt 3: Korrelationen

// Correlation matrix across all four metrics
val matrix = correlationMatrix(responseTimeMs, errorsPerHour, memoryUsageMb, throughputRps)
// matrix[i][j] gives Pearson r between metrics i and j

// Deeper look at specific relationships
val rtVsErrors = pearsonCorrelation(responseTimeMs, errorsPerHour)
rtVsErrors.coefficient // positive = errors rise with latency
rtVsErrors.pValue

val rtVsThroughput = spearmanCorrelation(responseTimeMs, throughputRps)
rtVsThroughput.coefficient // negative = latency rises when throughput drops
rtVsThroughput.pValue

Regression

// Model: how does error count relate to response time?
val regression = simpleLinearRegression(errorsPerHour, responseTimeMs)

regression.slope     // ms increase per additional error/hour
regression.intercept // baseline response time at zero errors
regression.rSquared  // proportion of variance explained

regression.predict(4.0) // expected latency at 4 errors/hour

Schritt 4: Zeiträume vergleichen

Die Daten in zwei Hälften aufteilen und prüfen, ob sich die Performance verändert hat.
val firstHalfRt = responseTimeMs.sliceArray(0 until 15)
val secondHalfRt = responseTimeMs.sliceArray(15 until 30)

val periodComparison = tTest(firstHalfRt, secondHalfRt)
periodComparison.pValue
periodComparison.isSignificant()

// Non-parametric alternative
val periodRank = mannWhitneyUTest(firstHalfRt, secondHalfRt)
periodRank.pValue
Durchsatz zwischen Zeiträumen vergleichen:
val firstHalfTp = throughputRps.sliceArray(0 until 15)
val secondHalfTp = throughputRps.sliceArray(15 until 30)

val tpComparison = tTest(firstHalfTp, secondHalfTp)
tpComparison.pValue

Schritt 5: Normalisieren und Rangordnung

Metriken auf eine gemeinsame Skala bringen.
// Z-score: values become standard deviations from mean
val rtNormalized = responseTimeMs.zScore()
val memNormalized = memoryUsageMb.zScore()
// Both are now on the same scale and can be compared directly

// Min-max scaling to [0, 1]
val rtScaled = responseTimeMs.minMaxNormalize()
val tpScaled = throughputRps.minMaxNormalize()

// Rank the days by response time (worst days get highest rank)
val rtRanked = responseTimeMs.rank()
Z-Score-Normalisierung eignet sich gut, um Metriken zu einem Gesamtwert zu kombinieren: Ein Tag mit hohen Z-Scores bei Antwortzeit, Fehlern und Speicherverbrauch sollte näher untersucht werden.
Last modified on March 22, 2026