Notes d’ingénierie SureVM

Gérer la priorité des processus dans une CI Mac cloud

Gérer la priorité des processus dans une CI Mac cloud

Lorsqu’un même Mac cloud exécute simultanément une compilation Xcode, des tests, la compression des journaux et la vérification des artefacts, le problème le plus courant n’est pas qu’un processus particulier échappe totalement à tout contrôle. Il vient plutôt de plusieurs tâches auxiliaires, qui pourraient être terminées plus tard, mais qui disputent au build critique les ressources CPU ainsi que la bande passante du disque et de la mémoire. Cela se traduit généralement par des temps de compilation irréguliers, des délais d’attente dépassés pendant les tests ou un bureau à distance peu réactif. Pour résoudre ce type de problème, il ne faut pas commencer par réduire aveuglément la priorité de tous les processus, mais par distinguer le chemin critique des traitements en arrière-plan.

Commencer par établir une référence comparable des processus

Exécutez une première fois un build représentatif sans aucune autre tâche, puis recommencez pendant que la compression, les vérifications et l’indexation s’exécutent simultanément. Pour les deux exécutions, consignez la durée du build, l’étape ayant échoué ainsi que l’utilisation du CPU, la consommation de mémoire, l’état et la valeur nice des principaux processus.

mkdir -p "$HOME/ci-observe"

ps -axo pid,ppid,user,%cpu,%mem,state,nice,etime,command \
  | sort -k4 -nr \
  | head -n 40 \
  > "$HOME/ci-observe/processes.txt"

iostat -w 2 -c 15 > "$HOME/ci-observe/iostat.txt"
vm_stat 2 15 > "$HOME/ci-observe/vm-stat.txt"

L’échantillonnage doit couvrir l’espace de travail réellement utilisé et un volume de dépendances représentatif. En revanche, ne mélangez pas le premier téléchargement des dépendances avec les builds stabilisés dans une même référence. Si le temps de build augmente, que iostat montre une activité soutenue et que le processus de compression consomme une part notable du CPU, il est justifié d’ajuster les tâches auxiliaires. Si le principal problème vient plutôt de la pression mémoire ou d’un trop grand nombre de tests exécutés en parallèle, modifier uniquement les priorités sera généralement inefficace.

Une priorité est une indication fournie à l’ordonnanceur, pas une limite de ressources. Elle aide à déterminer quel processus doit s’exécuter en premier, mais ne remplace pas le contrôle de la concurrence et ne garantit pas qu’une tâche recevra une proportion fixe du CPU.

Classer les tâches selon leur chemin critique

Les processus de CI peuvent être répartis en trois catégories. La première regroupe la compilation, l’édition de liens, les tests et l’exportation des artefacts. Ces opérations déterminent si le pipeline peut aboutir et doivent conserver une priorité normale. La deuxième comprend la compression des journaux, la génération des sommes de contrôle et le nettoyage des anciens artefacts. Ces tâches doivent être menées à terme, mais il n’est généralement pas nécessaire qu’elles passent avant le build. La troisième catégorie réunit l’indexation de l’éditeur, les analyses interactives et les diagnostics temporaires, qui ne doivent être lancés qu’en cas de besoin.

Ne pas appliquer une règle globale selon le nom de l’outil

Un même outil peut intervenir sur des chemins différents. Par exemple, tar est une tâche critique lorsqu’il décompresse avant le build une dépendance indispensable. En revanche, il s’agit d’une tâche d’arrière-plan lorsqu’il compresse les journaux après le build. Pour trancher, demandez-vous si l’opération bloque l’étape suivante, si son échec doit impérativement interrompre le pipeline et si sa sortie est immédiatement utilisée par la tâche en cours.

Il est préférable d’inscrire cette classification dans la configuration du pipeline plutôt que de compter sur des commandes exécutées ponctuellement par un opérateur. Lors de la revue des scripts, les commandes dont la priorité est réduite sont alors directement visibles. Cela évite également que les nouvelles tâches héritent par défaut d’une stratégie inadaptée.

Encapsuler les tâches d’arrière-plan avec nice et taskpolicy

nice ajuste la valeur de priorité d’ordonnancement d’un processus. Une valeur positive indique que le processus est davantage disposé à céder son tour d’exécution. Un utilisateur standard peut réduire la priorité des processus qu’il lance, mais ne peut pas les faire passer arbitrairement à une priorité supérieure. Sous macOS, taskpolicy -b permet de placer une commande sous une politique d’arrière-plan. Comme son effet ne se limite pas au CPU, cette option ne doit être utilisée que pour les tâches dont il a été confirmé qu’elles ne bloquent pas le chemin critique.

