Manuel de suivi dynamique Solaris

Chapitre 13 Suivi spéculatif

Ce chapitre traite de l'utilitaire DTrace pour le suivi spéculatif et de la possibilité de réaliser provisoirement le suivi de données, puis de décider de valider ou supprimer les données à un tampon de suivi. Dans DTrace, le principal mécanisme de filtrage des événements inintéressants, prédicat, est abordé dans le Chapitre4Structure de programme D. Les prédicats sont pratiques lorsque vous savez à quel moment une sonde se déclenche, que l'événement qui lui est associé vous intéresse ou non. Par exemple, si vous n'êtes intéressé que par l'activité en rapport avec un certain processus ou un certain descripteur de fichier, vous savez quand la sonde se déclenche si elle est liée au processus ou au descripteur de fichier qui vous intéresse. Dans d'autres situations, cependant, vous pouvez ne pas être mesure de déterminer si un événement de sonde vous intéresse avant que la sonde ne se soit déclenchée.

Par exemple, si un appel système échoue exceptionnellement avec un code d'erreur standard, (EIO ou EINVAL, notamment), vous souhaiterez peut-être examiner le chemin d'accès au code conduisant à l'erreur. Pour capturer le chemin d'accès au code, vous devez activer toutes les sondes — mais seulement si vous êtes en mesure d'isoler le code défaillant de manière à pouvoir créer un prédicat clair. Si les défaillances sont sporadiques et non déterminantes, vous n'aurez peut-être pas d'autre choix que de suivre tous les événements susceptibles de vous intéresser, puis de traiter ultérieurement les données pour filtrer celles qui n'ont pas été associées au chemin d'accès au code défaillant. Le cas échéant, même si le nombre d'événements intéressants peut s'avérer raisonnablement faible, le nombre d'événements suivis doit être très élevé, ce qui complique le traitement ultérieur.

Vous pouvez donc recourir à la fonction de suivi spéculatif pour suivre provisoirement les données vers un ou plusieurs emplacements de sonde, puis choisir de valider les données vers le tampon principal au niveau d'un autre emplacement de sonde. En conclusion, les données suivies ne contiennent que les résultats qui vous intéressent, aucun traitement ultérieur n'est requis et le temps système de DTrace est réduit au minimum.

Interfaces de spéculation

Le tableau suivant décrit les fonctions de spéculation de DTrace :

Tableau 13–1 Fonctions de spéculation de DTrace

Nom de la fonction 

Args 

Description 

speculation

Aucune 

Retourne un identificateur pour un nouveau tampon spéculatif 

speculate

ID 

Indique que le reste de la clause doit être suivi vers le tampon spéculatif spécifié par l'ID 

commit

ID 

Valide le tampon spéculatif lié à l'ID 

discard

ID 

Supprime le tampon spéculatif lié à l'ID 

Création d'une spéculation

La fonction speculation() alloue un tampon spéculatif et renvoie un identificateur de spéculation. L'identificateur de spéculation doit être utilisé dans les appels ultérieurs vers la fonction speculate. () Les tampons spéculatifs constituent une ressource infinie : Si aucun tampon spéculatif n'est disponible lorsque la fonction speculation() est appelée, un ID de zéro est retourné et un compteur d'erreur correspondant de DTrace est incrémenté. Un ID de zéro est toujours invalide mais il peut être transmis à une fonction speculate(), commit() ou discard(). Si une fonction speculation() échoue, un message dtrace similaire à l'exemple suivant est généré :


dtrace: 2 failed speculations (no speculative buffer space available)

Le nombre de tampons spéculatifs par défaut est de 1 mais ce chiffre peut éventuellement être plus élevé. Pour plus d'informations, reportez-vous à la section Options de spéculation et réglage.

Utilisation d'une spéculation

