Notes d’ingénierie MiniBin

Diagnostiquer la pression mémoire d’une CI sur Mac cloud

Diagnostiquer la pression mémoire d’une CI sur Mac cloud

Une même pipeline de CI peut se terminer en 18 minutes lors d’une exécution, puis se bloquer pendant les tests à la suivante avant de fonctionner de nouveau après relance. N’en concluez pas trop vite que « la machine ralentit de manière aléatoire ». Sur un Mac cloud, compilateurs, éditeurs de liens, simulateurs et processus de test se disputent la mémoire unifiée. Le seul résultat du build ne permet pas de déterminer si le problème vient du code, d’une concurrence excessive ou d’un système entré dans un cycle prolongé de compression et de pagination.

Définir d’abord une fenêtre d’observation reproductible

Le diagnostic doit être effectué avec le même commit, le même état du cache des dépendances, la même commande de build et le même jeu de tests. Choisissez une tâche qui reproduit le problème de façon fiable, puis consignez l’heure de début, l’heure de fin, le code de sortie et l’étape où l’échec survient. Lors de la première collecte, ne videz pas les caches tout en modifiant la concurrence : vous ne pourriez plus savoir quelle variable a eu un effet.

Commencez par établir la référence pour la mémoire physique et l’espace d’échange :

sysctl -n hw.memsize
sysctl vm.swapusage
memory_pressure -Q
vm_stat

hw.memsize indique la quantité de mémoire physique en octets. vm.swapusage fournit l’utilisation actuelle de l’espace d’échange. Dans vm_stat, la taille des pages, le nombre de pages compressées et les compteurs d’entrées-sorties permettent d’observer la tendance. Ce sont les écarts entre le début et la fin de la tâche qui comptent, et non une valeur instantanée prise comme seuil.

Une faible quantité de mémoire libre ne signifie pas nécessairement que la mémoire manque. macOS utilise activement la mémoire disponible pour mettre en cache les pages de fichiers. Les véritables signaux d’alerte sont une pression qui reste élevée, un espace d’échange qui augmente continuellement et, au même moment, un allongement du build ou l’arrêt de processus.

Échantillonner en continu pendant le build

Exécutez les commandes de diagnostic séparément de la commande de build afin de conserver les données même après l’arrêt d’un sous-processus. Le script suivant enregistre toutes les 10 secondes la pression mémoire, l’espace d’échange et les 15 processus ayant le RSS le plus élevé :

#!/bin/zsh
set -eu

out="${1:-memory-samples.log}"

while true; do
  printf '
=== %s ===
' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$out"
  memory_pressure -Q >> "$out" 2>&1
  sysctl vm.swapusage >> "$out" 2>&1
  ps -axo pid,ppid,rss,etime,command | sort -nrk3 | head -n 16 >> "$out"
  sleep 10
done

Lancez d’abord zsh sample-memory.zsh dans un terminal distinct, puis démarrez la pipeline. Arrêtez la collecte une fois la tâche terminée. Le RSS est généralement exprimé en KB. Il ne représente pas l’intégralité de l’espace d’adressage virtuel du processus, mais suffit à repérer un compilateur, un simulateur ou un processus de test dont la consommation augmente durablement.

Conserver également des repères pour chaque étape

Affichez l’heure UTC avant et après la résolution des dépendances, la compilation, l’édition de liens, le démarrage des simulateurs et l’exécution des tests. Vous pourrez ainsi rattacher un changement de tendance de l’espace d’échange à une étape précise, au lieu de disposer d’un simple instantané système sans contexte.

Distinguer les sources de pression

Les situations courantes peuvent être classées de manière préliminaire à l’aide du tableau suivant :

Symptôme Cause la plus probable Étape suivante
Le RSS de plusieurs processus augmente simultanément pendant la compilation La concurrence de compilation dépasse le budget mémoire Réduire le nombre de tâches de build, puis recommencer la mesure
La pression augmente brutalement après le démarrage des simulateurs Trop de destinations de test sont exécutées en parallèle Limiter le nombre de simulateurs actifs simultanément
Le RSS d’un seul processus de test augmente continuellement Fuite dans le test ou dans le code testé Fractionner les tests et collecter des échantillons du processus
Le swap augmente continuellement et la durée devient plus variable Le système effectue fréquemment des échanges de pages Réduire la concurrence et le nombre de tâches résidentes en arrière-plan
La pression reste normale, mais la tâche est interrompue Le problème n’est pas nécessairement lié à la mémoire Vérifier le code de sortie et le journal unifié

