Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 13 Spekulative Ablaufverfolgung

Dieses Kapitel befasst sich mit der DTrace-Einrichtung für die spekulative Ablaufverfolgung, der Fähigkeit, Daten „verdachtsweise“ zu verfolgen und später zu entscheiden, ob sie an einen Tracing-Puffer übergeben oder aber verworfen werden. Der wichtigste Mechanismus zum Herausfiltern uninteressanter Ereignisse in DTrace ist der inKapitel 4D-Programmstruktur besprochene Prädikatmechanismus. Prädikate sind dann nützlich, wenn wir bereits zum Zeitpunkt einer Prüfpunktauslösung wissen, ob das Prüfpunktereignis von Interesse ist. Wenn uns beispielsweise nur eine zu einem bestimmten Prozess oder Dateibezeichner gehörende Tätigkeit interessiert, wissen wir bei der Prüfpunktauslösung, ob sich diese auf den relevanten Prozess bzw. Dateibezeichner bezieht. In anderen Situationen wissen wir aber unter Umständen erst einen Moment nach der Prüfpunktauslösung, ob ein bestimmtes Ereignis von Interesse ist.

Wenn beispielsweise ein Systemaufruf gelegentlich mit einem üblichen Fehlercode (z. B. EIO oder EINVAL) fehlschlägt, kann es interessant sein, den Codepfad zu untersuchen, der zu dieser Fehlerbedingung führt. Zum Erfassen des Codepfads könnten Sie jeden Prüfpunkt aktivieren - allerdings nur, wenn sich der scheiternde Aufruf so isolieren lässt, dass ein bedeutungsvolles Prädikat konstruiert werden kann. Treten die Fehler nur sporadisch auf oder sind sie nichtdeterministisch, wären Sie gezwungen, alle potenziell interessanten Ereignisse zu verfolgen und die Daten später nachzubearbeiten, um die Ereignisse herauszufiltern, die nichts mit dem zum Fehler führenden Codepfad zu tun haben. In diesem Fall müssen sehr viele Ereignisse überwacht werden, selbst wenn die Anzahl der interessanten Ereignisse möglicherweise relativ gering ausfällt. Die Nachbearbeitung wird dadurch erschwert.

Die Einrichtung für die spekulative Ablaufverfolgung bietet Ihnen in solchen Situationen die Möglichkeit, Daten an einer oder mehreren Prüfpunktpositionen auf Verdacht hin zu verfolgen und anschließend zu entscheiden, ob die Daten an den Hauptpuffer an einer anderen Prüfpunktposition übergeben werden sollen. So enthalten die Ablaufverfolgungsdaten nur die interessante Ausgabe, es ist keine Nachbearbeitung erforderlich, und der DTrace-Overhead wird auf ein Minimum reduziert.

Schnittstellen für die Spekulation

In der folgenden Tabelle sind die DTrace-Spekulationsfunktionen beschrieben:

Tabelle 13–1 DTrace-Spekulationsfunktionen

Funktionsname 

Argumente 

Beschreibung 

speculation

Keinen 

Gibt eine ID für einen neuen spekulativen Puffer zurück 

speculate

ID 

Der übrige Teil der Klausel wird in dem mit ID angegebenen spekulativen Puffer aufgezeichnet 

commit

ID 

Übergibt den mit ID angegebenen spekulativen Puffer 

discard

ID 

Verwirft den mit ID angegebenen spekulativen Puffer 

Erzeugen von Spekulationen

Die Funktion speculation() reserviert Speicherplatz für einen spekulativen Puffer und gibt eine Spekulations-ID zurück. Die Spekulations-ID dient für die nachfolgenden Aufrufe der Funktion speculate. () Spekulative Puffer sind eine endliche Ressource: Wenn zum Zeitpunkt des Aufrufs von speculation() kein spekulativer Puffer verfügbar ist, wird eine ID von Null zurückgegeben und der entsprechende DTrace-Fehlerzähler erhöht. Eine ID von Null ist immer ungültig, kann aber an speculate(), commit() oder discard() übergeben werden. Schlägt ein Aufruf von speculation() fehl, wird eine dtrace-Meldung wie in folgendem Beispiel generiert:


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

