Instruments for Apple's Xcode est un outil pour analyser les performances d'une application iOS. Ils sont utilisés pour collecter et afficher les données nécessaires au débogage du code. L'année dernière, Apple a 
présenté des instruments personnalisés. C'est l'occasion d'élargir l'ensemble standard d'outils pour le profilage des applications. Lorsque les outils existants ne suffisent pas, vous pouvez en créer vous-même - ils collecteront, analyseront et afficheront les données selon vos besoins.
Une année s'est écoulée et il n'y a presque pas de nouveaux outils publics et d'informations sur leur création sur le réseau. Nous avons donc décidé de rectifier la situation et de partager comment nous avons créé notre propre instrument personnalisé, qui détermine la raison de la faible isolation des tests unitaires. Il est basé sur la technologie des panneaux (nous l'avons écrit 
dans l'article précédent ) et vous permet de déterminer rapidement et précisément où le test clignote.

Minimum théorique
Pour créer un nouvel outil pour Xcode, vous devez comprendre deux blocs théoriques. Pour ceux qui veulent le découvrir par eux-mêmes, nous donnerons immédiatement les liens nécessaires:
Pour le reste - ci-dessous est un bref résumé des sujets nécessaires.
Sélectionnez d'abord Fichier -> Nouveau -> Projet -> Catégorie macOS -> Package d'instruments. Le projet créé comprend un fichier avec l'extension .instrpkg, dans lequel un nouvel outil est déclaré de manière déclarative au format xml. Familiarisons-nous avec les éléments de balisage:
| Quoi | Attributs | La description 
 | 
| Schémas de données 
 | schéma d'intervalle, schéma de point, etc. 
 | Décrit la structure de données sous forme de tableau comme les schémas SQL. Les schémas sont utilisés dans d'autres éléments de balisage pour déterminer le type de données à l'entrée et à la sortie du modèle, par exemple, lors de la description d'un mappage (UI). 
 | 
| Importer des schémas de données 
 | schéma d'importation 
 | Importez des schémas prêts à l'emploi. Il vous permet d'utiliser des structures de données définies par Apple. 
 | 
| Modèle d'outil 
 | modeleur 
 | Associe l'outil à un fichier .clp, dans lequel la logique de l'outil est définie, et annonce le schéma de données attendu à l'entrée et à la sortie du modèle. 
 | 
| Description de l'outil 
 | instrument 
 | Décrit le modèle de données et détermine comment les événements seront affichés dans l'interface utilisateur. Le modèle de données est décrit à l'aide des attributs create-table, create-parameter, etc. Les diagrammes d'outils sont définis par des attributs de graphique et le tableau des pièces est défini par une liste, un récit, etc. 
 | 
Si nous voulons compléter la logique du nouvel outil, alors créez un fichier .clp avec le code CLIPS. Entités linguistiques de base:
- «Fact» est un certain événement enregistré dans le système à l'aide de la commande assert;
 
- «Règle» est un bloc if avec une syntaxe spécifique qui contient une condition sous laquelle un ensemble d'actions est effectué.
 
Les règles et la séquence qui seront activées sont déterminées par le CLIPS lui-même en fonction des faits entrants, des priorités des règles et du mécanisme de résolution des conflits.
Le langage prend en charge la création de types de données basés sur des primitives, l'utilisation de fonctions et d'opérations arithmétiques et logiques. Et aussi une programmation orientée objet (OOP) à part entière avec la définition de classes, l'envoi de messages, l'héritage multiple.
Considérez la syntaxe de base d'un langage qui vous permet de créer une logique pour des outils personnalisés.
1. Pour créer un 
fact , utilisez la construction 
assert :
 CLIPS> (assert (duck)) 
Ainsi, nous obtenons l'entrée 
duck dans la table de faits, qui peut être consultée à l'aide de la commande 
facts :
 CLIPS> (facts) 
Pour supprimer le fait, utilisez la commande 
retract : 
(retract duck)2. Pour créer une 
rule , utilisez la construction 
defrule :
 CLIPS> (defrule duck) —     duck (animal-is duck)</i> —  animal-is duck     => (assert (sound-is quack))) —     sound-is quack 
