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

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

このコミットは、Go言語のプロファイリングツールであるruntime/pprofパッケージにおけるテストの調整と、特定のオペレーティングシステム(OS X、NetBSD、OpenBSD)でのプロファイリングの不正確さに関するバグ情報の更新を目的としています。特に、CPUプロファイルのテストが特定の環境で不安定であった問題に対処し、テストの閾値を緩和することで、より広範な環境でのテストの安定性を確保しています。

コミット

commit 8dc7a31d7796d379d36729f5209d93a898471eac
Author: Russ Cox <rsc@golang.org>
Date:   Tue Aug 6 14:49:55 2013 -0400

    runtime/pprof: adjust test
    
    NetBSD and OpenBSD are broken like OS X is. Good to know.
    
    Drop required count from avg/2 to avg/3, because the
    Plan 9 builder just barely missed avg/2 in one of its runs.
    
    R=golang-dev, dvyukov
    CC=golang-dev
    https://golang.org/cl/12548043

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

https://github.com/golang/go/commit/8dc7a31d7796d379d36729f5209d93a898471eac

元コミット内容

    runtime/pprof: adjust test
    
    NetBSD and OpenBSD are broken like OS X is. Good to know.
    
    Drop required count from avg/2 to avg/3, because the
    Plan 9 builder just barely missed avg/2 in one of its runs.
    
    R=golang-dev, dvyukov
    CC=golang-dev
    https://golang.org/cl/12548043

変更の背景

このコミットの背景には、Go言語のプロファイリングツールであるruntime/pprofが、特定のオペレーティングシステム(特にOS X、NetBSD、OpenBSD)上でCPUプロファイルを正確に取得できないという既知の問題(golang.org/issue/6047)が存在します。この問題は、OSがプロファイリングシグナルを誤ったスレッドに配信するために発生します。

元々、runtime/pprofのテストはOS Xでの失敗を無視するように設定されていましたが、NetBSDとOpenBSDでも同様の問題が確認されたため、これらのOSもテストのスキップ対象に追加する必要がありました。

また、CPUプロファイルのテストにおいて、収集されるサンプル数の最小要件が厳しすぎたため、Plan 9のビルド環境でテストが不安定になるという問題も発生していました。具体的には、平均サンプル数の半分(avg/2)という閾値が、Plan 9環境ではギリギリ満たせないケースがあったため、この閾値を平均の3分の1(avg/3)に緩和することで、テストの安定性を向上させる必要がありました。

このコミットは、これらの問題を解決し、runtime/pprofのテストがより堅牢で、様々な環境で安定して動作するように調整することを目的としています。

前提知識の解説

  • Go言語のプロファイリング (pprof): Go言語には、プログラムのパフォーマンス特性を分析するためのプロファイリングツールキットpprofが標準で提供されています。CPU使用率、メモリ割り当て、ゴルーチン、ブロック、ミューテックス競合など、様々な種類のプロファイルを収集し、視覚化することができます。CPUプロファイルは、プログラムがCPU時間をどこで消費しているかを特定するのに役立ちます。
  • CPUプロファイリングの仕組み: CPUプロファイリングは通常、一定の間隔(例えば、100Hz)でプログラムの実行を一時停止し、その時点でのコールスタック(関数呼び出しの履歴)を記録することで行われます。これにより、どの関数がCPU時間を多く消費しているかの統計的な情報が得られます。
  • プロファイリングシグナル: OSレベルでは、プロファイリングはシグナル(例えば、Unix系のSIGPROF)を使用して実装されることが多いです。このシグナルが定期的にプロセスに送信され、シグナルハンドラがコールスタックを収集します。
  • マルチスレッド環境でのプロファイリングの課題: マルチスレッド環境では、プロファイリングシグナルがどのスレッドに配信されるかが重要になります。もしOSがシグナルを誤ったスレッドに配信したり、プロファイリング対象のスレッドがシグナルを受け取れない状態にあったりすると、正確なプロファイルが収集できません。golang.org/issue/6047で言及されている問題は、まさにこのシグナル配信の不正確さに起因しています。
  • runtime.GOOS: Go言語の標準ライブラリruntimeパッケージには、現在のオペレーティングシステムを示す定数GOOSが定義されています。例えば、darwinはmacOS、linuxはLinux、netbsdはNetBSD、openbsdはOpenBSDを示します。この定数を利用することで、OSに依存するコードの分岐を行うことができます。
  • テストの閾値: ソフトウェアテストにおいて、特定の条件が満たされているかを判断するために設定される基準値です。このコミットでは、CPUプロファイルで収集されるサンプル数が、期待される最小値を満たしているかどうかの閾値が調整されています。

