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

[インデックス 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を使用しようとすると、接続エラーが発生していました。
  • 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スクリプトはこの特定のエラーメッセージを適切に処理せず、一般的な接続エラーとして扱ってしまうため、ユーザーが問題の原因を特定しにくい状況でした。このコミットは、この特定のエラーメッセージを捕捉し、より分かりやすいエラーとして報告するように改善します。

これらの問題は、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点です。

  1. 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を透過的に処理できるようになります。
  2. 「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スクリプトの堅牢性と使いやすさを大幅に向上させています。

関連リンク

参考にした情報源リンク