7 投機トレース

この章では、DTraceの投機トレース機能の使用方法について説明します。これには、暫定的にデータをトレースし、後でそのデータをトレース・バッファにコミットするか破棄するかを決定できる機能が含まれます。

投機トレースについて

DTraceの場合、不要なイベントを除外するための主要なメカニズムは述語メカニズムです。詳細は、「Dプログラム構造」を参照してください。述語は、その起動時にプローブ・イベントが重要であるかどうかがわかっている場合に便利です。たとえば、特定のプロセスや特定のファイル記述子に関連するアクティビティのみが重要な場合は、プローブの起動時に、それが対象のプロセスまたはファイル記述子に関係するプローブかどうか判断できます。プローブの起動後しばらく経過しないと、そのプローブ・イベントが重要かどうかわからない場合もあります。

たとえば、EIOEINVALなどの一般的なエラー・コードで失敗することがあるシステム・コールについて考えてみます。この場合、エラー状態につながるコード・パスを調べると役立つことがあります。このコード・パスを取得するために、すべてのプローブを有効にすることもできます。ただしそれが可能なのは、失敗したシステム・コールを切り分けて、意味のある述語を作成できる場合に限られます。エラーが散発的であったり特定困難な場合には、重要な可能性があるイベントをすべてトレースし、データを後処理して問題のコード・パスと無関係なイベントを除外する必要があります。この場合、重要なイベントの数がわずかであってもトレースが必要なイベントの数は膨大になり、後処理が難しくなります。

このような状況では、投機トレース機能を使用して、1つ以上のプローブの場所にあるデータを暫定的にトレースできます。その後、別のプローブの場所にある主バッファにデータをコミットできます。結果として、トレース・データには重要な出力のみが含まれ、後処理は必要とされず、DTraceのオーバーヘッドが最小化されます。

投機インタフェース

次の表では、DTraceの投機関数について説明します。

表7-1 DTraceの投機関数

関数 引数 説明

speculation

なし

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

speculate

ID

同じ節の残りの部分を、指定されたIDの投機バッファにトレースすることを示します

commit

ID

指定されたIDの投機バッファをコミットします。

discard

ID

指定されたIDの投機バッファを破棄します。

投機の作成

speculation関数は、投機バッファを割り当て、投機識別子を返します。投機識別子は、後でspeculate関数をコールするときに使用する必要があります。投機バッファは有限のリソースです。speculationをコールしたときに使用可能な投機バッファがない場合、IDとしてゼロが返され、対応するDTraceエラー・カウンタが増分されます。ゼロのIDは常に無効ですが、speculatecommitおよびdiscardの各関数に渡すことができます。speculationのコールに失敗した場合は、次のようなメッセージがdtraceによって生成されます。

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

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

投機の使用

投機を使用するには、speculationから返された識別子を、データ記録アクションの前に節でspeculate関数に渡す必要があります。speculateを含む節の後続のデータ記録アクションはすべて、投機的にトレースされます。1つのDプローブ節でデータ記録アクションの後にspeculateがコールされた場合は、Dコンパイラでコンパイル時エラーが生成されます。したがって、節には投機トレース・リクエストのみ、またはそれ非投機トレース・リクエストのみを含めることができ、両方を含めることはできません。

集積アクション、破壊アクションおよびexitアクションは投機的になりません。speculateを含む節でこれらのアクションのいずれかを実行しようとすると、コンパイル時エラーが発生します。また、speculatespeculateの後に続くことはありません。1つの節に許可される投機は1つのみです。speculateしか含まない節は、有効なプローブIDのみをトレースするように定義されているデフォルトのアクションを投機的にトレースします。デフォルト・アクションの詳細は、「アクションおよびサブルーチン」を参照してください。

次の例に示すように、通常は、speculationの結果をスレッド固有変数に割り当て、その変数を他のプローブに対する述語として、またspeculateに対する引数として使用します。

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

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

投機のコミット

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

コミットする投機バッファは、各CPUの投機バッファが対応するCPUごとの主バッファに完全にコピーされるまで、後続のspeculationコールで使用することはできません。同様に、コミットするバッファに対する後続のspeculateコールは暗黙的に破棄され、後続のcommitコールやdiscardコールは暗黙的に失敗します。最後に、commitを含む節にデータ記録アクションを含めることはできません。ただし、1つの節に複数のcommitコールを含めて、ばらばらのバッファをコミットできます。

投機の破棄

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

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

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

投機の例

投機を使用できる1つの場合として、特定のコード・パスをハイライトする例があります。次の例は、open()システム・コールでコールに失敗したときのコード・パスをすべて示しています。次のソース・コードを入力し、specopen.dという名前のファイルに保存します。

#!/usr/sbin/dtrace -Fs

syscall::open:entry
{
  /*
   * The call to speculation() creates a new speculation. If this fails,
   * dtrace 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 committed.
   */
  printf("%s", copyinstr(arg0));
}

syscall::open: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
/self->spec && errno != 0/
{
  /*
   * If errno is non-zero, we want to commit the speculation.
   */
  commit(self->spec);
  self->spec = 0;
}

syscall::open: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 4 probes
CPU FUNCTION
  1  => open                                  /var/ld/ld.config
  1  <= open                                          2
  1  => open                                  /images/UnorderedList16.gif
  1  <= open                                          4
...

投機のオプションおよびチューニング

投機トレース・アクションを試行したときに投機バッファがいっぱいになっていると、バッファには何のデータも格納されず、欠落カウントが増分されます。この状況では、次のようなメッセージがdtraceによって生成されます。

dtrace: 38 speculative drops

投機が欠落しても、投機バッファのコミット時に投機バッファの全内容が主バッファにコピーされる機能は阻害されません。同様に、最終的に破棄された投機バッファで欠落が発生した場合でも、投機の欠落が発生することもあります。投機の欠落を少なくするには投機バッファのサイズを大きくしますが、これはspecsizeオプションを使用してチューニングします。specsizeオプションには、任意のサイズ接尾辞を指定できます。このバッファのサイズ変更ポリシーはbufresizeオプションで指定します。

speculationのコール時に、投機バッファが使用できないこともあります。まだコミットも破棄もされていないバッファが存在する場合には、次のようなメッセージがdtraceで生成されます。

dtrace: 1 failed speculation (no speculative buffer available)

このような投機が失敗する可能性を小さくするには、nspecオプションを指定して、投機バッファの数を増やします。nspecのデフォルト値は1です。

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

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

このエラー・メッセージは、1つの投機バッファに対してcommitがコールされてから、そのバッファが実際にすべてのCPUでコミットされるまでの間にspeculationがコールされたことを示しています。このような投機が失敗する可能性を小さくするには、cleanrateオプションを使用して、CPUがクリーニングされるレートを大きくします。cleanrateのデフォルト値は101です。