Der Standardwert für die Anzahl spekulativer Puffer ist 1, kann aber bei Bedarf auf einen höheren Wert eingestellt werden. Weitere Informationen dazu finden Sie unter Spekulationsoptionen und Abstimmung.

Arbeiten mit Spekulationen

Wenn Sie eine Spekulation benutzen möchten, müssen Sie der Funktion speculate() vor() jeglichen Daten aufzeichnenden Aktionen in einer Klausel eine von der Funktion speculation zurückgegebene ID übergeben. Alle nachfolgenden Daten aufzeichnenden Aktionen in einer Klausel, die eine speculate()-Funktion enthält, werden spekulativ verfolgt. Sollte ein Aufruf von speculate() in einer D-Prüfpunktklausel hinter Daten aufzeichnenden Aktionen stehen, generiert der D-Compiler einen Kompilierungszeitfehler. Aus diesem Grund dürfen Klauseln entweder spekulative oder nicht spekulative Ablaufverfolgungsanforderungen enthalten, nicht aber beides.

Aggregataktionen, destruktive Aktionen und die Aktion exit sind keinesfalls spekulativ. Jeder Versuch, eine dieser Aktionen in eine Klausel aufzunehmen, die eine speculate()-Funktion enthält, ergibt einen Kompilierzeitfehler. speculate() darf nicht auf speculate() folgen: Pro Klausel ist nur eine Spekulation zulässig. Eine Klausel, die nur eine speculate()-Funktion enthält, nimmt eine spekulative Ablaufverfolgung der Standardaktion vor, die nach Vereinbarung nur die aktivierte Prüfpunkt-ID verfolgt. Eine Beschreibung der Standardaktion finden Sie in Kapitel 10Aktionen und Subroutinen.

Im Normalfall weisen wir das Ergebnis einer speculation() einer thread-lokalen Variable zu und setzen diese Variable dann als nachfolgendes Prädikat für andere Prüfpunkte sowie als Argument für speculate() ein. Beispiel:

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

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

Übergeben von Spekulationen

Spekulationen übergeben Sie mit der Funktion commit. () Mit der Übergabe eines spekulativen Puffers werden dessen Daten in den Hauptpuffer kopiert. Wenn die Daten im angegebenen spekulativen Puffer den im Hauptpuffer verfügbaren Speicherplatz überschreiten, werden keine Daten kopiert und der Zähler für Auslassungen wird erhöht. Wurden in dem Puffer Daten von mehreren CPUs spekulativ aufgezeichnet, werden die spekulativen Daten auf der übergebenden CPU unverzüglich und die spekulativen Daten auf anderen CPUs erst kurz nach der commit()-Funktion kopiert. Folglich kann zwischen einer auf einer CPU beginnenden commit()-Funktion und dem Kopieren der Daten aus spekulativen Puffern in die Hauptpuffer auf allen CPUs einige Zeit vergehen. Diese Dauer überschreitet jedoch keinesfalls die durch die Bereinigungsfrequenz vorgegebene Dauer. Ausführliche Informationen dazu finden Sie unter Spekulationsoptionen und Abstimmung.

Ein übergebender spekulativer Puffer steht nachfolgenden speculation()-Aufrufen erst dann zur Verfügung, wenn jeder CPU-weite spekulative Puffer vollständig in den entsprechenden CPU-weiten Hauptpuffer kopiert wurde. In gleicher Weise werden nachfolgende Aufrufe von speculate() für den übergebenden Puffer kommentarlos verworfen und nachfolgende Aufrufe von commit() oder discard() schlagen kommentarlos fehl. Darüber hinaus darf eine Klausel mit einer commit()-Funktion keine Daten aufzeichnenden Aktionen enthalten. Eine Klausel kann jedoch mehrere commit()-Aufrufe zum Übergeben nicht zusammenhängender Puffer enthalten.

Verwerfen von Spekulationen

