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 :
- Les processus de compilation, d’édition de liens et de test n’ont pas hérité de la politique d’arrière-plan.
- L’échec d’une tâche d’arrière-plan renvoie un code de sortie différent de zéro.
- Le nombre de pipelines exécutés simultanément par le Runner ne dépasse pas la capacité du nœud.
- Le même code, les mêmes fichiers de verrouillage des dépendances et la même version de Xcode sont utilisés avant et après les ajustements.
- La réactivité du bureau à distance n’est pas le seul critère d’évaluation ; les mesures des processus et des E/S doivent être conservées.
- Après le redémarrage du nœud, l’encapsulation des priorités reste automatiquement appliquée par le script du pipeline.
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.
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.