技術的詳細

このコミットは、主にsrc/pkg/runtime/pprof/pprof.gosrc/pkg/runtime/pprof/pprof_test.goの2つのファイルに変更を加えています。

  1. src/pkg/runtime/pprof/pprof.goの変更:

    • 既存のBUGコメントが更新されました。以前はOS Xのみが不正確なプロファイルの問題を抱えていると記載されていましたが、NetBSDとOpenBSDも同様の問題を抱えていることが確認されたため、これらのOSも追記されました。
    • これはコードの動作には影響しませんが、ドキュメントとして重要な更新であり、ユーザーや開発者に対して既知の制限を明確に伝えます。
  2. src/pkg/runtime/pprof/pprof_test.goの変更:

    • testCPUProfile関数内で、プロファイルが短すぎる場合の処理が変更されました。
      • 以前はOS Xでのみ失敗を無視していましたが、badOSマップを導入し、darwin(OS X)、netbsdopenbsdのいずれかのOSであれば、テストをスキップするように変更されました。これにより、これらのOS上でのテストの不安定性を回避し、CI/CDパイプラインの安定化に貢献します。t.Skipfを使用することで、テストがスキップされた理由が明確にログに出力されます。
      • それ以外のOSでプロファイルが短すぎる場合は、以前と同様にt.FailNow()でテストを即座に失敗させます。
    • CPUプロファイルのサンプル数に関する最小要件が緩和されました。
      • min := total / uintptr(len(have)) / 2という計算式が、min := total / uintptr(len(have)) / 3に変更されました。これは、各関数が収集すべき最小サンプル数を、総サンプル数を関数の数で割った平均値の半分から、平均値の3分の1に引き下げることを意味します。
      • この変更は、Plan 9ビルド環境でのテストの不安定性に対処するためのものです。Plan 9環境では、プロファイリングの精度が他の環境とわずかに異なり、以前の厳しすぎる閾値ではテストが時折失敗していました。閾値を緩和することで、このような環境でもテストが安定してパスするようになります。
    • badOSというグローバルマップが新しく定義されました。
      • このマップは、キーとしてOS名(文字列)、値としてブール値(true)を持ち、プロファイリングテストが失敗することが予想されるOSをリストアップします。これにより、コード内で特定のOSをハードコードするのではなく、一箇所で管理できるようになり、可読性と保守性が向上します。

これらの変更により、runtime/pprofのテストは、特定のOSでの既知のプロファイリング問題による影響を受けにくくなり、より堅牢で信頼性の高いものになりました。また、Plan 9のような特定のビルド環境でのテストの不安定性も解消されています。

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

diff --git a/src/pkg/runtime/pprof/pprof.go b/src/pkg/runtime/pprof/pprof.go
index 5c1f3d460d..e7eb66a557 100644
--- a/src/pkg/runtime/pprof/pprof.go
+++ b/src/pkg/runtime/pprof/pprof.go
@@ -20,7 +20,8 @@ import (
 	"text/tabwriter"
 )
 
-// BUG(rsc): Profiles are incomplete and inaccuate on OS X. See http://golang.org/issue/6047 for details.
+// BUG(rsc): Profiles are incomplete and inaccuate on NetBSD, OpenBSD, and OS X.
+// See http://golang.org/issue/6047 for details.
 
 // A Profile is a collection of stack traces showing the call sequences
 // that led to instances of a particular event, such as allocation.
