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

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

このコミットは、Go言語のプロファイリングツールであるpprofスクリプト(Perlで書かれている)が、HTTPリクエストを行う際にLWP::UserAgentモジュールとcurlコマンドの両方に対応するように改善したものです。これにより、異なるオペレーティングシステム(特にWindowsとmacOS)での互換性が向上し、ユーザーがプロファイリングデータをより確実に取得できるようになります。

コミット

commit ccfe1bfd92461e2743d1004da0365ac2b33f2a6a
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 29 16:42:13 2013 -0700

    misc/pprof: work with either LWP::UserAgent or curl
    
    Use either LWP::UserAgent or curl to make HTTP requests so it
    works on Windows (most Perl distros include LWP::UserAgent),
    and also on OS X (whose Perl at least sometimes doesn't
    include LWP::UserAgent).
    
    Fixes #6273
    
    R=golang-dev, alex.brainman, cldorian
    CC=golang-dev
    https://golang.org/cl/13330044

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

https://github.com/golang/go/commit/ccfe1bfd92461e2743d1004da0365ac2b33f2a6a

元コミット内容

misc/pprof: work with either LWP::UserAgent or curl

Use either LWP::UserAgent or curl to make HTTP requests so it
works on Windows (most Perl distros include LWP::UserAgent),
and also on OS X (whose Perl at least sometimes doesn't
include LWP::UserAgent).

Fixes #6273

変更の背景

この変更の背景には、Go言語のプロファイリングツールであるpprofスクリプトが、異なるオペレーティングシステム環境下でHTTPリクエストを正常に実行できないという問題がありました。具体的には、以下の点が挙げられます。

  1. Windows環境での問題: 多くのPerlディストリビューションにはLWP::UserAgentモジュールが含まれているため、Windows上ではこのモジュールを使用してHTTPリクエストを行うことが期待されていました。しかし、一部の環境では問題が発生する可能性がありました。
  2. macOS環境での問題: macOSにプリインストールされているPerl環境では、LWP::UserAgentモジュールが常に利用可能であるとは限りませんでした。これにより、macOSユーザーがpprofを使用してリモートプロファイリングを行う際に、HTTPリクエストが失敗するという問題に直面していました。
  3. 互換性の向上: pprofスクリプトがより多くの環境で安定して動作するように、HTTPリクエストの実行方法に柔軟性を持たせる必要がありました。既存のLWP::UserAgentに加えて、広く利用されているコマンドラインツールであるcurlを代替手段として導入することで、モジュールの有無に依存しない堅牢なメカニズムを提供することが目的でした。

この変更は、Go issue #6273で報告された問題を解決するために行われました。ユーザーが直面していたプロファイリングデータの取得に関する問題を解消し、pprofツールの利便性と信頼性を向上させることが、このコミットの主要な動機となっています。

前提知識の解説

このコミットを理解するためには、以下の技術要素に関する知識が役立ちます。

Go言語のpprofツール

pprofは、Go言語のプログラムのパフォーマンスプロファイリングを行うためのツールです。CPU使用率、メモリ割り当て、ゴルーチン、ブロック、ミューテックス競合など、様々な種類のプロファイルデータを収集し、視覚化することができます。GoプログラムはHTTPエンドポイントを通じてプロファイルデータを提供し、pprofスクリプト(このコミットで変更されているPerlスクリプト)がそのデータを取得して解析します。

Perlスクリプト

このコミットで変更されているmisc/pprofファイルは、Perlで書かれたスクリプトです。Perlは、テキスト処理やシステム管理タスクに広く使用される高水準の汎用プログラミング言語です。pprofスクリプトは、Goプログラムからプロファイルデータを取得し、それを解析してグラフやテキスト形式で表示する役割を担っています。

LWP::UserAgent

LWP::UserAgentは、Perlの標準的なモジュール群であるlibwww-perlの一部であり、HTTPクライアント機能を提供します。これにより、PerlスクリプトからWebサーバーに対してHTTPリクエスト(GET、POSTなど)を送信し、そのレスポンスを受け取ることができます。PerlでWebコンテンツを取得したり、Web APIと連携したりする際によく使用されます。

curl

