[インデックス 17402] ファイルの概要
このコミットは、Go言語のプロファイリングツールであるpprof
に関連するPerlスクリプトmisc/pprof
に対する変更です。misc/pprof
は、Goプログラムから生成されたプロファイリングデータを取得し、解析し、可視化するためのクライアントサイドツールとして機能します。具体的には、HTTPエンドポイントを通じて提供されるプロファイル(CPU、メモリなど)をフェッチし、グラフ生成ツール(Graphvizなど)と連携して視覚的なレポートを作成します。
コミット
このコミットは、pprof
のHTTPプロファイリング機能がWindows/amd64環境で正しく動作しない問題を修正することを目的としています。主な変更点として、一時ファイルの生成におけるPOSIX依存の仮定の削除、curl
コマンドへの依存の排除、およびSVGファイルのオープン方法の変更が含まれています。これにより、Windows環境を含む様々なプラットフォームで、go tool pprof
コマンドがHTTP経由でプロファイルを取得し、シンボル解決を行う機能が安定して動作するようになります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/daed1fee8b8fb7e899fa137e5df62df01bd1c18d
元コミット内容
misc/pprof: pprof http used with net/http/pprof not working on windows/amd64
Removed posix assumptions in temporary file generation
Removed curl dependence
Changed opening of svg file
These must now work including symbol resolution.
[1] go tool pprof <prog_name> http://.../debug/pprof/profile
[2] go tool pprof http://.../debug/pprof/profile
Fixes 6177.
R=golang-dev, alex.brainman, bradfitz, kamil.kisiel
CC=golang-dev
https://golang.org/cl/13085043
変更の背景
Go言語のpprof
ツールは、Goプログラムのパフォーマンス特性を分析するための非常に強力なツールです。net/http/pprof
パッケージを使用すると、実行中のGoアプリケーションがHTTPエンドポイントを通じてプロファイリングデータを提供できるようになります。しかし、このコミットがなされる以前は、misc/pprof
スクリプトがWindows/amd64環境でこれらのHTTPエンドポイントからプロファイルを取得する際に問題が発生していました。
この問題の根本原因は、スクリプトが一時ファイルの生成やネットワーク通信において、UNIX/Linux系のシステム(POSIX準拠システム)に特有の仮定(例: /dev/null
の存在、mv
コマンドの挙動、curl
コマンドの利用可能性と挙動)に依存していたことにあります。Windows環境ではこれらの仮定が成り立たないため、プロファイルの取得や処理が失敗していました。特に、curl
コマンドはWindowsに標準でインストールされているとは限らず、またその挙動がUNIX系システムと異なる場合がありました。このコミットは、これらのプラットフォーム固有の依存関係を解消し、pprof
ツールがWindows環境でも信頼性高く動作するようにすることを目的としています。
前提知識の解説
Go pprof
pprof
は、Go言語の標準的なプロファイリングツールです。CPU使用率、メモリ割り当て、ゴルーチン、ミューテックス競合、ブロック操作など、様々な種類のプロファイルデータを収集し、分析することができます。開発者はpprof
を使用して、アプリケーションのパフォーマンスボトルネックを特定し、最適化を行うことができます。
net/http/pprof
Goの標準ライブラリに含まれるnet/http/pprof
パッケージは、HTTPサーバーを介してpprof
プロファイルデータを公開するための機能を提供します。これにより、実行中のGoアプリケーションにHTTPリクエストを送るだけで、プロファイルデータを動的に取得することが可能になります。通常、/debug/pprof/
以下のパス(例: /debug/pprof/profile
でCPUプロファイル、/debug/pprof/heap
でヒーププロファイル)でアクセスできます。
Perlスクリプト misc/pprof
このコミットで変更されているmisc/pprof
は、Go言語のソースリポジトリに含まれるPerlスクリプトです。これは、go tool pprof
コマンドのバックエンドとして機能し、Goアプリケーションからプロファイルデータをフェッチし、Graphvizなどの外部ツールと連携してプロファイルデータを視覚化する役割を担っています。このスクリプトは、プロファイルデータの取得、シンボル解決、そして最終的なグラフ生成までの一連の処理を自動化します。
POSIXの仮定
POSIX (Portable Operating System Interface) は、UNIX系オペレーティングシステムの標準化されたインターフェース群です。多くのUNIX/LinuxシステムはPOSIXに準拠しており、/dev/null
のような特殊ファイルや、mv
のような標準コマンドの特定の挙動が保証されています。しかし、Windowsのような非POSIXシステムでは、これらの仮定が成り立たないため、POSIXに依存したスクリプトは互換性の問題を引き起こす可能性があります。
curl
curl
は、URL構文を使ってデータを転送するためのコマンドラインツールおよびライブラリです。HTTP、HTTPS、FTPなど、多くのプロトコルをサポートしています。misc/pprof
スクリプトでは、以前はHTTPエンドポイントからプロファイルデータを取得するためにcurl
コマンドを外部プロセスとして呼び出していました。
LWP::UserAgent
(Perlモジュール)
LWP::UserAgent
は、Perlのlibwww-perl
ライブラリの一部であり、HTTPリクエストをプログラム的に行うためのモジュールです。curl
のような外部コマンドを呼び出す代わりに、Perlスクリプト内で直接HTTP通信を処理できるため、より細かな制御が可能になり、プラットフォーム間の互換性も高まります。
File::Temp
(Perlモジュール)
File::Temp
は、Perlで一時ファイルを安全かつポータブルに作成するためのモジュールです。一時ファイルの命名規則や保存場所に関するOS固有の差異を吸収し、競合状態やセキュリティリスクを低減します。
File::Copy
(Perlモジュール)
File::Copy
は、Perlでファイルをコピーまたは移動するためのモジュールです。OSのファイルシステム操作を抽象化し、mv
コマンドのような外部コマンドに依存することなく、クロスプラットフォームでファイル操作を実行できます。
技術的詳細
このコミットの技術的詳細は、misc/pprof
スクリプトがWindows環境で直面していた具体的な問題を、Perlの標準モジュールを活用して解決した点にあります。
-
curl
依存の排除とLWP::UserAgent
への移行:- 以前のスクリプトは、HTTP経由でプロファイルデータやシンボル情報を取得するために、
curl
コマンドをサブプロセスとして実行していました。これは、open(CMDLINE, "$command_line |")
のような形式で実装されていました。 - Windows環境では
curl
が標準で利用できない場合があること、またその挙動がUNIX系システムと異なる(特にリダイレクト処理など)ことが問題でした。 - このコミットでは、
curl
への依存を完全に排除し、PerlのLWP::UserAgent
モジュールを使用するように変更されました。LWP::UserAgent
は、HTTPリクエストをPerlスクリプト内で直接処理するため、外部コマンドの有無や挙動に左右されず、より堅牢でクロスプラットフォームなネットワーク通信を実現します。 - 特に、以前
curl
の-L (--location)
オプションの信頼性の低さを回避するために実装されていたResolveRedirectionForCurl
サブルーチンが削除されました。これは、LWP::UserAgent
がリダイレクトを適切に処理できるため不要になったことを示しています。
- 以前のスクリプトは、HTTP経由でプロファイルデータやシンボル情報を取得するために、
-
一時ファイル生成におけるPOSIX仮定の排除:
- スクリプトはプロファイルデータを一時ファイルに保存し、その後処理していました。以前の実装では、一時ファイルのパスや移動にUNIX系の慣習(例:
$ENV{HOME} . "/pprof"
のようなディレクトリ構造、mv
コマンド)が使われていました。 - このコミットでは、一時ファイルの生成に
File::Temp->new()->filename
を使用するようになりました。これにより、OSが推奨する一時ディレクトリに、安全かつ一意な名前で一時ファイルが作成されるため、Windowsを含む様々なOSで互換性が確保されます。 - また、一時ファイルを最終的なプロファイルファイルに移動する際に、以前は
system("mv $tmp_profile $real_profile")
のようにmv
コマンドを呼び出していましたが、これをPerlのFile::Copy::copy($tmp_profile, $real_profile)
に置き換えました。これにより、mv
コマンドが利用できない、または挙動が異なる環境でもファイル移動が確実に行われるようになりました。
- スクリプトはプロファイルデータを一時ファイルに保存し、その後処理していました。以前の実装では、一時ファイルのパスや移動にUNIX系の慣習(例:
-
シンボル解決の改善:
- プロファイルデータにはメモリアドレスが含まれており、これを人間が読める関数名などに変換する「シンボル解決」が必要です。この処理もHTTP経由で行われるため、
curl
依存の排除とLWP::UserAgent
への移行がシンボル解決の安定性向上に寄与しています。 - 特に、POSTリクエストでシンボル情報を送信する際も
LWP::UserAgent
が使用され、より信頼性の高い通信が実現されています。
- プロファイルデータにはメモリアドレスが含まれており、これを人間が読める関数名などに変換する「シンボル解決」が必要です。この処理もHTTP経由で行われるため、
-
クリーンアップ処理の改善:
cleanup
サブルーチンにunlink($main::collected_profile)
が追加されました。これにより、収集されたプロファイルファイルも一時ファイルとして適切に削除されるようになり、リソース管理が改善されました。
これらの変更により、misc/pprof
スクリプトは、Windows環境でもnet/http/pprof
が提供するHTTPプロファイリングエンドポイントからプロファイルデータを確実に取得し、処理できるようになりました。
コアとなるコードの変更箇所
misc/pprof
ファイルにおける主要な変更点は以下の通りです。
--- a/misc/pprof
+++ b/misc/pprof
@@ -79,6 +79,8 @@ use strict;
use warnings;
use Getopt::Long;
use File::Temp;
+use LWP::UserAgent;
+use File::Copy;
my $PPROF_VERSION = "1.5";
@@ -104,7 +106,6 @@ my $GV = "gv";
my $KCACHEGRIND = "kcachegrind";
my $PS2PDF = "ps2pdf";
# These are used for dynamic profiles
-my $CURL = "curl";
# These are the web pages that servers need to support for dynamic profiles
my $HEAP_PAGE = "/pprof/heap";
@@ -2977,10 +2978,13 @@ sub IsSymbolizedProfileFile {
sub CheckSymbolPage {
my $url = SymbolPageURL();
print STDERR "Read $url\n";
- open(SYMBOL, "$CURL -s '$url' |");
- my $line = <SYMBOL>;
+
+ my $ua = LWP::UserAgent->new;
+ my $response = $ua->get($url);
+ error("Failed to get symbol page from $url\n") unless $response->is_success;
+
+ my $line = $response->content;
$line =~ s/\r//g; # turn windows-looking lines into unix-looking lines
- close(SYMBOL);
unless (defined($line)) {
error("$url doesn't exist\n");
}
@@ -3022,35 +3026,20 @@ sub SymbolPageURL {
sub FetchProgramName() {
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>;
+
+ my $ua = LWP::UserAgent->new;
+ my $response = $ua->get($url);
+ error("Failed to get program name from $url\n") unless $response->is_success;
+ my $cmdline = $response->content;
+
+ $cmdline =~ s/\n.*//s;
$cmdline =~ s/\r//g; # turn windows-looking lines into unix-looking lines
- close(CMDLINE);
error("Failed to get program name from $url\n") unless defined($cmdline);
$cmdline =~ s/\x00.+//; # Remove argv[1] and latters.
$cmdline =~ s!\n!!g; # Remove LFs.
return $cmdline;
}
-# Gee, curl's -L (--location) option isn't reliable at least
-# with its 7.12.3 version. Curl will forget to post data if
-# there is a redirection. This function is a workaround for
-# curl. Redirection happens on borg hosts.
-sub ResolveRedirectionForCurl {
- my $url = shift;
- my $command_line = "$CURL -s --head '$url'";
- open(CMDLINE, "$command_line |") or error($command_line);
- while (<CMDLINE>) {
- s/\r//g; # turn windows-looking lines into unix-looking lines
- if (/^Location: (.*)/) {
- $url = $1;
- }
- }
- close(CMDLINE);
- return $url;
-}
-
# Reads a symbol map from the file handle name given as $1, returning
# the resulting symbol map. Also processes variables relating to symbols.
# Currently, the only variable processed is 'binary=<value>' which updates
@@ -3107,9 +3096,17 @@ sub FetchSymbols {
close(POSTFILE);
my $url = SymbolPageURL();
- $url = ResolveRedirectionForCurl($url);
- my $command_line = "$CURL -sd '\@$main::tmpfile_sym' '$url'";
- open(SYMBOL, "$command_line |") or error($command_line);\n
+ my $req = HTTP::Request->new(POST => $url);
+ $req->content($post_data);
+ my $lwp = LWP::UserAgent->new;
+ my $response = $lwp->request($req);
+
+ my $tmp_symbol = File::Temp->new()->filename;
+ open(SYMBOL, ">$tmp_symbol");
+ print SYMBOL $response->content;
+ close(SYMBOL);
+
+ open(SYMBOL, "<$tmp_symbol") || error("$tmp_symbol");
ReadSymbols(*SYMBOL{IO}, $symbol_map);
close(SYMBOL);
}\n
@@ -3175,7 +3172,7 @@ sub FetchDynamicProfile {
my $profile_file = MakeProfileBaseName($binary_name, $profile_name);
my $url;
- my $curl_timeout;
+ my $timeout;
if (($path =~ m/$PROFILE_PAGE/) || ($path =~ m/$PMUPROFILE_PAGE/)) {
if ($path =~ m/$PROFILE_PAGE/) {
$url = sprintf("$scheme://$host:$port$prefix$path?seconds=%d",
@@ -3189,22 +3186,16 @@ sub FetchDynamicProfile {
$url = sprintf("$scheme://$profile_name" . "seconds=%d",
$main::opt_seconds);
}\n
- $curl_timeout = sprintf("--max-time %d",
+ $timeout = sprintf("%d",
int($main::opt_seconds * 1.01 + 60));
} else {
# For non-CPU profiles, we add a type-extension to
@@ -3198,22 +3195,16 @@ sub FetchDynamicProfile {
$suffix =~ s,/,.,g;
$profile_file .= "$suffix";
$url = "$scheme://$host:$port$prefix$path";
- $curl_timeout = "";
+ $timeout = "";
}\n
- my $profile_dir = $ENV{"PPROF_TMPDIR"} || ($ENV{HOME} . "/pprof");
- if (!(-d $profile_dir)) {
- mkdir($profile_dir)
- || die("Unable to create profile directory $profile_dir: $!\n");
- }\n
- my $tmp_profile = "$profile_dir/.tmp.$profile_file";
- my $real_profile = "$profile_dir/$profile_file";
+ my $tmp_profile = File::Temp->new()->filename;
+ my $real_profile = File::Temp->new()->filename;
if ($fetch_name_only > 0) {
return $real_profile;
}\n
- my $cmd = "$CURL $curl_timeout -s -o $tmp_profile '$url'";
if (($path =~ m/$PROFILE_PAGE/) || ($path =~ m/$PMUPROFILE_PAGE/)){
print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n";
if ($encourage_patience) {
@@ -3223,12 +3214,21 @@ sub FetchDynamicProfile {
print STDERR "Fetching $path profile from $host:$port to\n ${real_profile}\n";
}\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);
+ my $ua = LWP::UserAgent->new;
+ $ua->timeout($timeout);
+ my $response = $ua->get($url);
+\n
+ error("Failed to get profile: $url $timeout!\n") unless $response->is_success;
+
+ open(OUTFILE, ">$tmp_profile");
+ binmode(OUTFILE);
+ print OUTFILE $response->content;
+ close(OUTFILE);
+
+ my $line = $response->content;
$line !~ /^Could not enable CPU profiling/ || error($line);
- (system("mv $tmp_profile $real_profile") == 0) || error("Unable to rename profile\n");
+
+ copy($tmp_profile, $real_profile) || error("Unable to copy profile\n");
print STDERR "Wrote profile to $real_profile\n";
$main::collected_profile = $real_profile;
return $main::collected_profile;
@@ -4683,6 +4683,9 @@ sub ConfigureTool {
unlink($main::tmpfile_sym);
unlink(keys %main::tempnames);
+ if (defined($main::collected_profile)) {
+ unlink($main::collected_profile);
+ }\n
# We leave any collected profiles in $HOME/pprof in case the user wants
# to look at them later. We print a message informing them of this.\n
コアとなるコードの解説
上記のコード変更は、misc/pprof
スクリプトの堅牢性とクロスプラットフォーム互換性を大幅に向上させています。
-
モジュールの追加:
use LWP::UserAgent;
とuse File::Copy;
が追加され、Perlスクリプト内でHTTP通信とファイル操作を直接、かつポータブルに行うための基盤が確立されました。
-
curl
の削除とLWP::UserAgent
への置き換え:my $CURL = "curl";
の行が削除され、curl
コマンドへの明示的な依存がなくなりました。CheckSymbolPage
、FetchProgramName
、FetchSymbols
、FetchDynamicProfile
といったHTTPリソースをフェッチするサブルーチン内で、open(..., "$CURL ... |")
やsystem("$CURL ...")
のような外部コマンド呼び出しが、LWP::UserAgent
オブジェクト (my $ua = LWP::UserAgent->new;
) を使用したHTTPリクエスト ($ua->get($url)
や$ua->request($req)
) に置き換えられました。- これにより、
curl
の有無やバージョン、OSによる挙動の違いに悩まされることなく、Perlスクリプト自身がHTTP通信を制御できるようになりました。特に、FetchSymbols
におけるPOSTリクエストの処理は、HTTP::Request
オブジェクトとLWP::UserAgent
を組み合わせることで、より明確かつ信頼性の高い実装になっています。
-
一時ファイル処理の改善:
- 以前は
$ENV{HOME} . "/pprof"
のような環境変数と固定パスを組み合わせた一時ディレクトリの作成や、"$profile_dir/.tmp.$profile_file"
のような手動での一時ファイル名生成が行われていました。これらはWindows環境では問題となる可能性がありました。 - 新しいコードでは、
my $tmp_profile = File::Temp->new()->filename;
を使用して、OSが推奨する一時ディレクトリに安全かつ一意な名前で一時ファイルが作成されるようになりました。これにより、一時ファイルのパスに関するPOSIX依存の仮定が排除されました。 - プロファイルデータを一時ファイルに書き込む際も、
open(OUTFILE, ">$tmp_profile"); binmode(OUTFILE); print OUTFILE $response->content;
のように、LWP::UserAgent
で取得したコンテンツを直接ファイルに書き込む形式に変更されました。 - 一時ファイルを最終的なプロファイルファイルに移動する処理も、
system("mv $tmp_profile $real_profile")
からcopy($tmp_profile, $real_profile)
に変更されました。File::Copy::copy
は、OSに依存しないファイルコピー機能を提供し、mv
コマンドが利用できない環境でも確実にファイル移動が行われることを保証します。
- 以前は
-
クリーンアップ処理の追加:
cleanup
サブルーチンに、if (defined($main::collected_profile)) { unlink($main::collected_profile); }
という行が追加されました。これにより、収集されたプロファイルファイルが不要になった際に適切に削除されるようになり、ディスクスペースの管理が改善されました。
これらの変更は、misc/pprof
スクリプトがGoのプロファイリングエコシステムにおいて、よりポータブルで信頼性の高いツールとして機能するための重要なステップでした。
関連リンク
- Go Issue 6177: https://github.com/golang/go/issues/6177 (このコミットが修正した問題のトラッキング)
参考にした情報源リンク
- Go言語公式ドキュメント:
pprof
- Perl公式ドキュメント:
LWP::UserAgent
,File::Temp
,File::Copy
- POSIX標準に関する一般的な情報
curl
コマンドに関する一般的な情報- Go言語のGitHubリポジトリのコミット履歴