[インデックス 14597] ファイルの概要
このコミットは、Go言語のプロファイリングツールであるpprof
スクリプトにおける2つの重要な問題を修正します。具体的には、HTTPS URLのサポートの欠如と、「profiler in use」エラーの不適切な処理に対処しています。これにより、pprof
がより堅牢になり、HTTPSを使用するアプリケーションのプロファイリングや、プロファイラが既にアクティブな場合のより適切なエラー報告が可能になります。
コミット
commit 9838774622ed18cf449a0e10b1946104e917408d
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 10 17:06:30 2012 -0500
pprof: fix https URLs and 'profiler in use' errors
Fixes #3666.
Fixes #3680.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/6899054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9838774622ed18cf449a0e10b1946104e917408d
元コミット内容
pprof: fix https URLs and 'profiler in use' errors
このコミットは、pprof
ツールがHTTPSプロトコルを使用するURLを正しく処理できない問題と、プロファイラが既に別のプロセスで使用されている場合に発生するエラーメッセージの処理に関する問題を修正します。
変更の背景
このコミットは、以下の2つのGoイシューを解決するために行われました。
-
Issue 3666:
pprof
does not support https URLs- Goの
net/http/pprof
パッケージは、HTTPとHTTPSの両方でプロファイリングエンドポイントを提供します。しかし、pprof
スクリプト(Perlで書かれている)は、プロファイルURLを解析する際にhttp://
をハードコードしており、HTTPSを使用するアプリケーションのプロファイルを正しく取得できませんでした。これにより、HTTPS経由でプロファイリングデータを提供しているGoアプリケーションに対してpprof
を使用しようとすると、接続エラーが発生していました。
- Goの
-
Issue 3680:
pprof
should report "profiler in use" errors- Goのプロファイラは、一度に1つのプロセスしかCPUプロファイリングを実行できません。もし既にプロファイリングが進行中のGoアプリケーションに対して
pprof
がCPUプロファイルを要求した場合、Goランタイムは「Could not enable CPU profiling: profiler already in use」というエラーメッセージを返します。しかし、pprof
スクリプトはこの特定のエラーメッセージを適切に処理せず、一般的な接続エラーとして扱ってしまうため、ユーザーが問題の原因を特定しにくい状況でした。このコミットは、この特定のエラーメッセージを捕捉し、より分かりやすいエラーとして報告するように改善します。
- Goのプロファイラは、一度に1つのプロセスしかCPUプロファイリングを実行できません。もし既にプロファイリングが進行中のGoアプリケーションに対して
これらの問題は、pprof
ツールの実用性とユーザーエクスペリエンスを向上させるために修正が必要でした。
前提知識の解説
-
Go言語の
pprof
ツール:pprof
は、Goプログラムのパフォーマンスプロファイルを視覚化・分析するためのツール群です。Goランタイムは、CPU使用率、メモリ割り当て(ヒープ)、ゴルーチン、ブロッキングプロファイルなど、様々な種類のプロファイルデータをHTTPエンドポイント(通常は/debug/pprof/
)経由で提供できます。go tool pprof
コマンドは、これらのエンドポイントからプロファイルデータを取得し、グラフやテキスト形式で分析結果を表示するために使用されます。このコミットで修正されているmisc/pprof
スクリプトは、go tool pprof
の内部で利用されるか、あるいはその前身となるような、プロファイルデータを取得・処理するためのPerlスクリプトです。
-
HTTP/HTTPS:
- HTTP (Hypertext Transfer Protocol) は、Web上でデータを転送するためのプロトコルです。
- HTTPS (Hypertext Transfer Protocol Secure) は、HTTPにSSL/TLS暗号化を追加したもので、セキュアな通信を提供します。WebアプリケーションがHTTPSを使用することは一般的であり、プロファイリングエンドポイントもHTTPSで提供されることがあります。
-
Perlスクリプト:
misc/pprof
ファイルはPerlで書かれたスクリプトです。Perlはテキスト処理に強く、システム管理やスクリプト作成によく用いられます。このスクリプトは、Goアプリケーションからプロファイルデータを取得するためにcurl
などの外部コマンドを呼び出し、その結果を処理しています。
-
正規表現 (Regex):
- テキストパターンを記述するための強力なツールです。このコミットでは、URLからスキーム(http/https)、ホスト、ポートなどを抽出するために正規表現が使用されています。
技術的詳細
このコミットの技術的な変更点は主に以下の2点です。
-
URLスキームの動的な取得と利用:
- 以前の
pprof
スクリプトは、プロファイルURLを解析する際に、常にhttp://
を前提としていました。これは、ParseProfileURL
関数がURLのスキーム部分を抽出せず、URLを構築する際にhttp://
をハードコードしていたためです。 - このコミットでは、
ParseProfileURL
関数の正規表現が修正され、URLの先頭にhttps?://
(http://
またはhttps://
)が存在する場合に、そのスキーム部分をキャプチャするように変更されました。- 変更前:
m,^(http://|)([^/:]+):(\d+)(|\\@\\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o)
- 変更後:
m,^(?:(https?)://|)([^/:]+):(\d+)(|\\@\\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o)
?:
は非キャプチャグループを示し、https?
はhttp
またはhttps
にマッチします。これにより、スキームがキャプチャグループ$1
に格納されるようになります。
- 変更前:
ParseProfileURL
の戻り値にスキームが追加され、このスキームがSymbolPageURL
,FetchProgramName
,FetchDynamicProfile
などのURLを構築する関数に渡されるようになりました。- これらの関数内で、ハードコードされていた
http://
が、動的に取得した$scheme://
に置き換えられました。これにより、pprof
はHTTPとHTTPSの両方のURLを透過的に処理できるようになります。
- 以前の
-
「profiler in use」エラーの明示的な検出と報告:
FetchDynamicProfile
関数内で、curl
コマンドでプロファイルデータを取得した後、一時ファイルの内容をチェックするロジックが追加されました。- 具体的には、取得したプロファイルデータが「
Could not enable CPU profiling
」で始まる行を含んでいる場合、それはプロファイラが既に別の場所で使用されていることを意味するため、スクリプトは即座にエラーを報告して終了するようになりました。 - これにより、ユーザーはより正確なエラーメッセージを受け取り、問題の原因(プロファイラが既にアクティブであること)を迅速に理解できるようになります。
これらの変更により、pprof
スクリプトはより柔軟でユーザーフレンドリーなツールとなりました。
コアとなるコードの変更箇所
misc/pprof
ファイル内の以下の関数が変更されています。
IsProfileURL
ParseProfileURL
SymbolPageURL
FetchProgramName
MakeProfileBaseName
FetchDynamicProfile
特に重要な変更はParseProfileURL
の正規表現と、FetchDynamicProfile
におけるエラーチェックの追加です。
--- a/misc/pprof
+++ b/misc/pprof
@@ -2982,32 +2982,32 @@ print STDERR "Read $url\n";
sub IsProfileURL {
my $profile_name = shift;
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
return defined($host) and defined($port) and defined($path);
}
sub ParseProfileURL {
my $profile_name = shift;
if (defined($profile_name) &&
- $profile_name =~ m,^(http://|)([^/:]+):(\d+)(|\\@\\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o) {
+ $profile_name =~ m,^(?:(https?)://|)([^/:]+):(\d+)(|\\@\\d+)(|/|(.*?)($PROFILE_PAGE|$PMUPROFILE_PAGE|$HEAP_PAGE|$GROWTH_PAGE|$THREAD_PAGE|$CONTENTION_PAGE|$WALL_PAGE|$FILTEREDPROFILE_PAGE))$,o) {
# $7 is $PROFILE_PAGE/$HEAP_PAGE/etc. $5 is *everything* after
# the hostname, as long as that everything is the empty string,
# a slash, or something ending in $PROFILE_PAGE/$HEAP_PAGE/etc.
# So "$7 || $5" is $PROFILE_PAGE/etc if there, or else it's "/" or "".
- return ($2, $3, $6, $7 || $5);
+ return ($1 || "http", $2, $3, $6, $7 || $5);
}
return ();
}
# We fetch symbols from the first profile argument.
sub SymbolPageURL {
- my ($host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
- return "http://$host:$port$prefix$SYMBOL_PAGE";
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
+ return "$scheme://$host:$port$prefix$SYMBOL_PAGE";
}
sub FetchProgramName() {
- my ($host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
- my $url = "http://$host:$port$prefix$PROGRAM_NAME_PAGE";
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($main::pfile_args[0]);
+ my $url = "$scheme://$host:$port$prefix$PROGRAM_NAME_PAGE";
my $command_line = "$CURL -s '$url'";
open(CMDLINE, "$command_line |") or error($command_line);
my $cmdline = <CMDLINE>;
@@ -3139,7 +3139,7 @@ sub BaseName {
sub MakeProfileBaseName {
my ($binary_name, $profile_name) = @_;
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
my $binary_shortname = BaseName($binary_name);
return sprintf("%s.%s.%s-port%s",
$binary_shortname, $main::op_time, $host, $port);
@@ -3154,7 +3154,7 @@ sub FetchDynamicProfile {
if (!IsProfileURL($profile_name)) {
return $profile_name;
} else {
- my ($host, $port, $prefix, $path) = ParseProfileURL($profile_name);
+ my ($scheme, $host, $port, $prefix, $path) = ParseProfileURL($profile_name);
if ($path eq "" || $path eq "/") {
# Missing type specifier defaults to cpu-profile
$path = $PROFILE_PAGE;
@@ -3166,7 +3166,7 @@ sub FetchDynamicProfile {
my $curl_timeout;
if (($path =~ m/$PROFILE_PAGE/) || ($path =~ m/$PMUPROFILE_PAGE/)) {
if ($path =~ m/$PROFILE_PAGE/) {
- $url = sprintf("http://$host:$port$prefix$path?seconds=%d",
+ $url = sprintf("$scheme://$host:$port$prefix$path?seconds=%d",
$main::opt_seconds);
} else {
if ($profile_name =~ m/[?]/) {
@@ -3174,7 +3174,7 @@ sub FetchDynamicProfile {
} else {
$profile_name .= "?"
}\n- $url = sprintf("http://$profile_name" . "seconds=%d",
+ $url = sprintf("$scheme://$profile_name" . "seconds=%d",
$main::opt_seconds);
}\n $curl_timeout = sprintf("--max-time %d",
@@ -3185,7 +3185,7 @@ sub FetchDynamicProfile {
my $suffix = $path;
$suffix =~ s,/,.,g;
$profile_file .= "$suffix";
- $url = "http://$host:$port$prefix$path";
+ $url = "$scheme://$host:$port$prefix$path";
$curl_timeout = "";
}\n
@@ -3212,6 +3212,10 @@ sub FetchDynamicProfile {
}\n
(system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n");
+ open(TMPPROF, "$tmp_profile") || error("Cannot open $tmp_profile: $!\n");
+ my $line = <TMPPROF>;
+ close(TMPPROF);\n+ $line !~ /^Could not enable CPU profiling/ || error($line);\n (system("mv $tmp_profile $real_profile") == 0) || error("Unable to rename profile\n");
print STDERR "Wrote profile to $real_profile\n";
$main::collected_profile = $real_profile;\n
コアとなるコードの解説
-
ParseProfileURL
関数の変更:- 正規表現の
^(http://|)
が^(?:(https?)://|)
に変更されました。?:
は非キャプチャグループであり、https?
はhttp
またはhttps
にマッチします。これにより、プロトコルスキーム(http
またはhttps
)が最初のキャプチャグループ$1
に格納されます。
- 戻り値の行が
return ($2, $3, $6, $7 || $5);
からreturn ($1 || "http", $2, $3, $6, $7 || $5);
に変更されました。$1 || "http"
は、もしスキームがキャプチャされなかった場合(例えば、URLがlocalhost:6060
のようにスキームなしで指定された場合)にデフォルトでhttp
を使用することを意味します。これにより、ParseProfileURL
は常にスキームを返すようになります。
- 正規表現の
-
URL構築箇所の変更:
SymbolPageURL
,FetchProgramName
,FetchDynamicProfile
内のURL構築ロジックで、ハードコードされていたhttp://
が、ParseProfileURL
から取得した$scheme
変数を使用するように変更されました。- 例:
return "http://$host:$port$prefix$SYMBOL_PAGE";
がreturn "$scheme://$host:$port$prefix$SYMBOL_PAGE";
に変更。 - これにより、
pprof
は指定されたURLのスキーム(HTTPまたはHTTPS)を尊重してプロファイルデータを取得できるようになります。
- 例:
-
FetchDynamicProfile
におけるエラーハンドリングの追加:- プロファイルデータを一時ファイルに保存した後、そのファイルを開き、最初の行を読み込む処理が追加されました。
$line !~ /^Could not enable CPU profiling/ || error($line);
- この行は、読み込んだ
$line
が正規表現^Could not enable CPU profiling
にマッチしない場合に真となり、マッチした場合はerror($line)
が実行されます。 - つまり、もしGoアプリケーションが「Could not enable CPU profiling」というエラーメッセージを返した場合、
pprof
スクリプトはそのメッセージをそのままエラーとして出力し、処理を中断します。これにより、ユーザーはプロファイラが既にアクティブであるという明確な情報を得ることができます。
- この行は、読み込んだ
これらの変更は、pprof
スクリプトの堅牢性と使いやすさを大幅に向上させています。
関連リンク
- Go言語のプロファイリングに関する公式ドキュメント: https://go.dev/doc/diagnostics#profiling
net/http/pprof
パッケージのドキュメント: https://pkg.go.dev/net/http/pprof
参考にした情報源リンク
- Go Issue 3666:
pprof
does not support https URLs: https://github.com/golang/go/issues/3666 - Go Issue 3680:
pprof
should report "profiler in use" errors: https://github.com/golang/go/issues/3680 - Go CL 6899054:
pprof
: fix https URLs and 'profiler in use' errors: https://go.dev/cl/6899054 - Perl正規表現のドキュメント: https://perldoc.perl.org/perlre (一般的なPerl正規表現の知識)
curl
コマンドのドキュメント: https://curl.se/docs/manpage.html (pprof
スクリプトが内部でcurl
を使用しているため)