フレームグラフ(flame graph)は、コールスタックのサンプリングを可視化する手法であり、プログラムが CPU 時間をどこに費やしているかを示します。2011 年に Brendan Gregg が考案したもので、サンプリングした関数呼び出しを下から上へ積み上げることで、プログラムの最もホットなコードパスを数秒で読み取れます。すべてのフレームグラフは 4 つのルールに従います:幅はその関数がサンプリングに占める比率であり、継続時間ではない。y 軸はコールスタックの深さ。上端こそが真の on-CPU の位置(ルートを上に描画する方向では逆に、下端を見る)。x 軸はアルファベット順であり、時間軸ではない。ほかは忘れても、この 4 つだけは覚えておいてください。

このグラフが必要になる瞬間は、たいていこうです。アラートが鳴り、top で見るとあるプロセスが CPU 使用率 100% に張り付いている。そして……その先がない。top が教えてくれるのはどのプロセスが CPU を食い潰しているかまでで、本当に知りたいこと——どの関数か、どのファイルか、どの行か——には答えてくれません。フレームグラフが埋めるのは、まさにこのステップです。切り分けを「プロセスレベル」から「コード行レベル」まで進めてくれます。本記事ではまず 4 つの読み方ルールと典型的な形状を示し、続いて 3 つの実例(PHP・Go・Erlang)で、実際のフレームグラフから原因のコード行までを一気にたどります。

フレームグラフの読み方

多くの解説はフレームグラフを「CPU 使用状況を示すもの」と説明します。正しいけれど役に立ちません。ここでは、実際に読むために必要なことを示します。

4 つのルール

1. 幅 = サンプル比率であり、継続時間ではない。 ある枠がグラフの 40% を占めるということは、その関数がコールスタックのサンプリングの 40% に現れたという意味です。その関数が実時間の 40% 動いていたという意味ではありません。CPU プロファイリングでは両者の違いはたいてい問題になりませんが、「なぜこの枠は火曜日のグラフでは広いのか」と問われたときには大きな違いになります——答えはサンプリング回数であり、実時間ではありません。

2. y 軸はコールスタックの深さ。 下端がエントリポイント(mainstart_thread、ランタイムのブートストラップ関数)、上端が CPU が実際に実行していたリーフ関数です。その間の枠はすべて、呼び出しチェーン上の呼び出し元です。

3. 上端が on-CPU の位置。 何が CPU を食い潰しているか知りたければ、グラフの上端を見ます。上端で最も幅の広い枠が、まさに CPU 上で実行されている関数です。その下にあるものはすべて「そこへどう至ったか」を表します。描画方向に注意してください。古典的なフレームグラフはルートが下、炎が上へ伸びます。一方、多くのモダンなツール(本記事の実例で用いる OpenResty XRay を含む)はルートが上、リーフ関数が下へ伸びる方向で描画します——この場合、本ルールは下端に鏡映されます。どちらの方向でもデータは全く同じです。詳しくは後述の派生形の節を参照してください。

4. x 軸はアルファベット順であり、時間ではない。 これは最もよくある誤読です。フレームグラフはタイムラインではありません。水平方向の並びはアルファベット順(またはアドレス順)であり、同一のスタックフレームをより幅の広い枠にまとめるためのものです。並んだ 2 つの枠は「A が起きてから B が起きた」を意味しません。たとえば decodeencode の左にあっても、デコードがエンコードより先に実行されたわけではなく、単に文字 d が e より前に並ぶだけです。

形状による識別

