Manuel de suivi dynamique Solaris

Unions

Les unions représentent une autre catégorie de type de composite que les langages ANSI-C et D prennent en charge. Elles sont également étroitement liées aux structs. Une union est un type de composite dans lequel un ensemble de membres de différents types sont définis et dans lequel les objets membres occupent tous la même zone de stockage. Une union est, par conséquent, un objet de type variante dans lequel seul un membre est valide à un moment donné en fonction de la manière dont l'union a été affectée. En règle générale, certaines autres variables ou éléments d'état servent à indiquer le membre de l'union actuellement valide. La taille d'une union correspond à la taille du membre le plus grand et l'alignement de la mémoire utilisé pour l'union correspond à l'alignement maximum requis par les membres de l'union.

La structure kstat de Solaris définit une struct contenant une union utilisée dans l'exemple suivant pour illustrer et observer les unions en C et D. La structure kstat sert à exporter un ensemble de compteurs nommés représentant les statistiques du noyau comme l'utilisation de la mémoire et le débit d'E/S. La structure est utilisée pour implémenter des utilitaires comme mpstat(1M) et iostat(1M). Cette structure utilise struct kstat_named pour représenter un compteur nommé et sa valeur. Elle est définie comme suit :

struct kstat_named {
	char name[KSTAT_STRLEN]; /* name of counter */
	uchar_t data_type;	/* data type */
	union {
		char c[16];
		int32_t i32;
		uint32_t ui32;
		long l;
		ulong_t ul;
		...
	} value;	/* value of counter */
};	

La déclaration examinée est réduite à titre illustratif. La définition complète de la structure figure dans le fichier d'en-tête <sys/kstat.h> et est décrite dans kstat_named(9S). La déclaration ci-dessus est valide en langage ANSI-C et D et définit une struct dont l'un des membres est une valeur d'union dont les membres appartiennent à différents types en fonction du type de compteur. Étant donné que l'union elle-même est déclarée à l'intérieur d'un autre type, struct kstat_named, notez que le nom formel du type d'union est omis. Ce style de déclaration est connu comme étant une union anonyme. Le membre nommé value est un type d'union décrit par la déclaration précédente mais ce type d'union lui-même ne possède pas de nom car il n'a pas vocation à être utilisé autrement. Le membre data_type de la struct est affecté à une valeur qui indique le membre de l'union valide pour chaque objet du type struct kstat_named. Un ensemble de jetons de préprocesseur en C est défini pour les valeurs de data_type. Par exemple, le jeton KSTAT_DATA_CHAR est égal à 0 et indique que le membre value.c se trouve à l'endroit où est actuellement enregistrée la valeur.

L'Exemple 7–3 montre comment accéder à l'union kstat_named.value en assurant le suivi d'un processus utilisateur. Il est possible d'échantillonner les compteurs kstat à partir d'un processus utilisateur à l'aide de la fonction kstat_data_lookup(3KSTAT) qui renvoie un pointeur à une struct kstat_named. L'utilitaire mpstat(1M) appelle cette fonction à plusieurs reprises pendant son exécution afin d'échantillonner les dernières valeurs du compteur. Accédez à votre shell, essayez d'exécuter mpstat 1 et observez la sortie. Appuyez sur Control-C dans votre shell pour abandonner mpstat après quelques secondes. Pour observer l'échantillonnage du compteur, nous aimerions activer une sonde qui se déclenche à chaque fois que la commande mpstat appelle la fonction kstat_data_lookup(3KSTAT) dans libkstat. Pour ce faire, nous allons utiliser un nouveau fournisseur de DTrace : pid. Le fournisseur pid vous permet de créer des sondes de manière dynamique au sein de processus utilisateur au niveau des emplacements de symboles C comme les points d'entrée de fonction. Vous pouvez demander au fournisseur pid de créer une sonde au niveau de l'entrée d'une fonction utilisateur et de retourner les sites en décrivant les sondes comme suit :

pidID-processus:nom-objet:nom-fonction:entry
pidID-processus:nom-objet:nom-fonction:return

