Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1095] ファイルの概要

このコミットは、Go言語の初期のプロファイリングツールである prof コマンドの主要なソースファイルである src/cmd/prof/main.c に対する変更です。このファイルは、プロファイリングのサンプリングロジック、プロファイリング対象プロセスの制御、および収集されたデータの処理を担当しています。

コミット

このコミットは、プロファイリングツール prof におけるプロセスの開始/停止に関するバグを修正し、さらにプロファイリングの時間制限が指定された場合にのみ適用されるように変更します。具体的には、サンプリング処理中のプロセスの制御フローを改善し、時間制限のデフォルト値を変更することで、より柔軟かつ堅牢なプロファイリングを可能にしています。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/10137e7f1d5ec7d9ddedc02b50b6ad832bed7c0a

元コミット内容

fix start/stop bug in prof.
also only time-limit if a value is provided.

R=rsc
DELTA=9  (4 added, 2 deleted, 3 changed)
OCL=18917
CL=18920

変更の背景

このコミットが行われた背景には、prof ツールにおけるプロファイリング対象プロセスの制御に関する既存のバグと、時間制限の挙動に関する改善の必要性がありました。

  1. start/stop バグの修正: 従来の prof ツールでは、プロファイリングのサンプリング中にプロセスの停止 (stop) と再開 (start) を行うロジックに問題があったと考えられます。これは、サンプリングが中断された場合や、特定の条件下でプロセスが停止したままになるなどの不具合を引き起こす可能性がありました。プロファイリングは対象プロセスの実行を一時的に停止して状態を収集するため、この停止と再開のロジックは非常に重要です。不適切な制御は、プロファイリングの正確性を損なうだけでなく、対象アプリケーションの動作にも悪影響を及ぼす可能性があります。

  2. 時間制限の柔軟性向上: 以前は total_sec がデフォルトで10秒に設定されており、常に時間制限が適用されていました。しかし、プロファイリングのユースケースによっては、時間制限なしで実行したい場合や、特定の時間だけ実行したい場合があります。このコミットは、total_sec が明示的に設定された場合にのみ時間制限を適用し、デフォルトでは時間制限なしで動作するように変更することで、ツールの柔軟性を高めることを目的としています。

これらの変更は、prof ツールの信頼性と使いやすさを向上させるために不可欠でした。

前提知識の解説

このコミットの理解を深めるために、以下の技術的概念について解説します。

  • プロファイリング (Profiling): ソフトウェアのパフォーマンス特性を分析するプロセスです。CPU使用率、メモリ使用量、関数呼び出しの頻度と時間、I/O操作などを測定し、プログラムのボトルネックや非効率な部分を特定するのに役立ちます。prof のようなツールは、実行中のプログラムの状態を定期的に「サンプリング」することで、この情報を収集します。

  • prof コマンド (Go言語の初期プロファイラ): Go言語の初期段階で提供されていたプロファイリングツールの一つです。C言語で実装されており、Goプログラムの実行時の挙動を分析するために使用されました。現代のGo言語では、go tool pprof など、より高度で統合されたプロファイリングツールが提供されていますが、この prof はその前身にあたるツールと考えられます。

  • ctlproc 関数: このコードベースにおける ctlproc 関数は、プロファイリング対象のプロセス (pid) を制御するための内部関数です。引数として "stop""start" を受け取ることから、対象プロセスの実行を一時的に停止したり、再開したりする機能を提供していると推測されます。プロファイリングでは、正確な状態を収集するために、対象プロセスを一時的に停止させる必要があります。

  • サンプリング (Sampling): プロファイリング手法の一つで、プログラムの実行中に一定の間隔でその状態(例: 実行中の命令ポインタ、スタックトレース)を記録します。これにより、プログラムがどの部分で多くの時間を費やしているかを統計的に推定できます。sample() 関数がこの役割を担っています。

  • sample() 関数: プロファイリングにおける単一のサンプリング操作を実行する関数です。この関数は、対象プロセスの現在の状態(特にプログラムカウンタ ureg.ip とスタックポインタ ureg.sp)を収集します。

  • samples() 関数: プロファイリングセッション全体を管理し、sample() 関数を繰り返し呼び出して複数のサンプルを収集する関数です。時間制限やサンプリング間隔のロジックがこの関数内で制御されます。

  • total_secdelta_msec:

    • total_sec: プロファイリングの総実行時間を秒単位で指定する変数です。
    • delta_msec: 各サンプリング間の間隔をミリ秒単位で指定する変数です。
  • ureg.ip (Instruction Pointer) と ureg.sp (Stack Pointer):

    • ureg.ip: CPUの命令ポインタ(またはプログラムカウンタ)レジスタの値で、現在実行されている命令のアドレスを示します。
    • ureg.sp: CPUのスタックポインタレジスタの値で、現在のスタックフレームの最上位(または最下位)のアドレスを示します。 これらは、プログラムの実行フローとスタックの状態を把握するためにプロファイリングで重要な情報です。
  • nanosleep(): 指定されたナノ秒数だけ現在のスレッドの実行を一時停止するシステムコールです。samples() 関数内でサンプリング間隔を調整するために使用されます。