diff --git a/src/pkg/runtime/pprof/pprof_test.go b/src/pkg/runtime/pprof/pprof_test.go
index 995c2fe68d..040d77a434 100644
--- a/src/pkg/runtime/pprof/pprof_test.go
+++ b/src/pkg/runtime/pprof/pprof_test.go
@@ -78,11 +78,12 @@ func testCPUProfile(t *testing.T, need []string, f func()) {
 	val = val[:l]
 
 	if l < 13 {
-		if runtime.GOOS == "darwin" {
-			t.Logf("ignoring failure on OS X; see golang.org/issue/6047")
+		t.Logf("profile too short: %#x", val)
+		if badOS[runtime.GOOS] {
+			t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS)
 			return
 		}
-		t.Fatalf("profile too short: %#x", val)
+		t.FailNow()
 	}
 
 	hd, val, tl := val[:5], val[5:l-3], val[l-3:]
@@ -124,7 +125,7 @@ func testCPUProfile(t *testing.T, need []string, f func()) {
 		t.Logf("no CPU profile samples collected")
 		ok = false
 	}
-	min := total / uintptr(len(have)) / 2
+	min := total / uintptr(len(have)) / 3
 	for i, name := range need {
 		if have[i] < min {
 			t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
@@ -133,10 +134,17 @@ func testCPUProfile(t *testing.T, need []string, f func()) {
 	}
 
 	if !ok {
-		if runtime.GOOS == "darwin" {
-			t.Logf("ignoring failure on OS X; see golang.org/issue/6047")
+		if badOS[runtime.GOOS] {
+			t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS)
 			return
 		}
 		t.FailNow()
 	}
 }
+
+// Operating systems that are expected to fail the tests. See issue 6047.
+var badOS = map[string]bool{
+	"darwin":  true,
+	"netbsd":  true,
+	"openbsd": true,
+}

コアとなるコードの解説

src/pkg/runtime/pprof/pprof.go

// BUG(rsc): Profiles are incomplete and inaccuate on NetBSD, OpenBSD, and OS X.
// See http://golang.org/issue/6047 for details.

この変更は、pprofパッケージのドキュメンテーションコメントを更新し、CPUプロファイルが不完全または不正確になる既知のバグが、OS XだけでなくNetBSDとOpenBSDでも発生することを明記しています。これは、golang.org/issue/6047で追跡されている問題に関連しており、ユーザーや開発者に対して、これらのOS上でのプロファイリング結果の信頼性に関する注意喚起を促します。

src/pkg/runtime/pprof/pprof_test.go

testCPUProfile関数の変更点

	if l < 13 {
		t.Logf("profile too short: %#x", val)
		if badOS[runtime.GOOS] {
			t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS)
			return
		}
		t.FailNow()
	}

このブロックは、収集されたプロファイルの長さ(l)が短すぎる場合に実行されます。

  • t.Logf("profile too short: %#x", val): プロファイルが短すぎることをログに出力します。
  • if badOS[runtime.GOOS]: 新しく導入されたbadOSマップを使用して、現在のOSがプロファイリングテストで失敗することが予想されるOS(darwin, netbsd, openbsd)のいずれかであるかをチェックします。
  • t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS): もし現在のOSがbadOSマップに含まれていれば、テストをスキップします。これにより、既知のOS固有のプロファイリング問題によってテストが失敗するのを防ぎ、CI/CDの安定性を向上させます。t.Skipfは、テストがスキップされた理由を詳細にログに出力します。
  • return: テストをスキップした後、関数の実行を終了します。
  • t.FailNow(): badOSに含まれないOSでプロファイルが短すぎる場合は、テストを即座に失敗させます。これは、予期せぬプロファイリングの問題が発生したことを示します。
	min := total / uintptr(len(have)) / 3

この行は、各関数が収集すべき最小サンプル数を計算するロジックを変更しています。

  • 以前はtotal / uintptr(len(have)) / 2(総サンプル数を関数の数で割った平均値の半分)でした。
  • 変更後はtotal / uintptr(len(have)) / 3(総サンプル数を関数の数で割った平均値の3分の1)になりました。 この変更は、Plan 9ビルド環境でのテストの不安定性に対処するためのものです。プロファイリングの性質上、サンプル数にはある程度のばらつきがあるため、閾値を緩和することで、テストがより堅牢になり、特定の環境での偶発的な失敗を防ぎます。
	if !ok {
		if badOS[runtime.GOOS] {
			t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS)
			return
		}
		t.FailNow()
	}

このブロックは、プロファイルが期待通りに収集されなかった場合(okfalseの場合)に実行されます。

  • ここでもbadOSマップが使用され、既知の問題を抱えるOSであればテストをスキップします。
  • それ以外のOSであれば、t.FailNow()でテストを失敗させます。このロジックは、プロファイルの長さが短すぎる場合の処理と同様の目的を持っています。

badOSマップの定義

// Operating systems that are expected to fail the tests. See issue 6047.
var badOS = map[string]bool{
	"darwin":  true,
	"netbsd":  true,
	"openbsd": true,
}

この新しいグローバル変数badOSは、プロファイリングテストが失敗することが予想されるオペレーティングシステムをリストアップするマップです。

  • キーはruntime.GOOSで返されるOS名(例: "darwin", "netbsd", "openbsd")です。
  • 値はtrueで、そのOSが「悪いOS」であることを示します。 このマップを導入することで、テストコード内で特定のOS名をハードコードする代わりに、一元的に管理できるようになり、コードの可読性と保守性が向上します。また、将来的に同様の問題を抱えるOSが追加された場合でも、このマップを更新するだけで対応できます。

関連リンク

参考にした情報源リンク