Pour utiliser une spéculation, un identificateur retourné par une fonction speculation() doit être transféré à une fonction speculate() dans une clause avant toute action d'enregistrement des données. Toutes les actions d'enregistrement de données ultérieures dans une clause contenant une fonction speculate() feront l'objet d'un suivi spéculatif. Le compilateur D générera une erreur de durée de compilation si un appel de la fonction speculate() suit les actions d'enregistrement des données dans une clause de la sonde D. Par conséquent, les clauses peuvent contenir des requêtes de suivi spéculatif ou non-spéculatif, mais pas les deux.

Les actions d'agrégation, de destruction et exit peuvent ne jamais être spéculatives. Essayer d'entreprendre l'une de ces actions dans une clause contenant une fonction speculate() provoque une erreur de durée de compilation. Une fonction speculate() ne suit pas forcément une fonction speculate() : une clause ne peut contenir qu'une spéculation. Une clause contenant une seule fonction speculate() suit l'action par défaut de manière spéculative, cette action étant configurée pour ne suivre que l'ID de la sonde activée. Pour plus d'informations sur l'action par défaut, reportez-vous au Chapitre10Actions et sous-routines.

En règle générale, vous affectez le résultat d'une fonction speculation() à une variable de thread locale, puis vous utilisez cette variable comme prédicat ultérieur à d'autres sondes, ainsi que comme argument à la fonction speculate(). Exemple :

syscall::open:entry
{
	self->spec = speculation();
}

syscall:::
/self->spec/
{
	speculate(self->spec);
	printf("this is speculative");
}

Validation d'une spéculation

Vous validez des spéculations à l'aide de la fonction commit. () Lorsqu'un tampon spéculatif est validé, les données qu'il contient sont copiées dans le tampon principal. Si le tampon spéculatif spécifié comporte plus de données que l'espace disponible dans le tampon principal ne peut en recevoir, aucune donnée n'est copiée et le compteur de pose du tampon est incrémenté. Si le tampon a fait l'objet d'un suivi spéculatif sur plus d'une CPU, les données spéculatives sur la CPU de validation sont copiées immédiatement tandis que les données spéculatives sur les autres CPU sont parfois copiées après la fonction commit(). Par conséquent, un certain laps de temps peut s'écouler entre le moment où une fonction commit() est exécutée sur une CPU et celui où les données sont copiées des tampons spéculatifs sur les tampons principaux sur toutes les CPU. Ce laps de temps ne peut pas excéder la durée dictée par le taux de nettoyage. Pour plus d'informations, reportez-vous à la section Options de spéculation et réglage.

Aucun tampon spéculatif de validation n'est disponible pour les appels speculation() ultérieurs tant que chaque tampon spéculatif par CPU n'a pas été complètement copié dans le tampon principal par CPU correspondant. De même, les appels ultérieurs de la fonction speculate() sur le tampon de validation sont silencieusement supprimés et les appels ultérieurs de la fonction commit() ou discard() échouent silencieusement. Enfin, une clause contenant une fonction commit() ne peut pas contenir d'action d'enregistrement de données. Par contre, une clause peut contenir plusieurs appels commit() pour valider des tampons disjoints.

Annulation d'une spéculation

Vous annulez les spéculations à l'aide de la fonction discard. () Lorsqu'un tampon spéculatif est annulé, son contenu est perdu. Si la spéculation n'a été activée que sur la CPU qui appelle la fonction discard(), le tampon est immédiatement disponible pour les appels ultérieurs de la fonction speculation (). En cas d'activation de la spéculation sur plusieurs CPU, le tampon supprimé est parfois disponible pour la fonction speculation() ultérieure après appel de la fonction discard(). Le délai entre l'exécution d'une fonction discard() sur une CPU et le moment où le tampon est disponible pour les spéculations ultérieures ne peut pas excéder la durée dictée par le taux de nettoyage. Si au moment de l'appel de la fonctionspeculation(), aucun tampon n'est disponible car tous les tampons spéculatifs sont actuellement supprimés ou validés, un message dtrace similaire à l'exemple suivant est généré :


dtrace: 905 failed speculations (available buffer(s) still busy)

Vous pouvez réduire la probabilité d'indisponibilité de tous les tampons en ajustant le nombre de tampons de spéculation ou le taux de nettoyage. Pour plus d'informations, reportez-vous à la section Options de spéculation et réglage.