Après un arrêt anormal, recherchez immédiatement les événements système associés :

log show --last 30m --style compact \
  | grep -Ei 'memory pressure|memorystatus|killed process' \
  > memory-events.log

L’absence de résultat dans les journaux ne prouve pas que l’état de la mémoire était normal. Il faut toujours rapprocher ces données de la tendance observée dans les échantillons et du code de sortie de la pipeline. De même, un pic isolé pour un processus ne suffit pas à conclure à une fuite : la compilation et l’édition de liens génèrent naturellement des pointes temporaires. Une hausse continue qui ne retombe pas à la fin de l’étape est bien plus suspecte.

Transformer la concurrence en budget explicite

La limite de concurrence doit être calculée à partir des pics mesurés. Réservez d’abord environ 20% de la mémoire physique au système, aux sessions distantes et aux processus de journalisation, puis affectez le reste aux compilateurs et aux simulateurs. Si la compilation et les tests ne s’exécutent pas simultanément, vous pouvez établir des budgets distincts. S’ils se chevauchent dans la pipeline, leurs besoins doivent être additionnés.

Pour un build Xcode, commencez par un essai prudent avec -jobs :

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -jobs 4 \
  build

Pour les tests sur simulateur, limitez le nombre de destinations exécutées en parallèle :

xcodebuild \
  -workspace App.xcworkspace \
  -scheme AppTests \
  -parallel-testing-enabled YES \
  -maximum-concurrent-test-simulator-destinations 2 \
  test

Ne reprenez pas directement les valeurs 4 et 2 de ces exemples. Commencez par diviser par deux la concurrence actuelle, exécutez la même tâche au moins trois fois de suite, puis augmentez progressivement la limite. L’objectif n’est pas de saturer le CPU en permanence, mais d’obtenir un débit stable sans croissance continue de l’espace d’échange.

Terminer l’optimisation avec une grille de validation

Ne modifiez qu’un seul paramètre à chaque itération et consignez les résultats suivants :

  1. Le même commit se termine-t-il correctement trois fois de suite ?
  2. Quel est le pic de l’espace d’échange, et quelle est sa variation entre le début et la fin de la tâche ?
  3. À quelle étape de la pipeline la pression commence-t-elle à augmenter ?
  4. Des processus s’arrêtent-ils encore de manière anormale ?
  5. Quels sont la durée totale, la durée médiane et le degré de dispersion des trois résultats ?
  6. Après réduction de la concurrence, le nombre de tâches utiles terminées par unité de temps s’est-il amélioré ?

Si une concurrence plus faible ralentit légèrement un build isolé, mais supprime les relances et les arrêts aléatoires, le débit global devient généralement meilleur. Une fois les paramètres validés, inscrivez les valeurs de concurrence dans la configuration de la pipeline et conservez le script de collecte comme outil de diagnostic activable à la demande, plutôt que de le laisser fonctionner en permanence à haute fréquence. Recommencez les mesures après un changement de configuration ou un élargissement de la matrice de tests. Après avoir vérifié dans la console les configurations actuellement disponibles, rétablissez également le budget à l’aide du même jeu de tâches de référence.

Questions fréquentes

La mémoire libre suffit-elle pour diagnostiquer un Mac cloud ?

Non. Il faut croiser la pression mémoire, la mémoire compressée, la progression du swap et le RSS des processus. Une faible mémoire libre reste normale si macOS récupère ses caches sans échange durable.

Comment valider une réduction de la concurrence Xcode ?

Exécutez au moins trois fois le même commit avec le même cache et les mêmes tests. Comparez le pic de swap, les arrêts, la durée et sa variance avant de conserver le nouveau réglage.

Mac physique exclusif dans le cloud

Déployez votre environnement de développement reproductible sur un nœud physique exclusif

Choisissez une configuration, une durée de location et l’un des cinq nœuds pour les builds Xcode, les tests automatisés, le développement à distance ou l’inférence de modèles.

Choisir une configuration et commander