Zum Verwerfen von Spekulationen verwenden Sie die Funktion commit. () Das Verwerfen eines spekulativen Puffers bedeutet, dass sein Inhalt gelöscht wird. Wenn die Spekulation nur auf der die Funktion discard() aufrufenden CPU aktiv war, wird der Puffer unverzüglich für nachfolgende Aufrufe von speculation() verfügbar . War die Spekulation auf mehreren CPUs aktiv, wird der verworfene Puffer erst kurz nach dem discard()-Aufruf für nachfolgende Aufrufe von speculation() verfügbar. Dabei übersteigt die Zeit zwischen einem discard()-Aufruf auf einer CPU und der Freigabe des Puffers für nachfolgende Spekulationen garantiert nicht die durch die Bereinigungsfrequenz vorgegebene Dauer. Wenn zum Zeitpunkt des Aufrufs von speculation() kein Puffer verfügbar ist, da alle spekulativen Puffer derzeit verworfen oder übergeben werden, wird eine dtrace-Meldung wie in folgendem Beispiel generiert:


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

Die Wahrscheinlichkeit, dass kein Puffer verfügbar ist, lässt sich durch Anpassen der Menge der spekulativen Puffer oder der Bereinigungsfrequenz herabsetzen. Ausführliche Informationen dazu finden Sie unter Spekulationsoptionen und Abstimmung.

Beispiel für eine Spekulation

Spekulationen lassen sich beispielsweise zum Hervorheben eines bestimmten Codepfads einsetzen. Das folgende Beispiel zeigt den vollständigen Codepfad unter dem open(2)()-Systemaufruf nur dann, wenn die Funktion open fehlschlägt:


Beispiel 13–1 specopen.d: Codefluss für Fehlschlag von open( )

#!/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;
}

Die Ausführung des obigen Skripts erzeugt eine Ausgabe wie in folgendem Beispiel:


# ./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

Spekulationsoptionen und Abstimmung

Sollte ein spekulativer Puffer bei einem Versuch einer spekulativen Ablaufverfolgung bereits voll sein, werden keine Daten in dem Puffer gespeichert und der Auslassungszähler wird erhöht. In diesem Fall wird eine dtrace-Meldung wie in folgendem Beispiel generiert:


dtrace: 38 speculative drops

Auslassungen von Spekulationen verhindern nicht, dass der volle spekulative Puffer in den Hauptpuffer kopiert wird, wenn eine Übergabe stattfindet. Ebenso können Ausfälle von Spekulationen selbst dann auf einem spekulativen Puffer vorkommen, wenn dieser bereits verworfen wurde. Ausfälle von Spekulationen lassen sich durch Vergrößern des spekulativen Puffers mit der Option specsize herabsetzen. Die Option specsize kann mit jedem Größensuffix angegeben werden. Die Richtlinien zur Veränderung der Größe dieses Puffers wird von der Option bufresize vorgegeben.

Es ist denkbar, dass beim Aufruf von speculation() kein spekulativer Puffer verfügbar ist. Wenn Puffer vorliegen, die noch nicht übergeben oder verworfen wurden, generiert dtrace eine Meldung wie in folgendem Beispiel:


dtrace: 1 failed speculation (no speculative buffer available)

Sie können die Wahrscheinlichkeit, dass Spekulationen auf diese Weise scheitern, durch Erhöhung der Anzahl spekulativer Puffer mit der Option nspec verringern. Der Standardwert von nspec beträgt 1.

Andererseits ist es auch möglich, dass speculation() fehlschlägt, weil alle spekulativen Puffer ausgelastet sind. In diesem Fall wird eine dtrace-Meldung wie in folgendem Beispiel generiert:


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

Diese Meldung weist darauf hin, dass der speculation()-Aufruf erfolgt ist, nachdem commit() für einen spekulativen Puffer aufgerufen, aber noch bevor dieser Puffer tatsächlich auf allen CPUs übergeben wurde. Sie können die Wahrscheinlichkeit, dass Spekulationen auf diese Weise scheitern, durch Erhöhung der CPU-Bereinigungsfrequenz mit der Option cleanrate verringern. Der Standardwert von cleanrate beträgt 101hz.


Hinweis –

Die Werte für die Option cleanrate sind in Anzahl pro Sekunde anzugeben. Verwenden Sie das Suffix hz.