4 つのルールが身についたら、フレームグラフを読むことは形状認識になります:

  • Plateau(プラトー、幅広く平らな上端):1 つの関数が CPU を独占している状態。最も分かりやすいパターンで、この plateau の関数が何をしているかを見れば、それがボトルネックです。下の PHP の例では、preg_match がまさにこのボトルネックで——付随するレポートによれば、これをリーフとするパスが CPU の 56.2% を占めています。

  • Tower(タワー、高く細い突起):非常に深いコールスタックだが、発生頻度は低い。複数の tower が集まって幅広い土台を形成しない限り、たいていは問題ではありません。plateau だらけのフレームグラフに孤立した tower が 1 本あるなら、それはノイズです。

  • Hair(ヘアー、上端に並ぶ多数の細い突起):それぞれ数回のサンプリングにしか現れない短命な呼び出し——カーネル割り込み、タイマーティック、シグナルハンドラなど。Hair は正常です。もし hair が幅広ければ、それは割り込みストーム(interrupt storm)であって、アプリケーションのバグではありません。

  • 平坦な土台・凸凹の上端:フレームワークやランタイム(土台の部分)は健全で、差異はアプリケーションレベルのコード(上部の突起)にあります。土台が枝分かれし始める箇所から上へ読み始めます。下の PHP の例がまさにこれで——フレームワークの各層は等幅で、ホットスポットはより深い業務ロジックのコードにあります。

これらの形状を暗記する必要はありません。後述の「3 つの実例」の節ですべて対応づけられます:PHP は深い土台、Erlang はリーフ端の枝分かれです。まず実際のグラフを見れば、形状は自然に身につきます。

self と total

フレームグラフの各枠には 2 つの量があります。total(合計)はその枠の全幅で、呼び出したすべての関数を含みます。self(自身)は、その関数がスタックの最上部に位置するサンプリングの比率です(つまり、それより上に呼び出し先がサンプリングされなかった場合)。プロファイラではこの 2 つを “self time”・“total time” と表記することもよくありますが、サンプリング式のフレームグラフではこの “time” は正確ではありません——ルール 1 と同様、根底にある量はサンプリング回数/比率であって、実時間ではないからです。Go の pprof はこれらを flat(self)・cum(cumulative、すなわち total)と呼んでおり、こちらのほうが的確です:名前に “time” が入っていません。(Chrome DevTools は確かに “Self Time”・“Total Time” と呼びますが、それは DevTools が生成するのがフレームチャートであり、x 軸が本当に時間だからです——後述の派生形の節を参照。)

total は大きいが self がごく小さい関数はディスパッチャです:それ自身は CPU を消費しておらず、下位の子関数へ処理を振り分けているだけです。

プロファイラが self(pprof の flat)でのソートに対応しているなら、それを使ってください。self が最も高い関数こそ CPU が実際に動いている場所であり、最適化の対象です。

3 つの実例:フレームグラフから原因のコード行まで

前述のルールと形状は語彙であり、判断力を鍛えるのは実例です。以下の 3 例は、動的トレーシングによって実行中のプロセスをプロファイリングした結果です——コード変更も再起動も不要です。先に 2 点を明確にしておきます。OpenResty XRay が描画するフレームグラフはルートが上・リーフが下の方向(後述のアイシクル方向)で、最もホットなバックトレースを赤色でハイライトします——したがって以下のグラフは下端と赤いパスを見て読みます。また各例の百分率の数値は、フレームグラフと同じ分析実行で生成されたコードパスレポートから得たものです。各例は同じ 3 ステップの読み方に従います:何が見えるか → どう見分けるか → 何を意味するか

例 1:PHP preg_match — ループ内の正規表現に CPU の 56.2%

PHP レベルの CPU フレームグラフ(ルートが上)。可視部分は Laravel フレームワークの server.php からミドルウェアパイプラインへのブートストラップ呼び出しチェーンで、最もホットなパスはさらに下の業務ロジックのコードへ続く

何が見えるか: ルートが上の PHP レベルフレームグラフ。可視部分は Laravel フレームワークのブートストラップとミドルウェアのチェーン——server.php から Kernel::handle を経て Pipeline ミドルウェアへ、一層ずつほぼ等幅で下へ渡っていきます。これは「深い土台」の形状です:フレームワーク自体は CPU を消費せず、リクエストを下へ受け渡しているだけで、真のホットスポットは呼び出しチェーンのより深い業務ロジックのコードにあります。