技術的詳細

このコミットの技術的な変更点は、主に src/cmd/prof/main.c 内の total_sec 変数の初期化、sample 関数、および samples 関数のロジックに集中しています。

  1. total_sec の初期値変更:

    • 変更前: int total_sec = 10;
    • 変更後: int total_sec = 0; この変更により、プロファイリングのデフォルトの時間制限が10秒から0秒(時間制限なし)に変更されました。これは、ユーザーが明示的に時間制限を指定しない限り、プロファイリングが継続的に実行されることを意味します。これにより、より長時間のプロファイリングや、特定のイベントが発生するまでプロファイリングを継続するようなユースケースに対応できるようになります。
  2. sample 関数からの ctlproc 呼び出しの削除:

    • 変更前: sample 関数の冒頭と末尾に ctlproc(pid, "stop");ctlproc(pid, "start"); が存在していました。
    • 変更後: これらの ctlproc 呼び出しが sample 関数から完全に削除されました。 この変更は非常に重要です。以前は、個々のサンプリング操作 (sample 関数) の中でプロファイリング対象プロセスの停止と再開が行われていました。しかし、これはサンプリングロジックとプロセス制御ロジックが密結合している状態であり、競合状態やデッドロック、あるいはサンプリングが失敗した場合にプロセスが停止したままになるなどのバグを引き起こす可能性がありました。ctlproc 呼び出しを sample 関数から削除することで、サンプリング操作は純粋にデータ収集に専念し、プロセスの停止と再開はより上位の制御ロジック(samples 関数)に委ねられることになります。これにより、責任の分離が図られ、コードの堅牢性が向上します。
  3. samples 関数での ctlproc の移動と条件付き実行: samples 関数は、複数のサンプリングを繰り返し実行し、プロファイリングセッション全体を管理する役割を担っています。この関数における変更は、前述の sample 関数からの ctlproc 削除と連携して、プロセスの制御フローを改善します。

    • ループ条件の変更:

      • 変更前: for(msec = 0; msec < 1000*total_sec; msec += delta_msec)
      • 変更後: for(msec = 0; total_sec <= 0 || msec < 1000*total_sec; msec += delta_msec) この変更により、ループの継続条件に total_sec <= 0 が追加されました。これは、total_sec が0以下の場合(つまり時間制限が設定されていない場合)には、ループが msec < 1000*total_sec の条件に依存せず、実質的に無限に継続することを意味します。これにより、時間制限なしのプロファイリングが可能になります。
    • ctlproc 呼び出しの移動とエラーハンドリングの改善:

      • 変更前は sample() 呼び出しの前後で ctlproc が行われていませんでした(sample 関数内で処理されていたため)。
      • 変更後:
        for(...) {
            ctlproc(pid, "stop"); // サンプリング直前にプロセスを停止
            if(!sample()) {       // サンプリングが失敗した場合
                ctlproc(pid, "start"); // プロセスを再開してループを抜ける
                break;
            }
            printpc(ureg.ip, ureg.sp);
            ctlproc(pid, "start"); // サンプリング後にプロセスを再開
            nanosleep(&req, NULL);
        }
        

      この変更により、samples 関数内で各サンプリングの直前に ctlproc(pid, "stop") を呼び出してプロセスを停止し、サンプリングが完了した直後に ctlproc(pid, "start") を呼び出してプロセスを再開するようになりました。これにより、サンプリング中のプロセスの状態がより厳密に制御され、正確なデータ収集が保証されます。 さらに重要なのは、sample() が失敗した場合 (!sample()) でも ctlproc(pid, "start") が呼び出されるようになった点です。これにより、サンプリング中に何らかの問題が発生して sample() が早期に終了した場合でも、プロファイリング対象プロセスが停止したままになることを防ぎ、システムの安定性を向上させます。これは「start/stop bug」の直接的な修正です。

これらの変更は、prof ツールのプロファイリングロジックをより堅牢で柔軟なものにし、特にプロセスの制御と時間制限の挙動を改善することを目的としています。

コアとなるコードの変更箇所

src/cmd/prof/main.c ファイルにおける主要な変更箇所は以下の通りです。

--- a/src/cmd/prof/main.c
+++ b/src/cmd/prof/main.c
@@ -19,7 +19,7 @@ int fd;
 Map *map;
 Map	*symmap;
 struct Ureg ureg;
-int total_sec = 10;
+int total_sec = 0;
 int delta_msec = 100;
 int collapse = 1;	// collapse histogram trace points in same function
 
@@ -94,7 +94,6 @@ sample(void)
 	static int n;
 
 	n++;
-\tctlproc(pid, "stop");
 	for(i = 0; i < sizeof ureg; i+=8) {
 		if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) {
 			if(n == 1)
@@ -103,7 +102,6 @@ sample(void)
 			return 0;
 		}
 	}
-\tctlproc(pid, "start");
 	return 1;
 }
 