Exemple de spéculation

Les spéculations sont notamment utilisées pour mettre en valeur un chemin d'accès au code particulier. L'exemple suivant présente l'intégralité du chemin d'accès au code dans un appel système open(2) lorsque la fonction open() échoue :


Exemple 13–1 specopen.d : flux de code en cas d'échec open(2)

#!/usr/sbin/dtrace -Fs

syscall::open:entry,
syscall::open64:entry
{
	/*
	 * The call to speculation() creates a new speculation.  If this fails,
	 * dtrace(1M) will generate an error message indicating the reason for
	 * the failed speculation(), but subsequent speculative tracing will be
	 * silently discarded.
	 */
	self->spec = speculation();
	speculate(self->spec);

	/*
	 * Because this printf() follows the speculate(), it is being 
	 * speculatively traced; it will only appear in the data buffer if the
	 * speculation is subsequently commited.
	 */
	printf("%s", stringof(copyinstr(arg0)));
}

fbt:::
/self->spec/
{
	/*
	 * A speculate() with no other actions speculates the default action:
	 * tracing the EPID.
	 */
	speculate(self->spec);
}

syscall::open:return,
syscall::open64:return
/self->spec/
{
	/*
	 * To balance the output with the -F option, we want to be sure that
	 * every entry has a matching return.  Because we speculated the
	 * open entry above, we want to also speculate the open return.
	 * This is also a convenient time to trace the errno value.
	 */
	speculate(self->spec);
	trace(errno);
}

syscall::open:return,
syscall::open64:return
/self->spec && errno != 0/
{
	/*
	 * If errno is non-zero, we want to commit the speculation.
	 */
	commit(self->spec);
	self->spec = 0;
}

syscall::open:return,
syscall::open64:return
/self->spec && errno == 0/
{
	/*
	 * If errno is not set, we discard the speculation.
	 */
	discard(self->spec);
	self->spec = 0;
}

Exécuter le script ci-dessus engendre une sortie similaire à l'exemple suivant :


# ./specopen.d
dtrace: script './specopen.d' matched 24282 probes
CPU FUNCTION                                 
  1  => open                                  /var/ld/ld.config
  1    -> open                                
  1      -> copen                             
  1        -> falloc                          
  1          -> ufalloc                       
  1            -> fd_find                     
  1              -> mutex_owned               
  1              <- mutex_owned               
  1            <- fd_find                     
  1            -> fd_reserve                  
  1              -> mutex_owned               
  1              <- mutex_owned               
  1              -> mutex_owned               
  1              <- mutex_owned               
  1            <- fd_reserve                  
  1          <- ufalloc                       
  1          -> kmem_cache_alloc              
  1            -> kmem_cache_alloc_debug      
  1              -> verify_and_copy_pattern   
  1              <- verify_and_copy_pattern   
  1              -> file_cache_constructor    
  1                -> mutex_init              
  1                <- mutex_init              
  1              <- file_cache_constructor    
  1              -> tsc_gethrtime             
  1              <- tsc_gethrtime             
  1              -> getpcstack                
  1              <- getpcstack                
  1              -> kmem_log_enter            
  1              <- kmem_log_enter            
  1            <- kmem_cache_alloc_debug      
  1          <- kmem_cache_alloc              
  1          -> crhold                        
  1          <- crhold                        
  1        <- falloc                          
  1        -> vn_openat                       
  1          -> lookupnameat                  
  1            -> copyinstr                   
  1            <- copyinstr                   
  1            -> lookuppnat                  
  1              -> lookuppnvp                
  1                -> pn_fixslash             
  1                <- pn_fixslash             
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                    -> groupmember         
  1                      -> supgroupmember    
  1                      <- supgroupmember
  1                    <- groupmember         
  1                  <- ufs_iaccess           
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                  <- ufs_iaccess           
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1                -> pn_getcomponent         
  1                <- pn_getcomponent         
  1                -> ufs_lookup              
  1                  -> dnlc_lookup           
  1                    -> bcmp                
  1                    <- bcmp                
  1                  <- dnlc_lookup           
  1                  -> ufs_iaccess           
  1                    -> crgetuid            
  1                    <- crgetuid            
  1                  <- ufs_iaccess           
  1                  -> vn_rele               
  1                  <- vn_rele               
  1                <- ufs_lookup              
  1                -> vn_rele                 
  1                <- vn_rele                 
  1              <- lookuppnvp                
  1            <- lookuppnat                  
  1          <- lookupnameat                  
  1        <- vn_openat                       
  1        -> setf                            
  1          -> fd_reserve                    
  1            -> mutex_owned                 
  1            <- mutex_owned                 
  1            -> mutex_owned                 
  1            <- mutex_owned                 
  1          <- fd_reserve                    
  1          -> cv_broadcast                  
  1          <- cv_broadcast                  
  1        <- setf                            
  1        -> unfalloc                        
  1          -> mutex_owned                   
  1          <- mutex_owned                   
  1          -> crfree                        
  1          <- crfree                        
  1          -> kmem_cache_free               
  1            -> kmem_cache_free_debug       
  1              -> kmem_log_enter            
  1              <- kmem_log_enter            
  1              -> tsc_gethrtime             
  1              <- tsc_gethrtime             
  1              -> getpcstack                
  1              <- getpcstack                
  1              -> kmem_log_enter            
  1              <- kmem_log_enter
  1              -> file_cache_destructor     
  1                -> mutex_destroy           
  1                <- mutex_destroy           
  1              <- file_cache_destructor     
  1              -> copy_pattern              
  1              <- copy_pattern              
  1            <- kmem_cache_free_debug       
  1          <- kmem_cache_free               
  1        <- unfalloc                        
  1        -> set_errno                       
  1        <- set_errno                       
  1      <- copen                             
  1    <- open                                
  1  <= open                                          2