どう見分けるか: 土台の各層がほぼ等幅であることが、CPU がフレームワーク層で分散していないことを示しています。最も幅広い 1 本のチェーンを下へたどれば、業務ロジックの関数に到達します。付随するコードパスレポートが答えを直接示しています:第 1 位の最もホットな PHP パスは preg_match をリーフとし、Laravel の callAction を経て processOrders に至り、CPU 時間の 56.2% を占めています——リーフこそが CPU の燃える場所です。

第 1 位の最もホットな PHP コードパスレポート:preg_match をリーフとし、processOrders と Laravel の callAction を経て、CPU 時間の 56.2% を占める

何を意味するか: processOrdersProductServiceProvider.php の 437 行目)のループ内で、同一の正規表現を繰り返しマッチングしており、反復のたびに PCRE マッチのコストを支払っています。修正の方向性も元の事例に由来します:正規表現を事前にコンパイルして再利用し、反復ごとに再構築しなくて済むようにします。

同じプロセスの C レベルのコードパスレポートが、この結論を裏付けています:

C レベルの最もホットなコードパスレポート。第 1 位は pcre2_match_8 で、php_pcre_match_impl と Zend エグゼキュータを経て呼び出され、CPU の 34.7% を占める

pcre2_match_8 が最もホットな C 関数で 34.7% を占め、php_pcre_match_impl を経て呼び出されています。2 つの言語レベルが同じボトルネックを指すとき、この診断が正しいと確信できます。

詳細な切り分け手順:PHP 高 CPU 使用率:最もホットな PHP コードパスの特定

例 2:Go regexp.MustCompile — 正規表現のコンパイルに CPU の 36.8%

Go レベルの CPU フレームグラフ(ルートが上)。赤色でハイライトされた最もホットなバックトレースが prev_processor.go の 17 行目 CheckMessage を経て regexp.MustCompile のコンパイル呼び出しチェーンへ入る

何が見えるか: ルートが上の Go レベルフレームグラフ。最もホットなバックトレースが赤色でハイライトされています:HTTP 処理チェーンが chat.Handle を経て prev_processor.go の 17 行目 CheckMessage に至り、さらに regexp.MustCompile へ入り、その下に赤い regexpsyntax のコンパイル呼び出しが一面に広がります。付随するレポートによれば、この正規表現コンパイルのパスが CPU 時間の 36.8% を占めています。

どう見分けるか: 赤いハイライトをリーフ端までたどると、関数名が別の物語を語ります:これは正規表現のマッチング(実行)ではなく——正規表現のコンパイル(オートマトンの構築)です。コンパイルはマッチングよりはるかに高コストで、ホットパスでコンパイルを行うのは Go コードにありがちな誤りです。

何を意味するか: prev_processor.go の 17 行目 CheckMessage 関数が、呼び出しのたびに regexp.MustCompile を実行しています。一般的な作法は、各正規表現を一度だけコンパイルしてその結果を再利用すること——たとえばパッケージレベルの変数で初期化することです。コンパイルをホットパスの外へ移せば、この CPU の 36.8% を占めるコンパイル呼び出しチェーンは消えてなくなります。

詳細な切り分け手順:Go 高 CPU 使用率:正規表現のコンパイルが CPU 時間の 36.8% を消費

例 3:Erlang erts_pcre_execlists:filter ループ内の PCRE バックトラッキング

Erlang レベルの CPU フレームグラフ(ルートが上)。cowboy のリクエスト処理チェーンから check_resp_content と lists:filter の無名関数を経て、下端の erts_pcre_exec に到達。赤色でハイライトされた C:match.constprop.1 が最もホットなバックトレース

何が見えるか: ルートが上の Erlang レベルフレームグラフ:cowboy のリクエスト処理チェーンから業務ロジック関数 check_resp_content に至り、さらに lists:filter の無名関数を経て下端の erts_pcre_exec に到達します。赤色でハイライトされた C:match.constprop.1 が、最もホットなバックトレースの終点です。C レベルのコードパスレポートも同じチェーンを裏付けています——最もホットな C パスは matcherts_pcre_execre:run です:

C レベルの最もホットなコードパスレポート。第 1 位は match ← erts_pcre_exec ← re_run。Erlang レベルの第 1 位パスは check_resp_content と lists:filter を経て C:match.constprop.1 に至る