Le script suivant exécute la génération des sommes de contrôle en arrière-plan avec une priorité réduite, tout en conservant la politique normale pour le build Xcode. Il enregistre séparément les codes de sortie des deux opérations :

#!/bin/zsh

set -u
mkdir -p Artifacts Logs

taskpolicy -b nice -n 10 /bin/sh -c \
  'find Artifacts -type f -print0 | xargs -0 shasum -a 256 > Logs/checksums.txt' &
audit_pid=$!

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  build > Logs/xcodebuild.log 2>&1
build_rc=$?

wait "$audit_pid"
audit_rc=$?

if (( build_rc != 0 )); then
  exit "$build_rc"
fi

exit "$audit_rc"

N’appliquez pas uniformément taskpolicy -b à l’ensemble du Runner CI. Cela réduirait aussi la priorité du compilateur, des processus de test et des sous-processus. Le pipeline complet ralentirait alors sans qu’il soit possible d’identifier clairement la tâche responsable. L’encapsulation doit être placée aussi près que possible de chaque commande d’arrière-plan.

Utiliser correctement la QoS dans le code de l’application

Si les traitements auxiliaires sont effectués par des outils Swift développés en interne, définissez la QoS des files d’attente directement dans le code, au lieu d’essayer de déduire le rôle des processus après leur lancement. Les opérations déclenchées par l’utilisateur et dont celui-ci attend le résultat peuvent utiliser .userInitiated. Les traitements non immédiats, tels que l’organisation des journaux ou l’inventaire des caches, peuvent utiliser .utility. .background convient uniquement aux tâches que l’utilisateur n’attend pas et dont l’achèvement différé n’a aucune incidence sur le résultat en cours.

let queue = DispatchQueue(
    label: "com.surevm.ci.artifact-audit",
    qos: .utility
)

queue.async {
    auditArtifacts()
}

Ne définissez pas toutes les files d’attente sur .userInteractive dans l’espoir de les rendre plus rapides. Une QoS trop élevée met les tâches auxiliaires en concurrence avec les traitements critiques pour les ressources d’ordonnancement. Elle peut également entraîner une inversion de priorité lorsqu’une tâche hautement prioritaire attend un verrou détenu par une file d’attente de priorité inférieure. Les verrous partagés, les files d’attente série et les appels synchrones doivent donc également être contrôlés.

Valider les résultats et définir les conditions de retour en arrière

Après les ajustements, répétez au moins trois fois le build avec les mêmes entrées, puis comparez la durée médiane, les points de défaillance et les mesures de ressources. Une seule exécution plus rapide ne suffit pas à démontrer l’efficacité de la stratégie. Vérifiez également que les tâches d’arrière-plan, comme la compression des journaux et la génération des sommes de contrôle, sont arrivées à leur terme. La réussite du build critique ne doit pas masquer l’échec d’une tâche auxiliaire.

Pour la validation, contrôlez successivement les points suivants :

Si les tâches auxiliaires dépriorisées s’accumulent jusqu’au build suivant, le problème n’est plus une concurrence pour les ressources, mais un débit insuffisant. Il faut alors réduire le nombre de tâches d’arrière-plan, limiter le périmètre traité ou les déplacer vers une étape distincte exécutée après le build, plutôt que de continuer à augmenter la valeur nice. Les configurations précisément disponibles pour les nœuds SureVM doivent être vérifiées dans la console. Quelle que soit la configuration utilisée, identifier d’abord le chemin critique avant de modifier la politique d’ordonnancement est généralement plus facile à valider et à annuler qu’un ajustement global.

Questions fréquentes

nice ou taskpolicy peuvent-ils imposer un plafond fixe de CPU ?

Non. Ils modifient les préférences d’ordonnancement et la politique d’arrière-plan, sans créer de quota strict. Il faut aussi limiter les builds parallèles, les fragments de test et les tâches auxiliaires.

Quelles tâches CI peuvent passer en politique d’arrière-plan ?

La compression des journaux, les sommes de contrôle, le nettoyage d’anciens artefacts et l’indexation non bloquante conviennent généralement. La compilation, l’édition de liens et les tests restent prioritaires.

SureVM Mac cloud

Choisissez des nœuds Mac physiques dédiés pour compiler, développer et expérimenter

Comparez deux configurations de Mac mini M4, quatre durées de location et cinq nœuds disponibles, puis choisissez selon votre flux de travail.

Choisir une offre de location