2024/02/13 22:13:39

Plack アプリのプロファイリング by Devel::NYTProf

特に、Plack アプリに限定する話でもない部分は多々ありますが、Plack アプリを Devel::NYTProf でプロファイリングする方法について。

シングルプロセスの場合

plackup -MDevel::NYTProf

開発環境で plackup する場合など、シングルプロセスで起動する Plack アプリでプロファイルを取得するには、以下のように実行します。

NYTPROF="sigexit=int" plackup -MDevel::NYTProf -e 'sub { [200, [], ["ok $$"]] }'

通常なら、-d:NYTProf とするところを、-MDevel::NYTProf としていますが、動作は同じです。

環境変数 NYTPROF に設定している sigexit=int は、ワンライナーの実行を Ctrl + C で止めたときに、profiling を正しく終了するための設定です。

NYTProf に関する環境変数 NYTPROF について詳しくは、Devel::NYTProf の POD にあります。

取り急ぎ、上記のワンライナーを叩き、ローカルの port:5000 に HTTP でアクセスするたびに、プロファイリングが取得されます。カレントディレクトリに、nytprof.out というファイルが作成されているので、確認してみましょう。

nytprof.out から、HTML のプロファイルレポートを作成するために nytprofhtml コマンドを使います。

nytprofhtml

nytprofhtml コマンドは、Devel::NYTProf に同梱されているコマンドです。以下のように使用します。

mkdir nytprof_report
nytprofhtml -f nytprof.out -o nytprof_report

上記のコマンドを打つと、以下のように進捗が表示され、nytprof_report ディレクトリに HTML のレポートが作成されます。

Reading nytprof.out
Processing nytprof.out data
Writing sub reports to report directory
 100% ...
Writing block reports to report directory
 100% ...
Writing line reports to report directory
 100% ...

レポートの閲覧は、Plack::App::Directory 使うのが簡単です。

plackup -MPlack::App::Directory -e 'Plack::App::Directory->new({root => "./report"})->to_app'

なお、レポートは、HTML 形式だけでなく、CSV にする nytprofcsv コマンドもあったりします。

fork 環境の場合

StarmanStarlet を使って prefork した Plack アプリの場合、Devel::NYTProf の導入に考慮点が出てきます。

親子ともどもプロファイルする場合

親プロセスの実行から、子プロセスの実行まで、すべてのプロファイリングを素直に取得したい場合は、上に書いているシングルプロセスの場合と同じように実行すれば ok。

例えば Starlet の場合は、以下のようになります。

NYTPROF="sigexit=int" plackup -MDevel::NYTProf -s Starlet -e 'sub { [200, [], ["ok $$"]] }'

この場合、カレントディレクトリに、nytprof.out と nytprof.out.{PID} というファイルが作成されます。nytprof.out が親プロセスのプロファイル結果で、nytprof.out.{PID} が子プロセスのプロファイル結果になります。

なお、この子プロセスの nytprof.out.{PID} のファイル名に、{PID} が付いてくるところは、NYTProf の仕様なので、変更することができません。もしもそれをやるには XS にパッチを当てることになるでしょう。

環境変数の addpid でコントロールできるように思うかもしれませんが、そこでコントロールするのは、親プロセスのプロファイル出力である、nytprof.out に PID を付与するかどうかであって、 fork した子プロセスに PID が付くのはコントロール不可能なのです。さらに言うと、親プロセスで addpid=1 の場合に、PID がファイル名の最後に付与されるのも仕様で、変更するのは簡単ではありません。

このあたりを、仕様通り利用するなら、上記の通り、話は簡単です。

子プロセスの実行のみプロファイルする場合

fork する Plack アプリの場合、起動した後は、ほとんどが子プロセス内で起こることに終始します。したがって、親プロセスから子プロセスまで、まとめてプロファイリングをとる必要はあまりありません。しかも、親プロセスで Devel::NYTProf をロードしないで、子プロセスでロードすると、上に書いたような、プロファイル結果ファイルの名前が自由につけられなくなるなどの制約をとっぱらうことができます。

そこで登場するのが Plack::Middleware::Profiler::NYTProf です。

Plack::Middleware::Profiler::NYTProf

このミドルウェアは、子プロセスごとに Devel::NYTProf をロードすることによって、プロファイルの適用やプロファイル結果のファイル名をプログラマブルにコントロールすることを可能にします。

例えば、以下のような PSGI ファイルで実行します。

use Plack::Builder;

my $app = sub {
    my $env = shift;
    return [ '200', [ 'Content-Type' => 'text/plain' ], [ "Hello World $$" ] ];
};

builder {
    # you should execute 'mkdir /tmp/profile' before invoking this PSGI app;
    enable 'Profiler::NYTProf',
        enable_profile       => sub { $$ % 2 == 0 },
        env_nytprof          => 'start=no:addpid=0:file=/dev/null',
        profiling_result_dir => sub { '/tmp/profile' },
        enable_reporting     => 1;
    $app;
};

PID が偶数のプロセスでのみプロファイリングを行い、ついでに /tmp/profile にレポートを出力します。レポートはリクエストごとに生成されるので、本番投入などには現実的な設定例ではありませんが、上記のようなコントロールがオプションだけで可能です。

See Also

サイト内検索