どう見分けるか: リーフ端の枝分かれ形状。Go の例の単一の赤い幹とは異なり、このグラフは最下部で複数の並列するリーフパス(erts_iolist_sizeerts_iolist_to_buferts_pcre_exec——前 2 つはグラフ上では切り詰められたラベルで表示されます)に分かれます。ルールは変わりません:最も幅広く、赤色でハイライトされたものをたどれば——それが正規表現呼び出しである erts_pcre_exec へと通じます。

何を意味するか: リスト内の要素が一つ残らず PCRE 正規表現とマッチングされています。PCRE はバックトラッキング方式の NFA エンジンを使うため、.*.+・入れ子の選択分岐を含むパターンは指数時間のマッチングを引き起こしかねません——この失敗モードは破滅的バックトラッキング(catastrophic backtracking)と呼ばれます。修正方法は、バックトラッキングを避けるようにパターンを最適化するか、バックトラッキングを行わない正規表現エンジンに切り替えるかのいずれかです。

詳細な切り分け手順:Erlang 高 CPU 使用率:フレームグラフで PCRE 正規表現のボトルネックを追跡

3 つの実例に共通する法則

3 つのボトルネックはいずれも正規表現に関連しますが、結論は普遍的です:

  1. まずリーフ関数の側を見る(本記事の 3 例では下端)。赤いハイライトがあれば、それをそのままたどります。最も幅広いリーフ枠が出発点です。
  2. 下へ読んで文脈を得る。 下側の枠が、なぜその関数がホットなのか——誰が、どこから、どのループの中で呼び出しているのか——を教えてくれます。
  3. 言語レベルをまたいで相互検証する。 アプリケーションレベルのフレームグラフ(PHP/Go/Erlang)と C レベルのフレームグラフが同じボトルネックを指すとき、得られるのは確定した診断であり、仮説ではありません。

3 例はたまたますべて正規表現のボトルネックです。他の形状の実際のグラフ——ロック待ち、メモリ割り当てなど——を見たい場合は、後述のスタック別ルーティング表の最後の列にある各スタックの事例記事、および後述の off-CPU・メモリフレームグラフの派生形から始めてください。

100% CPU から原因のコード行まで

冒頭の場面に戻りましょう:top はプロセスレベルで止まっていました。前述の読み方をつなぎ合わせれば、4 ステップで 100% CPU から具体的なコード行までを走破できます:

ステップ 1:プロセスを特定する。 top または htop が、どのプロセスが CPU を食い潰しているかを教えてくれます。PID を控えておきます。

ステップ 2:フレームグラフを取得する。 プロファイラでその PID からコールスタックのサンプリングを採取します。どのスタックにどのツールを使うかは次節で説明します。

ステップ 3:リーフ関数の側を読む。 フレームグラフのリーフ端で最も幅広い枠を見つけます——それが直接 CPU を食い潰している関数です。ホバーまたはクリックすれば、そのソースファイルと行番号が分かります。注意:探すべきはリーフ端で最も幅広い(self が最も高い)枠であって、全体で最も幅広い枠ではありません——土台にある全幅を貫く枠はたいていフレームワークやディスパッチャで、total は大きくても自身は CPU を消費していません(前述の「self と total」の節を参照)。

ステップ 4:コードを読む。 ファイルを開き、その行へジャンプします。これで何が CPU を食い潰しているか、そしてなぜ呼び出されているかが分かります。フレームグラフが完全なコールスタックを与えてくれているからです。

この手順は言語やランタイムを問わず同じです。変わるのはステップ 2 で使うツールだけです。

スタック別ルーティング表