@@ -145,7 +143,7 @@ addtohistogram(uvlong pc, uvlong sp)
 {
 	int h;
 	PC *x;
-\t
+\
 	h = pc % Ncounters;
 	for(x = counters[h]; x != NULL; x = x->next) {
 		if(x->pc == pc) {
@@ -188,10 +186,14 @@ samples(void)
 
 	req.tv_sec = delta_msec/1000;
 	req.tv_nsec = 1000000*(delta_msec % 1000);
-\tfor(msec = 0; msec < 1000*total_sec; msec += delta_msec) {
-\t\tif(!sample())\
+\tfor(msec = 0; total_sec <= 0 || msec < 1000*total_sec; msec += delta_msec) {
+\t\tctlproc(pid, "stop");
+\t\tif(!sample()) {
+\t\t\tctlproc(pid, "start");
 \t\t\tbreak;
+\t\t}\
 \t\tprintpc(ureg.ip, ureg.sp);
+\t\tctlproc(pid, "start");
 \t\tnanosleep(&req, NULL);
 	}
 }

コアとなるコードの解説

上記の差分に基づいて、コアとなるコードの変更とその意図を詳細に解説します。

  1. total_sec の初期値変更:

    -int total_sec = 10;
    +int total_sec = 0;
    

    total_sec はプロファイリングの総実行時間を秒単位で設定する変数です。この変更により、デフォルトのプロファイリング時間が10秒から0秒に設定されました。これは、ユーザーが明示的に時間制限を指定しない限り、プロファイリングが時間制限なしで実行されることを意味します。これにより、プロファイリングの柔軟性が向上し、長時間の監視や特定のイベントベースのプロファイリングが可能になります。

  2. sample 関数からの ctlproc 呼び出しの削除:

    @@ -94,7 +94,6 @@ sample(void)
     	static int n;
     
     	n++;
    -\tctlproc(pid, "stop");
     	for(i = 0; i < sizeof ureg; i+=8) {
     	// ...
     	}
    -\tctlproc(pid, "start");
     	return 1;
     }
    

    sample 関数は、単一のサンプリング操作(レジスタ値の取得など)を実行します。変更前は、この関数内でプロファイリング対象プロセスの停止と再開 (ctlproc(pid, "stop")ctlproc(pid, "start")) が行われていました。この変更により、これらの ctlproc 呼び出しが sample 関数から削除されました。 この変更の意図は、関心の分離堅牢性の向上です。sample 関数は純粋にサンプリングデータの収集に責任を持ち、プロセスの制御はより上位の関数(samples 関数)に委ねられるべきです。これにより、sample 関数がよりシンプルになり、また、サンプリング中に発生しうる競合状態や、サンプリングが失敗した場合にプロセスが停止したままになるなどのバグのリスクが低減されます。

  3. samples 関数におけるループ条件と ctlproc の再配置:

    @@ -188,10 +186,14 @@ samples(void)
     
     	req.tv_sec = delta_msec/1000;
     	req.tv_nsec = 1000000*(delta_msec % 1000);
    -\tfor(msec = 0; msec < 1000*total_sec; msec += delta_msec) {
    -\t\tif(!sample())\
    +\tfor(msec = 0; total_sec <= 0 || msec < 1000*total_sec; msec += delta_msec) {
    +\t\tctlproc(pid, "stop");
    +\t\tif(!sample()) {
    +\t\t\tctlproc(pid, "start");
     \t\t\tbreak;
    +\t\t}\
     \t\tprintpc(ureg.ip, ureg.sp);
    +\t\tctlproc(pid, "start");
     \t\tnanosleep(&req, NULL);
     	}
     }
    
    • ループ条件の変更: for ループの条件が msec < 1000*total_sec から total_sec <= 0 || msec < 1000*total_sec に変更されました。 これは、total_sec が0以下の場合(つまり、時間制限が設定されていない場合)には、ループが msec < 1000*total_sec の条件に関わらず継続することを意味します。これにより、total_sec の初期値が0になったことと合わせて、デフォルトで時間制限なしのプロファイリングが可能になります。

    • ctlproc 呼び出しの移動とエラーハンドリング: sample 関数から削除された ctlproc 呼び出しが、samples 関数内のループに移動されました。

      • ctlproc(pid, "stop");: 各サンプリングの直前にプロファイリング対象プロセスを停止します。これにより、sample() 関数が実行される間、対象プロセスは静止した状態になり、より正確なデータ収集が可能になります。
      • if(!sample()) { ctlproc(pid, "start"); break; }: sample() 関数が失敗した場合(戻り値が0の場合)、プロファイリング対象プロセスを確実に再開 (ctlproc(pid, "start")) してからループを抜けます。これは、サンプリング中にエラーが発生しても、対象プロセスが停止したままになるという「start/stop bug」を修正するための重要な変更です。
      • ctlproc(pid, "start");: sample() 関数が成功し、データが処理された後、プロファイリング対象プロセスを再開します。

これらの変更により、プロファイリングの制御フローがより明確になり、エラー発生時の挙動も改善され、全体として prof ツールの信頼性と柔軟性が大幅に向上しています。

関連リンク

参考にした情報源リンク

  • コミット情報および差分: GitHub (golang/goリポジトリ)
  • 一般的なプロファイリング、OSのプロセス制御、C言語の基本に関する知識