Options de spéculation et réglage

Si un tampon spéculatif est plein lorsqu'une action de suivi spéculatif est tentée, aucune donnée n'est enregistrée dans le tampon et un compteur de pose est incrémenté. Le cas échéant, un message dtrace similaire à l'exemple suivant est généré :


dtrace: 38 speculative drops

Les poses spéculatives n'empêchent pas la copie du tampon spéculatif plein dans le tampon principal lorsque le tampon est validé. De même, elles peuvent se produire même si les poses ont eut lieu sur un tampon spéculatif qui a finalement été supprimé. Il est possible de réduire les poses spéculatives en augmentant la taille du tampon spéculatif que l'option specsize permet de régler. Vous pouvez spécifier l'option specsize avec n'importe quel suffixe de taille. La stratégie de redimensionnement de ce tampon est dictée par l'option bufresize.

Les tampons spéculatifs peuvent ne pas être disponibles lors de l'appel de la fonction speculation. () S'il reste encore des tampons à n'avoir été ni validés ni supprimés, un message dtrace similaire à l'exemple suivant est généré :


dtrace: 1 failed speculation (no speculative buffer available)

Vous pouvez réduire la probabilité que des spéculations de cette nature échouent en augmentant le nombre de tampons spéculatifs avec l'option nspec. La valeur par défaut de nspec est de 1.

Par ailleurs, la fonction speculation() peut échouer car tous les tampons spéculatifs sont occupés. Le cas échéant, un message dtrace similaire à l'exemple suivant est généré :


dtrace: 1 failed speculation (available buffer(s) still busy)

Ce message indique que la fonction speculation() a été appelée après l'appel de la fonction commit() pour un tampon spéculatif mais avant que ce tampon n'ait été réellement validé sur toutes les CPU. Vous pouvez réduire la probabilité que des spéculations de cette nature échouent en augmentant le taux de nettoyage des CPU à l'aide de l'option cleanrate. La valeur par défaut de cleanrate est 101.


Remarque –

Les valeurs spécifiées pour l'option cleanrate doivent être en nombre par seconde. Utilisez le suffixe hz.