3. Pour créer et utiliser des variables, la syntaxe suivante est utilisée (avant le nom de la variable, il y a un signe obligatoire "?"):
 ?<variable-name> 
4. Vous pouvez créer de nouveaux types de données en utilisant:
 CLIPS> (deftemplate prospect (slot name (type STRING) (default ?DERIVE)) (slot assets (type SYMBOL) (default rich)) (slot age (type NUMBER) (default 80))) 
Ainsi, nous avons défini une structure avec le nom du prospect et trois attributs nom, actifs et âge du type correspondant et une valeur par défaut.
5. Les opérations arithmétiques et logiques ont une syntaxe de préfixe. Autrement dit, pour ajouter 2 et 3, vous devez utiliser la construction suivante:
 CLIPS> (+ 2 3) 
Ou pour comparer deux variables x et y:
 CLIPS> (> ?x ?y) 
Exemple pratique
Dans notre projet, nous utilisons la bibliothèque 
OCMock pour créer des objets stub. Cependant, il existe des situations où un mok vit plus longtemps que le test pour lequel il a été créé et affecte l'isolement des autres tests. En conséquence, cela conduit au «clignotement» (instabilité) des tests unitaires. Afin de suivre la durée de vie des tests et des simulations, nous allons créer notre propre outil. Ce qui suit est un algorithme d'actions.
Étape 1. Création d'un balisage pour les événements de signalisation
Pour détecter les mox problématiques, deux catégories d'événements d'intervalle sont nécessaires: l'heure de création et de destruction du moxa, l'heure de début et l'heure de fin du test. Pour obtenir ces événements, accédez à la bibliothèque 
OCMock et 
OCMock les avec 
signpost dans les méthodes 
init et 
stopMocking de la classe 
stopMocking .


Ensuite, allez au projet à l'étude, faites le balisage dans les tests unitaires, les 
tearDown setUp et 
tearDown :

Étape 2. Créez un nouvel outil à partir du modèle de package d'instruments

Tout d'abord, nous déterminons le type de données de l'entrée. Pour ce faire, 
.instrpkg importons le schéma de 
signpost dans le 
signpost . Maintenant, les événements créés par 
signpost tomberont dans l'outil:

Ensuite, nous déterminons le type de données dans la sortie. Dans cet exemple, nous afficherons des événements simultanés. Chaque événement aura une heure et une description. Pour ce faire, déclarez le schéma:

Étape 3. Nous décrivons la logique de l'outil
Nous créons un fichier séparé avec l'extension 
.clp , dans lequel nous définissons les règles en utilisant le langage CLIPS. Pour indiquer au nouvel outil dans quel fichier la logique est définie, ajoutez le bloc de 
modeler :

Dans ce bloc, à l'aide de l'attribut 
production-system , spécifiez le chemin d'accès relatif au fichier avec la logique. Dans les attributs 
output et 
required-input définissons les schémas de données pour l'entrée et la sortie, respectivement.

Étape 4. Nous décrivons les spécificités de la présentation de l'outil (UI)
Dans le fichier 
.instrpkg , 
.instrpkg reste à décrire l'outil lui-même, c'est-à-dire à afficher les résultats. Créez une table pour les données dans l'attribut 
create-table utilisant le 
schema-ref detected-mocks-narrative précédemment déclaré dans l'attribut 
schema-ref . Et définissez le type de sortie d'information - narrative (descriptive):

Étape 5. Nous écrivons le code logique
Passons au fichier 
.clp , dans lequel la logique du système expert est définie. La logique sera la suivante: si l'heure de début du test chevauche l'intervalle de vie du moka, alors nous pensons que ce mok «provient» d'un autre test - qui viole l'isolement du test unitaire actuel. Afin de créer éventuellement un événement avec des informations d'intérêt, vous devez effectuer les étapes suivantes:
1. Définissez les structures mock et unitTest avec des champs - l'heure de l'événement, l'identifiant de l'événement, le nom du test et la classe du mok.

2. Nous définissons les règles qui créeront des faits avec les types 
mock et 
unitTest fonction des événements entrants du 
signpost :

