Pour un même ensemble de requêtes, le rapport local indique un P95 de 190 millisecondes, alors que le pipeline obtient une autre valeur ? N’attribuez pas d’emblée cet écart aux performances du Mac cloud. Un calcul peut utiliser la méthode du rang le plus proche et l’autre une interpolation linéaire. Lors de la validation des calculs statistiques sur les nœuds physiques VMFly, il faut d’abord fixer l’algorithme, le périmètre des échantillons et le traitement d’un ensemble vide, avant de chercher à savoir si une interface a ralenti. Cet article propose un script Swift autonome, sans dépendance à un service réseau, à intégrer aux tests de non-régression du module statistique d’un projet iOS.
Fixer le contrat de calcul des percentiles
Seuls les percentiles entiers de 1 à 100 sont acceptés ici. Après avoir trié par ordre croissant les durées finies et positives ou nulles, le rang le plus proche pour un percentile q et un nombre d’échantillons n est ceil(q × n / 100). Les rangs commencent à 1. Le calcul renvoie l’échantillon réellement présent à cette position, sans interpolation entre les valeurs voisines.
Par exemple, pour 20 échantillons croissants allant de 10 à 200 millisecondes, P50, P95 et P99 valent respectivement 100, 190 et 200 millisecondes. Avec un seul échantillon, les trois résultats sont égaux à sa valeur. C’est une conséquence de la définition de l’algorithme, et non la preuve que la latence de queue a été suffisamment observée.
Un ensemble vide n’a pas de percentile. Lui attribuer zéro ferait paraître une fenêtre sans aucune requête réussie plus rapide qu’une fenêtre normale.
Si une entrée est négative, infinie ou égale à NaN, le calcul échoue. Ne filtrez pas silencieusement les valeurs invalides : une erreur de collecte se retrouverait alors masquée dans un rapport apparemment normal. Si le système de production choisit de les isoler, il doit aussi comptabiliser séparément les échantillons rejetés.
Calculer le rang sans multiplication en virgule flottante
Enregistrez le contenu suivant dans percentile-check.swift. Le script utilise Swift fourni avec la chaîne d’outils Xcode complète, sans nécessiter de modification du projet de l’application. Toutes les durées sont exprimées en millisecondes ; la fonction ne peut pas déduire l’unité d’une simple valeur numérique, la conversion incombe donc au code appelant.
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)
}
}
Le calcul du rang décompose n en quotient et reste de la division par 100. Il évite ainsi les erreurs d’arrondi qu’une multiplication en Double pourrait produire près d’une limite entière, sans calculer directement n × q, qui pourrait provoquer un dépassement de capacité. Comme q ne dépasse pas 100, les résultats intermédiaires associés au reste sont bornés, et le rang final ne dépasse pas le nombre d’échantillons.
Exécuter une validation déterministe sur le nœud
Exécutez les commandes suivantes depuis la racine du dépôt de code. Le script ne produit aucune sortie si les vérifications réussissent ; en cas d’échec, le processus renvoie un code de sortie non nul. Le pipeline peut se fonder directement sur ce code, sans analyser des journaux localisés.
set -eu
xcrun swiftc percentile-check.swift -o /tmp/percentile-check
/tmp/percentile-check
L’exemple utilise un chemin temporaire fixe, adapté à une démonstration avec une seule tâche. Dans un pipeline exécutant plusieurs tâches en parallèle, placez chaque exécutable dans le répertoire de travail propre à sa tâche afin d’éviter les écrasements. N’ajoutez pas -Ounchecked : ce mode supprime les vérifications de précondition qui servent ici de critères de validation.
Le script autonome valide l’implémentation donnée en exemple. Une fois celle-ci intégrée au projet, transférez les mêmes vecteurs de test dans la cible de tests et appelez directement la fonction statistique de production, plutôt que de maintenir durablement une copie de l’algorithme réservée aux tests. Si vous ajoutez des tests de permutation avec une graine aléatoire fixe, conservez aussi les entrées d’origine pour pouvoir reproduire les échecs.
Couvrir les limites des fenêtres et des échantillons
Avant la mise en production, vérifiez au moins les quatre types de variations suivants :
- Mélanger l’ordre des entrées ne doit pas modifier le résultat ; la fonction statistique ne doit pas non plus modifier le tableau d’entrée.
- Après l’ajout de durées répétées, le classement doit toujours tenir compte du nombre d’échantillons. Il ne faut pas supprimer les doublons au préalable.
- Vérifiez le rang avec 99, 100 et 101 échantillons pour éviter un décalage au passage d’une centaine.
- Lorsque le percentile augmente, le résultat ne doit pas diminuer et doit toujours appartenir à l’ensemble des échantillons d’origine.
Tous les percentiles doivent être calculés à partir du même instantané immuable. Si le collecteur ajoute des éléments au tableau pendant que P50 et P99 sont calculés séparément, les deux résultats peuvent correspondre à des fenêtres différentes. Copiez d’abord les échantillons dans la section synchronisée du collecteur, puis effectuez le tri en dehors de cette section pour ne pas bloquer le chemin de collecte.
Une requête ayant expiré ne doit pas être ajoutée au tableau avec une durée nulle. Si seules les requêtes réussies sont prises en compte, indiquez explicitement « latence des requêtes réussies » et publiez également le nombre d’échecs et de délais d’attente dépassés. Sinon, la latence pourrait sembler s’améliorer alors que le taux de réussite diminue.
Rendre les rapports de non-régression vérifiables
Pour chaque rapport, enregistrez l’identifiant de l’algorithme, l’unité, le début et la fin de la fenêtre, le nombre d’échantillons réussis, le nombre d’échecs, le nombre de délais d’attente dépassés et les trois percentiles. Pour les jeux de test fixes, vous pouvez conserver les vecteurs complets. Pour les requêtes réelles, ne conservez que les données nécessaires au diagnostic, sans identifiants d’authentification ni corps de requête.
Ne faites pas la moyenne des P95 de plusieurs fenêtres pour la présenter comme le P95 de la fenêtre globale. Pour fusionner des fenêtres, recalculez le percentile sur les échantillons réunis, ou utilisez une structure statistique qui prend explicitement en charge la fusion et dont la marge d’erreur est indiquée. L’implémentation par tri présentée ici convient à une validation exacte, pas à un flux de données qui croît indéfiniment.
Enfin, séparez la « justesse de l’implémentation statistique » et le « respect des objectifs de performance » en deux contrôles distincts : le premier repose sur une comparaison exacte avec des vecteurs fixes ; le second compare les résultats aux seuils métier, avec suffisamment d’échantillons et des fenêtres cohérentes. Un échec pourra ainsi être attribué clairement à une régression de l’algorithme, à une anomalie de collecte ou à une variation réelle de la latence.
Questions fréquentes
Faut-il choisir le rang le plus proche ou l’interpolation ?
Respectez le contrat de mesure existant. Le rang le plus proche renvoie une valeur réellement observée. Un passage depuis l’interpolation impose de changer l’identifiant de méthode et de recréer la référence.
Peut-on enregistrer un P95 nul sans requête réussie ?
Non. Conservez un résultat absent et un nombre de mesures réussies égal à zéro. Comptez séparément les erreurs et les délais dépassés : une durée nulle reste une mesure valide.
Ajoutez un Mac physique à votre pipeline
Choisissez un Apple Silicon M4 ou M4 Pro : chaque machine est réservée à un seul client, sans virtualisation. Sélectionnez le modèle adapté à vos charges de compilation, puis la formule de facturation qui convient à votre équipe.
Louer un nœud physique