iOS-Latenzperzentile auf dem Cloud-Mac verlässlich testen
Für dieselben Anfragen weist der lokale Bericht einen P95-Wert von 190 Millisekunden aus, die Pipeline liefert dagegen einen anderen Wert. Das muss nicht an der Leistung des Cloud-Macs liegen: Möglicherweise verwendet eine Seite die Nächster-Rang-Methode und die andere lineare Interpolation. Bei statistischen Abnahmetests auf physischen VMFly-Knoten sollten zunächst Algorithmus, Stichprobenumfang und die Behandlung leerer Mengen feststehen. Erst danach lässt sich beurteilen, ob eine Schnittstelle langsamer geworden ist. Dieser Beitrag stellt ein Swift-Skript ohne Abhängigkeit von Netzwerkdiensten vor, das sich in die Regressionstests des Statistikmoduls eines iOS-Projekts einbinden lässt.
Den Berechnungsvertrag für Perzentile festlegen
Hier sind ausschließlich ganzzahlige Perzentilangaben von 1 bis 100 zulässig. Die nicht negativen, endlichen Laufzeiten werden aufsteigend sortiert. Für das Perzentil q und den Stichprobenumfang n ergibt sich der nächste Rang als ceil(q × n / 100), wobei die Zählung bei 1 beginnt. Zurückgegeben wird der tatsächliche Messwert an dieser Position; zwischen benachbarten Werten wird nicht interpoliert.
Bei 20 aufsteigenden Messwerten von 10 bis 200 Millisekunden ergeben sich beispielsweise für P50, P95 und P99 die Werte 100, 190 und 200 Millisekunden. Besteht die Stichprobe nur aus einem Messwert, entsprechen alle drei Ergebnisse diesem Wert. Das folgt aus der Definition des Algorithmus und bedeutet nicht, dass die Latenz am oberen Rand der Verteilung bereits ausreichend erfasst wurde.
Eine leere Menge hat kein Perzentil. Wird dafür null eingetragen, erscheint ein Zeitfenster ohne erfolgreiche Anfragen schneller als ein normales Zeitfenster.
Enthält die Eingabe negative Werte, unendliche Werte oder NaN, schlägt die gesamte Berechnung fehl. Ungültige Werte dürfen nicht stillschweigend herausgefiltert werden, da sonst Erfassungsfehler als regulärer Bericht erscheinen. Werden solche Werte im Produktivsystem gesondert ausgesondert, muss die Zahl der verworfenen Messwerte zusätzlich erfasst werden.
Ränge ohne Gleitkommamultiplikation berechnen
Den folgenden Inhalt als percentile-check.swift speichern. Das Skript verwendet Swift aus der vollständigen Xcode-Toolchain; Änderungen am App-Projekt sind nicht erforderlich. Sämtliche Laufzeiten werden in Millisekunden angegeben. Da die Funktion die Einheit nicht aus einem bloßen Zahlenwert ableiten kann, muss der aufrufende Code die Umrechnung übernehmen.
import Foundation
enum MetricError: Error, Equatable {
case invalidPercentile
case invalidDuration
}
func percentile(_ values: [Double], q: Int) throws -> Double? {
guard (1...100).contains(q) else {
throw MetricError.invalidPercentile
}
guard values.allSatisfy({ $0.isFinite && $0 >= 0 }) else {
throw MetricError.invalidDuration
}
guard !values.isEmpty else {
return nil
}
let sorted = values.sorted()
let n = sorted.count
let rank = (n / 100) * q + ((n % 100) * q + 99) / 100
return sorted[rank - 1]
}
func expectError(_ expected: MetricError,
_ operation: () throws -> Void) {
do {
try operation()
preconditionFailure("Expected metric error")
} catch let error as MetricError {
precondition(error == expected)
} catch {
preconditionFailure("Unexpected error type")
}
}
let samples = (1...20).map { Double($0 * 10) }
let p50 = try percentile(samples, q: 50)
let p95 = try percentile(samples, q: 95)
let p99 = try percentile(samples, q: 99)
precondition(p50 == 100 && p95 == 190 && p99 == 200)
let empty = try percentile([], q: 95)
let single = try percentile([7], q: 99)
let repeated = try percentile([4, 4, 4], q: 95)
let zero = try percentile([0], q: 50)
let reversed = try percentile(Array(samples.reversed()), q: 95)
precondition(empty == nil && single == 7)
precondition(repeated == 4 && zero == 0 && reversed == p95)
for q in [0, 101] {
expectError(.invalidPercentile) {
_ = try percentile(samples, q: q)
}
}
for value in [-1.0, Double.nan, Double.infinity] {
expectError(.invalidDuration) {
_ = try percentile([value], q: 95)
}
}
Die Rangberechnung zerlegt n in volle Hunderter und einen Rest. So werden Rundungsabweichungen vermieden, die bei einer Multiplikation mit Double nahe ganzzahliger Grenzen auftreten können. Auch das möglicherweise überlaufende Produkt n × q wird nicht direkt berechnet. Da q höchstens 100 beträgt, sind die Zwischenergebnisse für den Rest begrenzt; der endgültige Rang ist nie größer als der Stichprobenumfang.
Deterministische Abnahmetests auf dem Knoten ausführen
Die folgenden Befehle im Stammverzeichnis des Code-Repositorys ausführen. Bei Erfolg erzeugt das Skript keine Ausgabe, bei einem Fehler endet der Prozess mit einem Exit-Status ungleich null. Die Pipeline kann das Ergebnis direkt anhand dieses Status bewerten und muss keine lokalisierten Protokollmeldungen auswerten.
set -eu
xcrun swiftc percentile-check.swift -o /tmp/percentile-check
/tmp/percentile-check
Das Beispiel verwendet einen festen temporären Pfad und eignet sich damit für die Demonstration eines einzelnen Jobs. Bei parallel laufenden Pipeline-Jobs gehört die ausführbare Datei in das jeweils eigene Arbeitsverzeichnis, damit sie nicht gegenseitig überschrieben wird. -Ounchecked darf nicht hinzugefügt werden: Dieser Modus entfernt die Vorbedingungsprüfungen, die hier als Freigabekriterium dienen.
Das eigenständige Skript prüft die Beispielimplementierung. Nach der Integration in das Projekt sollten dieselben Testvektoren in das Test-Target übernommen werden und dort direkt die produktive Statistikfunktion aufrufen. Eine separate, ausschließlich für Tests gepflegte Kopie des Algorithmus ist keine dauerhafte Lösung. Werden ergänzende Tests mit permutierten Eingaben und festem Zufalls-Seed hinzugefügt, müssen auch die ursprünglichen Eingaben erhalten bleiben, damit sich Fehler reproduzieren lassen.
Zeitfenster und Stichprobengrenzen absichern
Vor dem Produktiveinsatz sind mindestens vier Arten von Änderungen zu prüfen:
- Eine andere Reihenfolge der Eingabewerte darf das Ergebnis nicht verändern. Auch das Eingabearray selbst darf von der Statistikfunktion nicht verändert werden.
- Nach dem Hinzufügen mehrfach vorkommender Laufzeiten muss die Rangbildung weiterhin alle Messwerte zählen. Duplikate dürfen nicht vorab entfernt werden.
- Die Ränge bei 99, 100 und 101 Messwerten prüfen, um Verschiebungen an der Hundertergrenze auszuschließen.
- Mit steigendem Perzentil darf das Ergebnis nicht sinken. Außerdem muss es stets ein Wert aus der ursprünglichen Stichprobe sein.
Alle Perzentile müssen aus demselben unveränderlichen Snapshot berechnet werden. Wenn die Erfassung laufend Werte an ein Array anhängt und währenddessen P50 und P99 getrennt berechnet werden, können die Ergebnisse unterschiedliche Zeitfenster abbilden. Die Messwerte daher zunächst innerhalb des Synchronisationsbereichs der Erfassung kopieren und erst außerhalb dieses Bereichs sortieren. So blockiert das Sortieren nicht den Erfassungspfad.
Anfragen mit Zeitüberschreitung dürfen nicht als Laufzeiten von null in das Array eingehen. Werden ausschließlich erfolgreiche Anfragen ausgewertet, muss die Kennzahl ausdrücklich als „Latenz erfolgreicher Anfragen“ bezeichnet werden. Zusätzlich sind die Anzahl fehlgeschlagener Anfragen und die Anzahl der Zeitüberschreitungen auszugeben. Andernfalls kann eine sinkende Erfolgsquote wie eine Verbesserung der Latenz aussehen.
Regressionsberichte nachvollziehbar machen
Jeder Bericht sollte die Algorithmuskennung, die Einheit, Beginn und Ende des Zeitfensters, die Anzahl erfolgreicher Messwerte, die Anzahl der Fehler und Zeitüberschreitungen sowie die drei Perzentile enthalten. Bei festen Testdaten kann der vollständige Vektor gespeichert werden. Für echte Anfragen sollten nur die zur Fehleranalyse erforderlichen Daten aufbewahrt werden, ohne Zugangsdaten oder Anfrageinhalte.
Der Mittelwert der P95-Werte mehrerer Zeitfenster ist kein P95 für das gesamte Zeitfenster. Für eine Zusammenführung müssen die vereinten Messwerte neu ausgewertet werden. Alternativ kommt eine statistische Datenstruktur infrage, die das Zusammenführen ausdrücklich unterstützt und deren Fehlergrenzen angegeben sind. Die hier gezeigte Sortierimplementierung eignet sich für exakte Abnahmetests, nicht für unbegrenzt wachsende Datenströme.
Zum Schluss sollten „korrekte Statistikimplementierung“ und „erfüllte Leistungsanforderungen“ als zwei getrennte Freigabekriterien behandelt werden: Das erste wird mit festen Testvektoren und exakten Vergleichen geprüft. Beim zweiten werden fachliche Grenzwerte auf Grundlage ausreichend großer Stichproben und einheitlicher Zeitfenster überprüft. So lässt sich ein Fehlschlag eindeutig einer Regression im Algorithmus, einem Erfassungsfehler oder einer tatsächlichen Latenzänderung zuordnen.
Häufig gestellte Fragen
Nächster Rang oder lineare Interpolation?
Entscheidend ist der vereinbarte Messvertrag. Die Nächster-Rang-Methode liefert einen vorhandenen Stichprobenwert. Beim Wechsel von Interpolation müssen Methodenkennung und Vergleichsbasis erneuert werden.
Darf P95 ohne erfolgreiche Anfragen null sein?
Nein. Geben Sie kein Ergebnis zurück und speichern Sie die Anzahl erfolgreicher Messungen als null. Fehler und Zeitüberschreitungen werden separat gezählt; eine Dauer von null bleibt ein gültiger Messwert.
Einen physischen Mac in die Build-Pipeline integrieren
Wählen Sie zwischen Apple Silicon M4 und M4 Pro. Der gesamte Rechner steht exklusiv einem Kunden zur Verfügung – keine virtuelle Maschine. Wählen Sie das Modell passend zur Build-Last und den Abrechnungszeitraum, der zu Ihrem Team passt.
Physische Macs mieten