curlは、URL構文を使用してデータを転送するためのコマンドラインツールおよびライブラリです。HTTP、HTTPS、FTPなど、多くのプロトコルをサポートしています。curlは、スクリプトやコマンドラインからWebリソースにアクセスする際に非常に広く利用されており、特にHTTPリクエストのデバッグや自動化に便利です。多くのUnix系システム(macOSを含む)にプリインストールされており、Windowsでも利用可能です。

HTTP GET/POSTリクエスト

  • GETリクエスト: サーバーからリソースを取得するために使用されます。URLのクエリパラメータを通じてデータを送信できます。
  • POSTリクエスト: サーバーにデータを送信するために使用されます。通常、リクエストボディにデータを含めて送信し、新しいリソースの作成や既存リソースの更新などに使われます。

pprofスクリプトは、Goプログラムからプロファイルデータを取得するためにこれらのHTTPリクエストを使用します。例えば、シンボル情報やプログラム名を取得するためにGETリクエストを、特定のプロファイルデータを取得するためにPOSTリクエストを使用することがあります。

技術的詳細

このコミットの技術的詳細の核心は、pprofPerlスクリプトがHTTPリクエストを実行する際のフォールバックメカニズムを導入した点にあります。以前はLWP::UserAgentモジュールに完全に依存していましたが、この変更により、LWP::UserAgentが利用できない環境でもcurlコマンドを代替として使用できるようになりました。

具体的な変更点は以下の通りです。

  1. LWP::UserAgentの依存関係の緩和:

    • スクリプトの冒頭からuse LWP::UserAgent;の行が削除されました。これは、LWP::UserAgentが常に利用可能であるという前提を取り除き、必要に応じて動的にロードする(またはフォールバックする)アプローチに切り替えるためです。
  2. FetchHTTPサブルーチンの導入:

    • この新しいサブルーチンは、指定されたURLからHTTP GETリクエストを使用してコンテンツを取得します。
    • 内部では、まずeval "use LWP::UserAgent ();";を使用してLWP::UserAgentモジュールのロードを試みます。
    • LWP::UserAgentのロードが成功した場合($@が偽の場合)、従来のLWP::UserAgentオブジェクトを作成し、get()メソッドでリクエストを実行します。タイムアウトが指定されていればそれも設定します。
    • LWP::UserAgentのロードが失敗した場合($@が真の場合)、curlコマンドにフォールバックします。curlコマンドは-s(サイレントモード)オプションと、必要に応じて--max-timeオプション(タイムアウト用)を指定して実行されます。curlの出力はすべて読み込まれ、エラーが発生した場合はプログラムが終了します。
    • どちらの方法でも、HTTPリクエストが成功しなかった場合はエラーメッセージを出力してプログラムを終了します。
  3. PostHTTPサブルーチンの導入:

    • このサブルーチンは、指定されたURLにHTTP POSTリクエストを送信し、$post_dataをリクエストボディとして含めます。
    • FetchHTTPと同様に、まずLWP::UserAgentのロードを試みます。
    • LWP::UserAgentが利用可能な場合、HTTP::Requestオブジェクトを作成し、POSTメソッドと$post_dataを設定してリクエストを実行します。
    • LWP::UserAgentが利用できない場合、curlコマンドにフォールバックします。curlでPOSTデータを送信するために、$post_dataを一時ファイル($main::tmpfile_sym)に書き込み、curl-d @filenameオプションを使用してそのファイルをリクエストボディとして送信します。これにより、コマンドライン引数の長さに制限されることなく、任意のサイズのPOSTデータを送信できます。
    • エラー処理もFetchHTTPと同様に行われます。
  4. 既存のHTTPリクエスト箇所の置き換え:

    • CheckSymbolPageFetchProgramNameFetchSymbolsFetchDynamicProfileといった既存のサブルーチン内で直接LWP::UserAgentを使用していた箇所が、新しく導入されたFetchHTTPまたはPostHTTPサブルーチンへの呼び出しに置き換えられました。これにより、コードの重複が排除され、HTTPリクエストのロジックが一元化されました。
  5. Init()サブルーチンの修正:

    • elsif (IsSymbolizedProfileFile($ARGV[0]))の条件に$ARGV[0] &&が追加されました。これは、コマンドライン引数が存在しない場合にIsSymbolizedProfileFileが呼び出されるのを防ぐための、より堅牢なチェックです。
  6. cleanupサブルーチンの改善:

    • 一時ファイルの削除処理において、unlinkの引数にdefinedチェックが追加されました。これにより、変数が未定義の場合にunlinkがエラーになるのを防ぎ、より安全なクリーンアップが保証されます。例えば、unlink($main::tmpfile_sym) if defined $main::tmpfile_sym;のように変更されています。