スタックプロファイリングツールワンライナー/エントリポイントフレームグラフ事例記事
Linux(任意のネイティブバイナリ)perfperf record -g -p PID; perf script | stackcollapse-perf.pl | flamegraph.pl
Gopprofgo tool pprof -http=:8080 http://host:port/debug/pprof/profileGo 高 CPU
Javaasync-profilerasprof -d 30 -f out.html PIDJava CPU 分析
Node.js0x / --prof0x app.jsNode.js CPU 分析
PHPExcimer(サンプリング)PHP 高 CPU
PerlDevel::NYTProfperl -d:NYTProf script.plPerl 高 CPU
Erlang/BEAMeflameErlang 高 CPU
Rustcargo-flamegraphcargo flamegraph --pid PIDRust/Sled 高 CPU
Nginx/OpenRestyNginx CPU 最もホットなリクエストLua CPU フレームグラフ
C/C++(Envoy、llama.cpp など)perfLinux の行と同じEnvoy CPUllama.cpp CPU

上表の各ツールはいずれも、何らかの形で計装、再コンパイル、または再起動を必要とします——このコストについては、次節でその内容と回避方法を詳しく述べます。

自分のスタックでフレームグラフを生成する方法

これまでフレームグラフを生成したことがなくても、前述のスタック別ルーティング表が主要なランタイムごとのエントリポイントを示しています。一般的な手順は次のとおりです:

  1. コールスタックのサンプリングを採取する——対象プロセスから採取します(ツールはスタックによって異なります)。
  2. 折りたたむ——サンプリングをテキスト形式に折りたたみます(一意なスタックごとに 1 行、カウント付き)。
  3. 描画する——折りたたんだスタックを SVG またはインタラクティブな HTML に描画します。

Brendan Gregg の FlameGraph リポジトリは、ステップ 2・3 のための stackcollapse-*.plflamegraph.pl を提供しています。多くのモダンなツール(pprof、async-profiler、cargo-flamegraph)は、この 3 ステップをワンコマンドで実行します。

これらすべてのツールに共通するコストはアクセス権です:プロセスを変更する(フラグを追加する、フレームポインタ付きで再コンパイルする、デバッグエージェントを有効にするために再起動する)か、perfeBPF を使うために rootCAP_SYS_ADMIN 権限を持つ必要があります。本番環境では、このコストは現実的な問題です——とりわけ障害対応の最中、最も避けたいのが再起動です。

OpenResty XRay はこのコストを取り除きます。動的トレーシングによって実行中のプロセスにアタッチし——コード変更も、再コンパイルも、再起動も不要で——上表のルーティング表に挙げたすべてのスタックについてフレームグラフを生成できます。本記事の 3 つの実例のフレームグラフは、いずれもこの方法で採取されたものです:実行中のプロセスから、ダウンタイムゼロで。

アイシクルグラフ・フレームチャートなどの派生形

アイシクルグラフ

**アイシクルグラフ(icicle graph、逆フレームグラフとも呼ばれる)**はスタックを反転させます:ルートが上端にあり、リーフ関数が下へ伸びていきます。データは全く同じ——同じサンプリング、同じ幅——で、変わるのは方向だけです。

多くのモダンなプロファイリングツール(Grafana Pyroscope、Polar Signals、Datadog の continuous profiler)はデフォルトでアイシクル方向を採用しています。使っているツールがルートを上端に表示するなら、見ているのはアイシクルグラフです——本記事の 3 つの実例の OpenResty XRay フレームグラフも、まさにこの方向です。読み方のルールは同じです:幅はサンプル比率、下端が on-CPU の位置になり、x 軸は依然として時間ではありません。

フレームチャートとフレームグラフの違い

**フレームチャート(flame chart)**はフレームグラフに似ていますが、x 軸時間です。フレームチャートはブラウザの DevTools(Chrome の Performance パネル)や一部のトレーシングツールが生成します。フレームチャートでは、水平方向の位置は「これが先に起き、あれが後に起きた」を意味します——つまり左右の順序に意味があります。

最も手早い見分け方:同一の関数名が、まとまらずに複数の枠として現れるなら、それはフレームチャート(時間順)です。同一の名前が常に 1 つの幅広い枠にまとまるなら、それはフレームグラフ(アルファベット順のまとめ)です。

その他の派生形

