Solaris 動的トレースガイド

第 13 章 投機トレース

この章では、DTrace の「投機トレース」機能について説明します。この機能を利用すると、一時的にデータをトレースし、このデータをトレースバッファーに「コミット」するか「破棄」するかをあとで決めることができます。DTrace では、重要でないイベントを除去する手段として、主に「述語」(第 4 章D プログラムの構造を参照) を使用します。述語は、そのプローブイベントがユーザーにとって重要かどうか、プローブの起動時にわかっている際に有用です。たとえば、特定のプロセスのアクティビティや特定のファイル記述子のアクティビティだけが重要である場合は、プローブの起動時に、それが該当のプロセスやファイル記述子に関係したプローブかどうか判断が可能です。一方、プローブの起動後しばらくしてからでないと、そのプローブイベントが重要かどうかわからない場合もあります。

たとえば、あるシステムコールが一般的なエラーコード (EIOEINVAL など) を出力して異常終了する場合に、エラー条件の原因となっているコードパスを調べるとします。該当のコードパスを探すため、すべてのプローブを有効にすることも考えられます。ただしこの方法は、意味のある述語を作成できる程度に問題のシステムコールが特定されている場合にかぎります。問題が散発的であったり特定が難しい場合は、今後重要になる可能性があるイベントをすべてトレースし、あとで、問題のコードパスに無関係なデータをふるい落とさなければなりません。この場合、本当に重要なイベントはわずかであっても、大量のイベントをトレースする必要があるので、後処理が難しくなります。

投機トレース機能は、こうした状況で利用します。この機能では、1 つ以上のプローブ位置で一時的にデータをトレースしたあと、データを主バッファーにコミットするかどうかは、別のプローブ位置で決めることができます。結果的に、重要な情報だけがトレースデータとして確保されます。さらに、後処理が不要になり、DTrace のオーバーヘッドも最小限に抑えることができます。

投機インタフェース

以下の表に、DTrace 投機関数を一覧します。

表 13–1 DTrace 投機関数

関数名 

引数 

説明 

speculation

なし 

新しい投機バッファーの識別子を返す 

speculate

ID 

同一節内の残りの部分を、指定された ID の投機バッファーにトレースする 

commit

ID 

指定された ID の投機バッファーをコミットする 

discard

ID 

指定された ID の投機バッファーを破棄する 

投機の作成

speculation() 関数は、投機バッファーを割り当て、投機識別子を返します。返された投機識別子は、その後、speculate() 関数を呼び出すときに使用します。投機バッファーは有限のリソースです。 speculation() の呼び出し時に使用可能な投機バッファーがない場合、ID としてゼロが返され、対応する DTrace エラーカウンタの値が大きくなります。値がゼロの ID は常に無効ですが、speculate()commit()discard() のいずれかの関数に渡すことができます。speculation() の呼び出しに失敗した場合、次のような dtrace メッセージが表示されます。


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

投機バッファーの数は、デフォルトでは 1 個ですが、さらに増やすこともできます。詳細については、「投機オプションとチューニング」を参照してください。

投機の使用

投機を使用するときは、speculation() から返される識別子を、節内のデータ記録アクションのspeculate() 関数に渡す必要があります。1 つの節内で、speculate() 以降に含まれるデータ記録アクションはすべて、投機的にトレースされます。1 つの D プローブ節内で、データ記録アクションのあとに speculate() を呼び出すと、D コンパイラから、コンパイル時エラーが返されます。1 つの節には必ず、投機トレース要求だけ、またはそれ以外のトレース要求だけを含めるようにしてください。同じ節に、投機トレース要求とそれ以外のトレース要求の両方を含めることはできません。

集積アクション、破壊アクション、exit アクションは、投機的に処理することはできません。これらのいずれかのアクションを speculate() と同じ節に含めると、コンパイル時エラーが発生します。同じ節で、複数の speculate() を使用することはできません。speculate() は、節当たり 1 個と決まっています。speculate() 1 個以外に何も含まれない節では、デフォルトのアクション (プローブ ID が有効なプローブのみをトレースする) が投機的にトレースされます。デフォルトアクションについては、第 10 章アクションとサブルーチンを参照してください。

通常、speculation() の結果は、スレッド固有変数に割り当てます。その後は、このスレッド固有変数を、ほかのプローブの述語や speculate() の引数として使用します。次に例を示します。

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

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

投機のコミット