この変更により、pprofスクリプトは、ユーザーのシステムにLWP::UserAgentがインストールされているかどうかにかかわらず、HTTPリクエストを実行できるようになり、より幅広い環境での互換性と信頼性が向上しました。

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

このコミットにおける主要なコードの変更箇所は、misc/pprofファイル内の以下の部分です。

  1. LWP::UserAgentuse文の削除:

    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -79,7 +79,6 @@ use strict;
     use warnings;
     use Getopt::Long;
     use File::Temp;
    -use LWP::UserAgent;
     use File::Copy;
    
  2. Init()サブルーチンの条件修正:

    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -502,7 +501,7 @@ sub Init() {
       # Remote profiling without a binary (using $SYMBOL_PAGE instead)
       if (IsProfileURL($ARGV[0])) {
         $main::use_symbol_page = 1;
    -  } elsif (IsSymbolizedProfileFile($ARGV[0])) {
    +  } elsif ($ARGV[0] && IsSymbolizedProfileFile($ARGV[0])) {
         $main::use_symbolized_profile = 1;
         $main::prog = $UNKNOWN_BINARY;  # will be set later from the profile file
       }
    
  3. HTTPリクエスト処理の共通化とFetchHTTPの導入:

    • CheckSymbolPageFetchProgramNameFetchDynamicProfile内のLWP::UserAgentを使ったHTTP GETリクエストがFetchHTTP呼び出しに置き換えられました。
    • 例: CheckSymbolPage
      --- a/misc/pprof
      +++ b/misc/pprof
      @@ -2979,11 +2978,7 @@ sub CheckSymbolPage {
         my $url = SymbolPageURL();
       print STDERR "Read $url\n";
       
      -  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;
      +  my $line = FetchHTTP($url);
         $line =~ s/\r//g;         # turn windows-looking lines into unix-looking lines
         unless (defined($line)) {
           error("$url doesn't exist\n");
      
  4. HTTP POSTリクエスト処理の共通化とPostHTTPの導入:

    • FetchSymbols内のLWP::UserAgentを使ったHTTP POSTリクエストがPostHTTP呼び出しに置き換えられました。
    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -3091,19 +3082,12 @@ sub FetchSymbols {
         $symbol_map = {};
     
         my $post_data = join("+", sort((map {"0x" . "$_"} @pcs)));
    -    open(POSTFILE, ">$main::tmpfile_sym");
    -    print POSTFILE $post_data;
    -    close(POSTFILE);
    -\n     my $url = SymbolPageURL();
    -    my $req = HTTP::Request->new(POST => $url);
    -    $req->content($post_data);
    -    my $lwp = LWP::UserAgent->new;
    -    my $response = $lwp->request($req);
    +    my $content = PostHTTP($url, $post_data);
         
         my $tmp_symbol = File::Temp->new()->filename;
         open(SYMBOL, ">$tmp_symbol");
    -    print SYMBOL $response->content;
    +    print SYMBOL $content;
         close(SYMBOL);
         
         open(SYMBOL, "<$tmp_symbol") || error("$tmp_symbol");
    
  5. FetchHTTPサブルーチンの追加:

    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -4680,12 +4658,59 @@ sub ConfigureTool {
       return $path;
     }
     
    -sub cleanup {
    -  unlink($main::tmpfile_sym);
    -  unlink(keys %main::tempnames);
    -  if (defined($main::collected_profile)) {
    -    unlink($main::collected_profile);
    +# FetchHTTP retrieves a URL using either curl or LWP::UserAgent.
    +# It returns the entire body of the page on success, or exits the program
    +# with an error message on any failure.
    +sub FetchHTTP {
    +  my $url = shift;
    +  my $timeout = shift;  # optional, in seconds
    +  eval "use LWP::UserAgent ();";
    +  if ($@) {
    +    my @max;
    +    push @max, "--max-time", $timeout if $timeout;
    +    open(my $fh, "-|", "curl", @max, "-s", $url) or error("Neither LWP::UserAgent nor curl is installed: $!\\n");
    +    my $slurp = do { local $/; <$fh> };
    +    close($fh);
    +    if ($? != 0) {
    +      error("Error fetching $url with curl: exit $?")
    +    }
    +    return $slurp;
    +  }\n+  my $ua = LWP::UserAgent->new;\n+  $ua->timeout($timeout) if $timeout;\n+  my $res = $ua->get($url);\n+  error("Failed to fetch $url\\n") unless $res->is_success();\n+  return $res->content();
    +}
    
  6. PostHTTPサブルーチンの追加:

    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -4680,12 +4658,59 @@ sub ConfigureTool {
       return $path;
     }
     
    -sub cleanup {
    -  unlink($main::tmpfile_sym);
    -  unlink(keys %main::tempnames);
    -  if (defined($main::collected_profile)) {
    -    unlink($main::collected_profile);
    +# ... (FetchHTTPのコード) ...
    +
    +sub PostHTTP {
    +  my ($url, $post_data) = @_;
    +  eval "use LWP::UserAgent ();";
    +  if ($@) {
    +    open(POSTFILE, ">$main::tmpfile_sym");
    +    print POSTFILE $post_data;
    +    close(POSTFILE);
    +\n+    open(my $fh, "-|", "curl", "-s", "-d", "\@$main::tmpfile_sym", $url) or error("Neither LWP::UserAgent nor curl is installed: $!\\n");
    +    my $slurp = do { local $/; <$fh> };
    +    close($fh);\n+    if ($? != 0) {\n+      error("Error fetching $url with curl: exit $?")\n+    }\n+    return $slurp;\n+  }\n+  my $req = HTTP::Request->new(POST => $url);\n+  $req->content($post_data);\n+  my $ua = LWP::UserAgent->new;\n+  my $res = $ua->request($req);\n+  error("Failed to POST to $url\\n") unless $res->is_success();\n+  return $res->content();
    +}
    
  7. cleanupサブルーチンの安全性の向上:

    --- a/misc/pprof
    +++ b/misc/pprof
    @@ -4680,12 +4658,59 @@ sub ConfigureTool {
       return $path;
     }
     
    -sub cleanup {
    -  unlink($main::tmpfile_sym);
    -  unlink(keys %main::tempnames);
    -  if (defined($main::collected_profile)) {
    -    unlink($main::collected_profile);
    +# ... (FetchHTTP, PostHTTPのコード) ...
    +
    +sub cleanup {\n+  unlink($main::tmpfile_sym) if defined $main::tmpfile_sym;\n+  unlink(keys %main::tempnames) if %main::tempnames;\n+  unlink($main::collected_profile) if defined $main::collected_profile;
    

コアとなるコードの解説

このコミットのコアとなる変更は、HTTPリクエストを処理するための新しいサブルーチンFetchHTTPPostHTTPの導入、そして既存のコードベースでのそれらの利用です。これにより、pprofスクリプトはLWP::UserAgentcurlのどちらか利用可能な方を使ってHTTP通信を行うことができるようになりました。

FetchHTTPサブルーチン

このサブルーチンは、HTTP GETリクエストを処理します。

sub FetchHTTP {
  my $url = shift;
  my $timeout = shift;  # optional, in seconds
  eval "use LWP::UserAgent ();"; # LWP::UserAgentのロードを試みる
  if ($@) { # ロードに失敗した場合 (LWP::UserAgentが利用できない場合)
    my @max;
    push @max, "--max-time", $timeout if $timeout; # タイムアウトがあればcurlのオプションに追加
    open(my $fh, "-|", "curl", @max, "-s", $url) or error("Neither LWP::UserAgent nor curl is installed: $!\\n"); # curlコマンドを実行
    my $slurp = do { local $/; <$fh> }; # curlの出力を全て読み込む
    close($fh);
    if ($? != 0) { # curlの実行が失敗した場合
      error("Error fetching $url with curl: exit $?")
    }
    return $slurp; # curlの出力を返す
  }
  my $ua = LWP::UserAgent->new; # LWP::UserAgentが利用可能な場合
  $ua->timeout($timeout) if $timeout; # タイムアウトを設定
  my $res = $ua->get($url); # GETリクエストを実行
  error("Failed to fetch $url\\n") unless $res->is_success(); # リクエストが成功しなかった場合エラー
  return $res->content(); # レスポンスの内容を返す
}
  • eval "use LWP::UserAgent ();";: これはPerlの強力な機能で、実行時にモジュールをロードしようとします。もしモジュールのロードに失敗した場合(例えば、モジュールがインストールされていない場合)、$@変数にエラーメッセージが設定されます。
  • if ($@): $@が真(エラーが発生した)の場合、LWP::UserAgentが利用できないと判断し、curlコマンドにフォールバックします。
  • open(my $fh, "-|", "curl", ...): これはPerlで外部コマンドを実行し、その標準出力をファイルハンドルとして読み込むための慣用句です。curlコマンドは-s(サイレントモード、プログレスバーなどを表示しない)と、オプションで--max-time(タイムアウト)を指定して実行されます。
  • do { local $/; <$fh> }: これは、ファイルハンドルからすべての内容を一気に読み込むためのPerlのイディオムです。local $/;はレコードセパレータを一時的に未定義にすることで、ファイル全体を一度に読み込ませます。
  • if ($? != 0): $?はPerlで最後に実行された外部コマンドの終了ステータスを保持します。0以外であればエラーが発生したことを意味します。
  • LWP::UserAgentの利用: $@が偽の場合、LWP::UserAgentが利用可能であるため、通常通りLWP::UserAgentオブジェクトを作成し、get()メソッドでHTTPリクエストを実行します。

PostHTTPサブルーチン

このサブルーチンは、HTTP POSTリクエストを処理します。

sub PostHTTP {
  my ($url, $post_data) = @_;
  eval "use LWP::UserAgent ();"; # LWP::UserAgentのロードを試みる
  if ($@) { # ロードに失敗した場合
    open(POSTFILE, ">$main::tmpfile_sym"); # POSTデータを一時ファイルに書き込む
    print POSTFILE $post_data;
    close(POSTFILE);

    open(my $fh, "-|", "curl", "-s", "-d", "\@$main::tmpfile_sym", $url) or error("Neither LWP::UserAgent nor curl is installed: $!\\n"); # curlコマンドで一時ファイルをPOSTデータとして送信
    my $slurp = do { local $/; <$fh> };
    close($fh);
    if ($? != 0) {
      error("Error fetching $url with curl: exit $?")
    }
    return $slurp;
  }
  my $req = HTTP::Request->new(POST => $url); # LWP::UserAgentが利用可能な場合
  $req->content($post_data); # POSTデータを設定
  my $ua = LWP::UserAgent->new;
  my $res = $ua->request($req); # POSTリクエストを実行
  error("Failed to POST to $url\\n") unless $res->is_success();
  return $res->content();
}
  • PostHTTPFetchHTTPと同様にLWP::UserAgentの利用可能性をチェックします。
  • curlでのPOSTデータ送信: curlでPOSTデータを送信する場合、-dオプションを使用します。特に大きなデータや特殊文字を含むデータを送信する場合、-d @filenameの形式で一時ファイルからデータを読み込ませるのが一般的です。このコミットでは、$post_data$main::tmpfile_symという一時ファイルに書き込み、curlにそのファイルを指定することで、安全かつ確実にPOSTデータを送信しています。
  • HTTP::Request->new(POST => $url): LWP::UserAgentを使用する場合、HTTP::Requestオブジェクトを作成し、そのcontent()メソッドでPOSTデータを設定してからrequest()メソッドで送信します。

その他の変更

  • Init()の修正: $ARGV[0] && IsSymbolizedProfileFile($ARGV[0])という変更は、コマンドライン引数$ARGV[0]が存在するかどうかを先にチェックすることで、未定義値に対する関数呼び出しを防ぎ、スクリプトの堅牢性を高めています。
  • cleanupの修正: unlink($main::tmpfile_sym) if defined $main::tmpfile_sym;のように、一時ファイルを削除する際に変数がdefined(定義されている)かどうかを確認するようになりました。これにより、変数が未定義の状態でunlinkが呼び出されることによる警告やエラーを防ぎ、よりクリーンな終了処理を実現しています。

これらの変更により、pprofスクリプトは、Perl環境に依存することなく、より多くのシステムで安定して動作するようになりました。

関連リンク

参考にした情報源リンク