フレームグラフは CPU に限りません:

  • Off-CPU フレームグラフは、プログラムが何を待っているか——ロック、I/O、sleep 呼び出し——を示します。Off-CPU 分析の実践を参照してください。
  • メモリフレームグラフは、どの呼び出しパスがメモリを割り当てているかを示します。Django のメモリ分析を参照してください。
  • **差分フレームグラフ(differential flame graph)**は、2 つのプロファイルを比較して何が変わったかを示します——デプロイ前後の比較に有用です。

各派生形は幅が何を表すか(待ち時間、割り当てバイト数、サンプリング差分)を変えますが、積み上げ構造は同じままです。

AI はフレームグラフを代わりに読めるか?

作業を速められますが、基礎を代替することはできません。AI は確かに、よくあるパターン(ループ内の正規表現、ロック競合、フレームワークのオーバーヘッド)の識別が速く、初めてフレームグラフを読むエンジニアにとって有用な出発点です。しかし本記事の 4 つのルールは十分に単純で、基礎に AI は要りません。AI が真に価値を発揮するのは、フレームグラフを利用者自身の具体的なコードベースとランタイムに結びつける場面です——そしてそれには、1 枚の SVG 画像以上のものが必要です。

OpenResty XRayAI アシスタントはまさにこれを行います:静的な画像を分析するのではなく、フレームグラフを生成した動的トレーシングのデータ——生のコールスタックのサンプリング、プロセスのメタデータ、ランタイムの文脈——を直接扱います。これにより、言語レベルをまたいだ相関付け(PHP + C、Go + ランタイム)が可能になり、利用中のフレームワーク固有のパターンを識別し、ソースファイルと行番号を引用した提案を提示できます。

FAQ

フレームグラフの枠の幅は何を表しますか?

幅は、その関数を含むコールスタックのサンプリングが全サンプリングに占める比率を表します。ある枠がグラフ幅の 30% を占めるということは、プロファイリング期間全体を通じて、その関数がサンプリングの 30% に現れたという意味です。これは CPU 時間のシェアの近似値であり、実時間の測定値ではありません。

フレームグラフの色に意味はありますか?

Brendan Gregg のオリジナルのフレームグラフでは、色はランダムな暖色系であり——意味を持ちません。一部のツールはモジュール、言語レベル、パッケージ名ごとに色を割り当てます(たとえば赤はカーネル、緑はアプリケーションコード)。使っているツールに凡例があるか確認してください。凡例がなければ、色は装飾にすぎません。OpenResty XRay のフレームグラフはオレンジと青の 2 色を用います:オレンジが CPU フレームグラフ、青が off-CPU フレームグラフです。

フレームグラフでボトルネックをどう見つけますか?

グラフの上端(アイシクルグラフなら下端)を見ます。その端で self が最も高い枠が、探している関数です——処理を振り分けているだけでなく、直接実行している関数です。それがボトルネックです。下へ読めば、そこへ至るコールチェーンが分かります。

top で 100% CPU が見えるのに、どのコードが原因か分かりません。どうすればよいですか?

top が教えてくれるのはどのプロセスが CPU を食い潰しているかまでで、どの関数・どの行かは分かりません。必要なのはフレームグラフです。そのプロセスの PID にプロファイラをアタッチし(前述のスタック別ルーティング表を参照)、10〜30 秒間コールスタックのサンプリングを採取して、フレームグラフを描画します。リーフ端(古典的な方向では上端、ルートが上の描画では下端)で最も幅広い plateau が、原因の関数です。ホバーまたはクリックすれば、ソースファイルと行番号が分かります。高い P99 レイテンシーを CPU 使用率が高い状態で追いかけているなら、フレームグラフがまさにこのステップです——診断の道筋と根本原因の全体像はP99 レイテンシー徹底解説をご覧ください。

OpenResty XRay について

OpenResty XRay動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、動作の問題、セキュリティの脆弱性を診断し、実行可能な提案を提供いたします。基盤となる実装において、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、様々な環境下で複数の異なるランタイムをサポートしております。

この記事を気に入っていただけましたら、ぜひ当ブログサイトおよび YouTube チャンネルのご登録をお願いいたします。ありがとうございます!

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵入型の障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。よろしくお願いいたします。