Ces règles peuvent être lues comme suit: si à l'entrée, nous obtenons un fait de type os-signpost avec le 
subsystem - 
subsystem , la 
category , le 
name et le 
event-type souhaités, puis créez un nouveau fait avec le type défini ci-dessus (unitTest ou mock) et remplissez-le de valeurs. Il est important de se souvenir ici - CLIPS est un langage sensible à la casse et les valeurs de sous-système, catégorie, nom et type d'événement doivent correspondre à ce qui a été utilisé dans le code du projet à l'étude.

Les variables des événements d'orientation sont transmises comme suit:

3. Nous définissons les règles qui libèrent les événements terminés (ils sont redondants, car ils n'affectent pas le résultat).

Étape 6. Définissez la règle qui générera les résultats.
Vous pouvez lire la règle comme ceci.
Si1) il y a unitTest et mock;
2) dans ce cas, le début du test se produit plus tard que le moka existant;
3) il existe un tableau pour stocker les résultats avec le schéma narratif simulé détecté;
alors4) créer un nouvel enregistrement;
5) remplissez avec le temps;
6) ... et une description.

Par conséquent, nous voyons l'image suivante lors de l'utilisation du nouvel outil:

Le code source de l'instrument personnalisé et un exemple de projet d'utilisation de l'instrument peuvent être consultés 
sur GitHub .
Débogage d'outils
Le débogueur est utilisé pour déboguer des outils personnalisés.

Il permet
1. Voir le code compilé basé sur la description dans instrpkg.
2. Consultez les informations détaillées sur ce qui arrive à l'outil lors de l'exécution.

3. Affichez une liste complète et une description des schémas de données système qui peuvent être utilisés comme entrée dans de nouveaux outils.

4. Exécutez des commandes arbitraires dans la console. Par exemple, affichez une liste de règles avec la commande list-defrules ou des faits avec la commande facts

Configuration sur le serveur CI
Vous pouvez exécuter des outils à partir de la ligne de commande - pour profiler l'application pendant l'exécution de tests unitaires ou d'interface utilisateur sur le serveur CI. Cela permettra, par exemple, de détecter une fuite de mémoire le plus tôt possible. Pour profiler les tests dans le pipeline, utilisez les commandes suivantes:
1. Exécution d'outils avec des attributs:
 xcrun instruments -t <template_name> -l <average_duration_ms> -w <device_udid> 
- où template_nameest le chemin d'accès au modèle avec des outils ou le nom du modèle. Vous pouvez obtenir la commandexcrun instruments -s;
- average_duration_ms- le temps d'enregistrement en millisecondes, doit être supérieur ou égal au temps d'exécution du test;
- device_udid- identifiant du simulateur. Vous pouvez obtenir la commande xcrun instruments -s. Doit correspondre à l'identifiant du simulateur sur lequel les tests seront exécutés.
2. Exécution de tests sur le même simulateur avec la commande:
 xcodebuild -workspace <path_to_workspace>-scheme <scheme_with_tests> -destination <device> test-without-building 
- où path_to_workspaceest le chemin d'accès à l'espace de travail Xcode;
- scheme_with_tests- schéma avec tests;
- device- identifiant du simulateur.
En conséquence, un rapport avec l'extension .trace sera créé dans le répertoire de travail, qui peut être ouvert par l'application Instruments ou en cliquant avec le bouton droit sur le fichier et en sélectionnant Afficher le contenu du package.
Conclusions
Nous avons examiné un exemple de mise à niveau de poteau indicateur vers un outil à part entière et expliqué comment l'appliquer automatiquement sur les «exécutions» du serveur CI et l'utiliser pour résoudre le problème des tests de «clignotement» (instable).
En plongeant dans les possibilités des instruments personnalisés, vous comprendrez mieux dans quels autres cas vous pouvez utiliser les instruments. Par exemple, ils nous aident également à comprendre les problèmes du multithreading - où et quand utiliser l'accès aux données thread-safe.
La création d'un nouvel outil était assez simple. Mais l'essentiel est qu'après avoir passé plusieurs jours à étudier la mécanique et la documentation pour le créer aujourd'hui, vous pourrez éviter plusieurs nuits blanches dans les tentatives de correction de bugs.
Les sources
L'article a été écrit avec @regno , Anton Vlasov, développeur iOS.