投機のコミットは、commit() 関数を使って行います。投機バッファーをコミットすると、そのデータが主バッファーにコピーされます。指定した投機バッファーに、主バッファーには収まり切らない量のデータが含まれている場合、データはコピーされず、バッファーの欠落カウントの値が大きくなります。バッファーを複数の CPU 上で投機的にトレースした場合、コミットされた CPU 上の投機データはすぐにコピーされますが、その他の CPU 上の投機データは、commit() の実行後しばらくしてからコピーされます。したがって、ある CPU 上で commit() の実行が開始されてから、投機バッファー内のデータがすべての CPU 上の主バッファーにコピーされるまでには、少し時間がかかります。ただし、クリーンアップレートで指定された時間より長くなることはありません。詳細については、「投機オプションとチューニング」を参照してください。

コミットされた投機バッファーをその後の speculation() 呼び出しで使用することはできません。使用したい場合は、各 CPU の投機バッファーを対応する CPU の主バッファーに完全にコピーする必要があります。同様に、コミットされたバッファーに対するその後の speculate() 呼び出しは、サイレントモードで破棄されます。したがって、その後の commit() 呼び出しや discard() 呼び出しは、何のメッセージも出さずに異常終了します。結局、commit() が含まれる節に、データ記録アクションを含めることはできません。しかし、同じ節に複数の commit() 呼び出しを含めて、複数のバッファーをばらばらにコミットすることは可能です。

投機の破棄

投機を破棄するには、discard() 関数を使用します。投機バッファーを破棄すると、その内容が失われます。投機が discard() を呼び出した CPU 上でのみアクティブになっている場合は、破棄された投機バッファーをその後の speculation() 呼び出しですぐに使用できます。投機が複数の CPU 上でアクティブになっている場合は、discard() を呼び出したあと、破棄された投機バッファーをその後の speculation() 呼び出しで使用できるようになるまで、少し時間がかかります。ある CPU 上で discard() が呼び出されたあと、破棄された投機バッファーをその後の speculation で使用できるようになるまでの時間が、クリーンアップレートで指定された時間より長くなることはありません。speculation() を呼び出したときに、すべての投機バッファーが破棄またはコミットされた状態で、使用可能なバッファーがまったく存在しない場合は、次のような dtrace メッセージが表示されます。


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

投機バッファー数やクリーンアップレートをチューニングすれば、すべてのバッファーが同時に使用不能状態になるのを防ぐことができます。詳細については、「投機オプションとチューニング」を参照してください。

投機の例

投機は、特定のコードパスを明らかにするために使用できます。以下の例では、 open() が異常終了したときだけ、open(2) システムコールのコードパスをすべて表示できます。


例 13–1 specopen.d: 異常終了した 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;
}

このスクリプトを実行すると、次のような出力が得られます。


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

投機オプションとチューニング

投機トレースアクションの実行時に投機バッファーがいっぱいになっていると、このバッファーには何のデータも格納されず、欠落カウントの値が大きくなります。この場合、次のような dtrace メッセージが表示されます。


dtrace: 38 speculative drops

投機欠落が起こっても、投機バッファーのコミット時には、バッファーの内容がすべて主バッファーにコピーされます。同様に、破棄された投機バッファーで欠落が発生した場合でも、投機欠落が発生します。投機欠落を防ぐには、specsize オプションを使って、投機バッファーのサイズを大きくします。specsize オプションには、任意のサイズ接尾辞を付けることができます。投機バッファーのサイズ変更ポリシーは、bufresize オプションで指定します。

speculation() の呼び出し時に、投機バッファーが使用不能になっていることもあります。まだコミットされておらず、破棄されてもいないバッファーが存在する場合、次のような dtrace メッセージが表示されます。


dtrace: 1 failed speculation (no speculative buffer available)

この種の投機の失敗を防ぐには、nspec オプションを使って、投機バッファー数を増やします。nspec のデフォルト値は 1 です。

すべての投機バッファーがビジー状態になっている場合も、speculation() は失敗します。この場合、次のような dtrace メッセージが表示されます。


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

ある投機バッファーの commit() が呼び出されたあと、このバッファーの内容がまだすべての CPU でコミットされていないのに speculation() が呼び出されたことがわかります。この種の投機の失敗を防ぐには、cleanrate オプションを使って、CPU のクリーンアップレートの値を大きくします。cleanrate のデフォルト値は 101hz です。


注 –

cleanrate オプションの値は、1 秒あたりの回数で指定する必要があります。接尾辞 hz を使用します。