Par exemple, pour créer une sonde dans l'ID de processus 12345 qui se déclenche lors de l'entrée dans kstat_data_lookup(3KSTAT), vous écririez la description de sonde suivante :

pid12345:libkstat:kstat_data_lookup:entry

Le fournisseur pid insère l'instrumentation dynamique dans le processus utilisateur spécifié à l'emplacement du programme correspondant à la description de la sonde. L'implémentation de la sonde contraint chaque thread utilisateur atteignant l'emplacement du programme instrumenté à s'interrompre dans le noyau du système d'exploitation et à lancer DTrace, déclenchant ainsi la sonde correspondante. Par conséquent, bien que l'emplacement d'instrumentation soit associé à un processus utilisateur, les prédicats et les actions de DTrace que vous spécifiez continuent de s'exécuter dans le contexte du noyau du système d'exploitation. Le fournisseur pid est décrit plus en détails dans le Chapitre30Fournisseur pid.

Plutôt que de devoir éditer la source de votre programme en D chaque fois que vous souhaitez appliquer votre programme à un processus différent, vous pouvez insérer des identifiants appelés variables de macro dans votre programme. Ces identificateurs sont évalués à chaque compilation et remplacement de votre programme par des arguments de ligne de commande dtrace supplémentaires. Les variables de macro sont spécifiées à l'aide du signe dollar $ suivi d'un identifiant ou d'un chiffre. Si vous exécutez la commande dtrace -s script foo bar baz, le compilateur D définit automatiquement les variables de macro $1, $2, et $3 sur les jetons foo, bar et baz respectivement. Vous pouvez utiliser des variables de macro dans des expressions de programme en D ou des descriptions de sonde. Par exemple, les descriptions de sonde suivantes sont instrumentées quel que soit l'ID de processus spécifié comme argument supplémentaire vers dtrace:

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n", copyinstr(self->ksname),
	    this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Les variables de macro et les scripts réutilisables sont décrits plus en détails dans le Chapitre15Scripts. Maintenant que nous savons comment instrumenter des processus utilisateur à l'aide de leur ID de processus, revenons aux unions d'échantillonnage. Ouvrez votre éditeur, tapez le code source de votre exemple complet et enregistrez-le dans un fichier nommé kstat.d:


Exemple 7–3 kstat.d : appels de suivi vers kstat_data_lookup(3KSTAT)

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n",
	    copyinstr(self->ksname), this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Accédez à présent à l'un de vos shells et exécutez la commande mpstat 1 pour lancer mpstat(1M) dans un mode dans lequel elle échantillonne les statistiques et les signale à la cadence d'une par seconde. Une fois mpstat en cours d'exécution, exécutez la commande dtrace -q -s kstat.d `pgrep mpstat` dans votre autre shell. Vous obtenez une sortie correspondant aux statistiques en cours d'accès. Appuyez sur Control-C pour abandonner dtrace et revenir à l'invite du shell.


# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#

Si vous capturez la sortie dans chaque fenêtre de terminal et que vous retirez chaque valeur de la valeur signalée par l'itération précédente par l'intermédiaire des statistiques, vous devriez pouvoir faire correspondre la sortie dtrace avec la sortie de mpstat. Le programme donné en exemple enregistre le pointeur du nom de compteur en entrée dans la fonction de recherche, puis exécute la majeure partie du travail de suivi en retour à partir de kstat_data_lookup(3KSTAT) Les fonctions en D intégrées copyinstr() et copyin() recopient les résultats du processus utilisateur dans DTrace lorsque arg1 (valeur retournée) n'est pas NULL. Une fois que les données de kstat ont été copiées, l'exemple signale la valeur de compteur de ui64 à partir de l'union. Cet exemple simplifié suppose que mpstat échantillonne les compteurs utilisant le membre value.ui64. Essayez, sous forme d'exercice, de recoder kstat.d pour utiliser des prédicats et d'imprimer le membre de l'union correspondant au membre data_type. Vous pouvez également essayer de créer une version de kstat.d qui calcule la différence entre des valeurs de données successives et offre généralement une sortie similaire à mpstat.