sysload exporterを実装してみた

タイトルままということで。

github.com

goでゼロからexporterを書こう、sysloadの実装を今更ながら把握しておこうというモチベーションのもとsysloadをprometheusのexporterとして実装してみました。

f:id:egmc:20210918175302p:plain
dashboard image

Dashboardも公開しているの動かして見るぜーという方はこちらもどうぞ。 クエリみて頂ければわかりますが、設計上sysloadの値は単純なgaugeなので既存のノード用ダッシュボードに組み込むのも簡単だとおもいます。

grafana.com

sysload is 何

labs.gree.jp

詳細はこちらの記事とgithubレポジトリを参照頂ければとおもいます。

もともとLoad Averageの不具合を起因としてサーバ負荷(システムリソースのsaturation)を0-100で表す指標として開発されたものです。 単純なCPU負荷だけでなく、nicからの割り込みを受けているCPU群とIOを考慮して高い値が採用される設計になっているのでいずれかのリソースが飽和状態であるということがわかりやすく表すことができるようになっています。

exporterの実装について

基本は写経スタイルで、一部パラメータまわりを扱いやすくしたり細かい部分を調整しつつ、メトリクスの構造をexporter的な形式に寄せた感じ(のつもり)になってます。

prometheusのexporterはcounterで扱うスタイルが基本ですが、sysload自体はgaugeの値ですしその他追加で取得しているメトリクスや1,5,15分平均もそのまま計算した値を採用しています。

移植にあたって変更した点は細かい点ですが

  • 外部コマンドなど細かい部分の依存をなくした
  • interruptチェックにあたってのデバイス候補のデフォルト値変更(追加)
  • その他データ構造を取り回ししやすいようにいくつか修正

あたりになります。

個人的にメンテしてる環境でさくっと使いたいということで簡易なdebパッケージのみ提供してますが、systemdを前提にしているのでubuntuであれば16.04xenial以降の環境であれば動作するとおもいます、バイナリは大体どこでも動くかと。

ライセンスはオリジナル実装がGPL 2.0なのでそのようになっています。

今後のメンテなど

せっかく実装してみたのでぼちぼち動かしつつメンテしていきたいと思います。

  • とりあえず実装して個人のサーバで動作確認しましたというレベルなので、とりあえず何らかのテストを実装したい
  • interruptチェックまわりはパラメータ依存ではなくもう少し賢くできるといいんじゃないかと思っているのでここらへんはなにか良き方法があればどーにしかしたい
  • issueにとりあえず入れてるけどコンテナから動かせるように/procのマウントパスオプションを追加する

などなど

eBPF Summit 2021

今年はDay1のみリアタイ参戦したのでいくつかセッションのメモを。 内容は全部見ればわかるやろ程度ではあるもののまあポインタ程度の情報でもないよりは良いのではということで感想交えて雑に書いてみようという感じのやつです。

動画はすでに上がっていて個別のセッションはYouTube字幕もすでについていてセッションだけをみるならそちらがよい、QA含めてみるためにはDay1,2それぞれ全部入りの動画をみればおっけーという感じ。ただしDay1のeBPF for Windowsは若干音声まわりのトラブルがあったのでセッション個別はそちらを参照する方がおすすめです。

全体的な雰囲気とか所感とか

eBPF Summit自体は初回からオンライン開催、事前収録+リアルタイムQAスタイルでコミュニケーションはslack、trackも一本なのであっちこっち移動したりスポンサー的な何かがあったりはしないのでここに集中すればよいというのがはっきりしていて参加する方としてもやりやすいんでないかと。

セッション時間は20分 or 5分で短い休憩が挟まるという形式なので、「40分英語セッション集中して聞くの正直しんどい」という自分みたいなものにはこれくらいでちょうどよいですね。

↑の通り動画はすぐにあがるし、自動生成字幕ついてくれた方がありがたいみたいなところもあるのだが、とはいえリアタイじゃないと見ないみたいなのもあるしslack内での盛り上がってる感、雑に質問投げられる感という体験はよいものなのでということでまあ1日でも参加してよかったなーというところです。

全体のレベル感としてはIntroductory and overviewがもっとも多く、1日目はAdvancedなものは一つもなかったので、概要を結構繰り返し説明されるので最近関連ツール触り始めましたみたいな感じでもとっかかりがあればよさそうと思う反面ガチ勢な方々にはちょい物足りないのかもというところでそちらはどちらかというとday2で取り扱ったのだろうと思われます(未視聴

BrendanGregg氏のまず使ってほしい、といったQAコメントや特にDay1のツール利用者等ビギナー向けセッションの様子などをみているとeBPFを長期的に有用なテクノロジーとして扱っていくために裾野を広げて需要があるよ、ということを示していきたいという思いがあるのかなーという印象を受けました。

以下いくつかセッションメモ

The State & Future of eBPF

  • 最近発表のあったeBPF Foundationの話と過去、現在、未来についての話
  • かつてアプリケーションの観測に必要な機能の実装を「カーネル開発者に依頼して1年後にサポートされ5年後に各ディストロに降ってきてようやく使えるころには不要になってる」みたいなのがeBPFで数日で出来るようになった、これからはネットワーキング、セキュリティ、オブザーバビリティそれぞれ低オーバーヘッドでこういったことを実現してく、というoverview的な内容
  • QAでドライバ作れる?というのがあり、一般的じゃないけどまあ出来るみたいな回答だった(はず

Getting Started with BPF observability

  • いきなりツール開発するのではなくsysadmin的に考えていこうという内容で昨年のセッションから現在の状況を踏まえてマイナーチェンジした感じ
  • よく使われるレイヤーごとのツール群のマッピング画像がパワーアップしてた、気がする
  • LISA21の方がInternalのお話をしていたのでこちらはやはりnewな人向けな内容だと思いますがとにかくBrendan Gregg見たいぜという人にも

eBPF in Microservices Observability

  • マイクロサービスをeBPFで観測してこうという話
  • 前半はOverview的な説明
  • 旧来のツールではそれぞれのツールを使いこなす必要があり必要な詳細度の情報が得られないことがある
  • 特定のサービス、RPCを特定するためコンテキストを付与した情報を得るためにeBPFを使う
  • 具体的なソリューションとしてはCilium/Hubble、Pixie、Flowmillなど
  • Futureの部分でFargateでeBPF使えるようにしたいという話が出てQAで早々に突っ込まれて盛り上がっていた
  • 個人的にもこのあたりのレイヤの機能提供の話がAWSから出てくると思ってなかったので今度の動きがやや気になるところ

Extending systemd security features with eBPF

  • セキュリティまわりは大体スルーしてしまったけどsystemdでも使われているという話
  • systemdでファイルシステムのタイプに応じてアクセス可否を制御したり特定のネットワークデバイスへの制御を行う実装をeBPFでしているというお話とデモ

Getting started deploying and running BCC in your Kubernetes Cluster

  • Kubernetes ClusterでBCCツールを動かすステップの解説、大体以下のような流れだったと思う、まあそうりますよねいう印象
  • initコンテナで カーネルヘッダをもってくる
  • BCCツール用コンテナを用意する
  • DaemonSetで、privilegedで動かす or capabilityをセットして動かす(カーネルバージョンが低くてcapabilityは試せてないって言ってたかも、コード的にはprivilegedとcap_sys_adminつけてそう
  • 必要な諸々のボリュームをマウントする
  • コードは↓なのでこれで試せる
  • (しかしやはりちょっと大変だなという印象・・

GitHub - mclenhard/ebpf-summit

ということで

  • 前回のサミットからもう1年経っており主にk8s環境まわりの可観測性みたいなところはまだホットなトピックなんだよなーというのがありつつ
  • 個人的にはKernel5.8以降の環境がぼちぼち使えてきそうな気配を感じるので、手軽に動かせる単体のexporter実装みたいなのが扱いやすそうな気がしてきたのでちょっとやってみたいですね

WFH2021

WorkFromHomeなスタイルが本格化して1年近くなり、数々のプラクティスやイケてる超絶スタイリッシュなデスクに憧れをいだきつつも全然そこまでではない、しかし開始時よりは良いQoLを目指し幾分整った感のある弊WFH環境を書いてみるやつ。

開始時からの変化としては、クラムシェル運用にしてデスク上を広くした、可能な限りワイヤレス化した、音を幾分よくした、以上、みたいな感じで終わるわけですが、まあまあ記録として。

before

f:id:egmc:20200301213540j:plain
before

after

f:id:egmc:20210218231738j:plain
after

いやあんまり変わってない。がまあ本体をクラムシェル運用にしてデスクの下に追いやったのでそのあたりはすっきりしてよくなった感。

デスク上が広くなったので、今まで棚上に配置するしかなかった49鍵のDTM用キーボードを配置できるようになったのはよき。

f:id:egmc:20210206203606j:plain
keyboard

クラムシェル運用

f:id:egmc:20210218225138j:plain
under my desk

デスク下にセリアで調達した網を固定し、フックを3点上下に設置してmacbook pro/airを差し替えできるように配置。 
当初Bauhutteのケーブルオーガナイザーとか、あのあたりを使えばいいかなーと考えていたものの、100均チャレンジで失敗してもまあよかろうということでやってみて、まあまあうまくいったんでないかと思う。
最終的に使ったものはこの網、アイアンバー、フック類、などトータルで1000円いかないくらい。 細かい部分の固定は安定の結束バンド(これもセリアのやつ)で。

ハブ類はtype-cのハブから有線LANやらワイレレスマウスのレシーバーやらを接続して固定しており、本体を差し替えればそのまま使えるように。

キーボードはbluetoothで複数端末をswitchできるということでこいつを(フルキーボードじゃなくてよかったしthinkpadのやつなども悩んだが結局素直にmac配列使いたい、ということでこれに)

マウスに関してはマイクロソフト製品を全面的に信用しているので、適当な2.4GHzワイヤレス接続タイプのマウスを使っている、bluetoothと違って天板を挟むと反応が非常に悪くなるので、仕方なくデスク上までレシーバーが出てくるように配置している。

その他の機材としてはモニタ&モニターアームを導入して、アームはとても良かったのだけど、IKEAの安デスクにモニターアーム二個取り付けたら重さで盛大に傾いてしまったしタイプ動作によってまあまあ揺れるので、デスクもしっかりしたものをチョイスすべきだなあという反省。次にQoLを高めるのはこれかな・・。

その他機材

マイク

f:id:egmc:20210206203507j:plain
BETA57

マイクはたまたま家に二本転がっていたSHUREのBETA57とこれも使ってなかったマイクスタンドとポップガードを引っ張り出して使っている。
USBのコンデンサマイクなども良いけど、実際ノイズも結構乗ったりすると思うので、単一指向性のダイナミックマイクをスタンドで使うというのも音質面ではそれなりに悪くないんじゃないかと思う(ミーティングと英会話にしか使っていないが)。

オーディオインターフェース

WFH開始時、マイクとスタンドはあってもオーディオインターフェースFireWire接続のものしかなく、2021年においてこいつをつなぐ術を模索するよりは昨今の安いUSB-A接続のものを合理的であろうということで割と序盤でポチったやつ。 当初Behringerの激安製品UM2 U-PHORIA を導入しそれなりに満足していたが、最近M-AUDIOから新たなる激安の刺客が来たので乗り換えてしまった。 どちらもそれなりに良かった。 UM-2の方が若干音声入出力が始まるタイミングのノイズが大きく、それだけがやや気になったが、概ね音質面ですごい差があるってほどではなく。

UM-2は5000円程度で購入しメルカリで3000円で売って利益2000円くらいだったので、結構コスパよく使えたという気分。ありがとうメルカリ。

ということで

このへんはQoLの高まりとしてよかったのは安オーディオインターフェースと各種ワイヤレスソリューションというベタな感じ締めつつ。

100均チャレンジは、最悪しくじってもいいというレベルのものであれば試していくのはよきですね。

Process ExporterとTreemap for Grafanaでプロセスごとのメモリ使用量をざっくり可視化する

タイトルまま。 いい加減Grafana力を高めようと最近Grafonnetに入門し、Process Exporterをソースとした有益なダッシュボードを作りたいなーとあれこれ試していた過程でTreemapプラグインを発見し、Datadogで以前みかけたメモリマップみたいなビュー作ったら良さそうだなーということで作ってみた。

f:id:egmc:20210117185020p:plain

環境

Process Exporterは2環境で動かしていてOSはそれぞれUbuntu xenial、bionic

  • Grafana  7.2.0
  • Treemap plugin 0.5.0
  • process-exporter 0.7.5

grafana.com

github.com

Process Exporterの設定

Process Exporterの設定はちょっと癖のある感じだが、どの単位でグルーピングしたいか?を定義して上から順に試行してマッチしたところにまとめられる、みたいな動作だと思っている。
のでこれはまとめたいみたいなものを上の方に条件として書いていき、最後にどこにもマッチしなかったら実行ユーザー単位でまとめるみたいな設定を書いている。

process_names:
  - comm:
    - php-fpm7.2
    - mysqld
    - memcached
    - apache2
  - name: "exporters"
    cmdline:
    - '/usr/local/bin/.+[_-]exporter'
  - name: "{{.Username}} processes"
    cmdline:
    - '.+'

exporter系はまとめたいなーとかそういう扱いをしているが、とりあえず実行ユーザー単位でまとめておけばカーディナリティも爆発せず割と推測できるレベルにまとまるのであまりがんばらなくてもいいんじゃないかと思う(ubuntuのデフォ環境だったらwww-dataだったらapacheの子プロセスだよねーとか)。

process exporterでまとまった結果はプロセス数もメトリクスとして持っているので、たとえば「長期的にapacheの1プロセスあたりが使うメモリが増えてきている」みたいな傾向をみたりするのにも使えるのでうまく使えばベンリなんでないかと。

グラフの定義

ダッシュボードが二個混在していて分かりづらいですがこんな感じで。 Treemapはtextとnumericの値をもったtable的な構造を期待しているので、Rangeとしての結果ではなくInstantオプションを指定して最新の値のみを使うように。

github.com

↑はgrezzlyでapplyすることを前提にしているのでいちおう出力結果のjsonはこんな感じ。

2021/2/14 追記

ある程度汎用的に定義しなおしてgrafana.comの方にあげてみました

grafana.com

2020

特に技術的な話でもないやつ、あまりに書く意味あるのみたいなことを書くのもどうかという気持ちになるくらいおっさんになったものの、まあなんかたまにはええやろというのと、そもそもかつてインターネッツはそういうところだったよね、という気分になったのでやる気があるうちに書いておくメモ。

SRE NEXT

sre-next.dev

人生初登壇、というわけではないのだけど「初proposal出して通ってお話したやつ」ということで結構個人的に頑張ったなあというやつ。
イベント自体も非常によかったしこれ本当に翌月以降の状況をみるにできていてよかったですねーーと思わざるを得ない。
懇親会も含め非常に体験がよかったので、次回というものがあるのか、どういう形になるのか、というか運営の皆様マジお疲れ様ですと思いつつ機会があるのならばぜひ参加したいイベント。

ちょうど人はなぜ登壇するのかみたいな話があがっていたりいなかったりするようですが、これに関しては結構思うところがあり「SREは盛り上がってるしSRE本で紹介されているプラクティスは素晴らしいが他方これは基本的にGoogleの話であり、規模もサービスの性質も異なる我々は実際どのようにこのプラクティスを自組織に適用していったり、あるいはしなかったりしているのかがみんな知りたいんじゃないの?というか自分はめっちゃ知りたいんだが・・」ということでGoogleが提示する理想と現実の我々、みたいなところをお話し、自分としては各組織感の差分を知っていきたいみたいなモチベーションで内容を考えてproposalを出すに至ったというところでした(ので内容もなるべくこういうプラクティスではあるけどうちはこうやったよみたいなところを意識してスライドを作った、つもり)。

個人的に反省点はめっちゃあり、人はおっさんになってもこんなに緊張するんかというくらい緊張したし、当初時間の少ない枠を選択し、時間に対しこれくらいの内容を提示できれば良いという方向で考えていたもののいざスライドを作っていくと連鎖的に説明が必要になる内容が増え、結果的にだいぶ駆け足になってしまい聞き苦しかったのではないかーーすいませんというところが非常にはい。

次登壇の機会があればこのあたりもっとペース配分考えてうまく作っていきたい。

英語

2018年のre:invent、2019年のSREcon19 Americasと続けて海外カンファレンスに参戦する機会があり、どちらもそれなりに良い体験であったもの、セッションをもうちょい自然に聞き取れるようになりたいなーということで人生において割と最高潮に高まっていたところ、リモートワーク環境も整ったというのもあり5月からレアジョブでオンライン英会話を開始した。 一回あたりのレッスン費用を考えると大変お安いのだが生来の貧乏性と半ば意地もありオフィシャルな休校日を除き開始から毎日継続している。基本カレンダー抑えて、超絶難しい教材を選択しない限りは会話、たのしい、くらいなのでそんなに頑張っているという感じではないがとはいえ予定があったりお酒飲みたかったりしたりする日もあるので調整しつつ継続している感じで。 出社がなくなり単に家族以外と話をする機会をもつ、という意味においてもまあ良いんじゃないかと思っている、微妙な英語力でもアニメの話したり、興味のある話をするのはまあまあたのしい。

9月頃TOEICのオンライン公開模試があって試しに家でやってみたところ800前後のまあまあよさげなスコアが取れたので、これはまあ少なくとも下がることはないだろうということで、会場の抽選にもあたったので実に16年ぶりくらいにTOEICを受けスコアを更新した。680->800ちょいくらい。

海外カンファレンスがオンライン開催になって参加しやすくのでeBPF Summit 2020、SREcon20 Americasはリアタイ参戦してみたが、レベル的には幾分ましにはなったもののやはりスピードがはやく抽象的な話に寄ってるものは厳しく、結局語彙の不足と英語発音が単純に聞き分けできてないってことで聞き分けとスピードの方は発音がーという一周まわってまあ地道にやるしかないですよねえ、ということで。
レッスンでも語彙が増えないわけではないのだけど、まあ会話だけしてても急に伸びたりはしないよねーという当たり前の話なので覚悟を決めて発音の訓練をやらんとなあ・・という結論になって終わった2020、でした、ね。ということで引き続き地味にやっていき。

技術方面

eBPF関連がたのしくBPF Performance Toolsを会社のみなさまの力を借りつつ読みすすめたりしつつ、特に現在語られる文脈とはやや違うものの、Brendan Gregg氏のいうプロダクション向けの継続的なSuperpowerなObservabilityツールとして有益な意思決定につなげる運用に持ち込みたいなーと目論んでいきたいとい2021ということで、周辺技術とか低レイヤーな方面を引き続きもうちょいなんとかしていきたいところ。

eBPF+USDTでphpをトレースしてみる、php-fpmとmod-phpでもやる

前回から気づけばだいぶ経過してましたが

dasalog.hatenablog.jp

実際phpスクリプトが動いてる環境はmod-phpであったりphp-fpmであったりするので、そのあたりは結局動かせておらず

このあたりも試してみたのでいちおう記録として。 個人的な環境のアレによりphp-fpmの方はubuntu bionic、mod-phpの方はfocalで動かしたがまあ特にやることは変わらないはず。

結局動かすためにはプロセスに対しUSE_ZEND_DTRACE=1環境変数をセットすることが必要で、「どこから仕込むことができるか?」ということだけですね、ということで基本はsystemdの設定を上書きする方法でトレースは実行できました。

環境は上述の通りubuntu前提ですがsystemdレイヤーでやっているので、他の環境でも同様にできはするかなと(ubuntu以外で--enable-dtraceされてるバイナリが提供されてるのかどうかは定かではないですが・・

php-fpm

systemdの設定を上書きするunitファイルを仕込んでみます。

sudo cp /lib/systemd/system/php7.2-fpm.service /etc/systemd/system/php7.2-fpm.service

して /etc/systemd/system/php7.2-fpm.serviceEnvironment="USE_ZEND_DTRACE=1" を追加して設定反映、再起動すればOK

$ cat /etc/systemd/system/php7.2-fpm.service
[Unit]
Description=The PHP 7.2 FastCGI Process Manager customized
Documentation=man:php-fpm7.2(8)
After=network.target

[Service]
Type=notify
PIDFile=/run/php/php7.2-fpm.pid
ExecStart=/usr/sbin/php-fpm7.2 --nodaemonize --fpm-config /etc/php/7.2/fpm/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID
Environment="USE_ZEND_DTRACE=1"

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl restart php7.2-fpm.service

おもむろにbpftraceでトレースすると対象プロセスでUSDTがトレースできることができる。

$ sudo BPFTRACE_STRLEN=200  bpftrace -e 'usdt:/usr/sbin/php-fpm7.2:execute__entry { printf("%s, %d\n", str(arg0), arg1); }' -p `pgrep -u www-data php-fpm|head -n1`
Attaching 2 probes...
/var/www/html/info.php, 2
, 0
/var/www/html/info.php, 2
, 0
...

probeが二個あることになってるのはなんでやろ・・。とりあえず動きはしたのでヨシ。

mod-php

php-fpmと同様にsystemdの設定に書くか、apacheのenvvarsで設定する、どちらでもとりあえず動かせはする。

$ cat /etc/systemd/system/apache2.service
[Unit]
Description=The Apache HTTP Server Customized
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/

[Service]
Type=forking
Environment="APACHE_STARTED_BY_SYSTEMD=true" "USE_ZEND_DTRACE=1"
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl stop
ExecReload=/usr/sbin/apachectl graceful
PrivateTmp=true
Restart=on-abort

[Install]
WantedBy=multi-user.target

or

/etc/apache2/envvarsexport USE_ZEND_DTRACE=1 を追記しておく。

おもむろに子プロセスにアタッチしてみれば動く、こちらの環境はlibphp7.4が使われてるのでこんなかんじで。

$ sudo BPFTRACE_STRLEN=200  bpftrace -e 'usdt:/usr/lib/apache2/modules/libphp7.4.so:execute__entry { printf("%s, %d\n", str(arg0), arg1); }' -p `pgrep -u www-data apache2|head -n1`
Attaching 1 probe...


/var/www/html/test.php, 2
/var/www/html/test.php, 2
/var/www/html/test.php, 2
...

その他

bpftraceで --usdt-file-activation 使えるとプロセス特定してアタッチみたいなところが幾分楽できそうな気がする。

まとめ

ということで若干あやしいところもありつつトレース自体は動かせた。

いずれにせよ USE_ZEND_DTRACE=1 したプロセスしかトレースはできないし、php-fpmにせよmod-phpにせよ子プロセスは入れ替わっていくので、アドホックなトレーシングはともかく「追っかけ続ける」仕組みをつくるのはしんどそうだなーとか、例えばexecute__entryを逐一標準出力に出していくみたいなのはもちろんプロダクションでおもむろにやるにはオーバーヘッドが許容できないであろうということは容易に想像でき、じゃあBPF_MAPを使うのか、どの粒度で情報を保持するのか、そもそもそここまでしてUSDT使うのか、みたいなところでやはりなかなか使い所はむずかしいなーという印象です。動かせるのは楽しいんですけどね。

全体に雰囲気で動かしているのでもっとうまくやる方法があればすごく知りたいところ・・。

参考

ubuntuのsystemd設定上書きについてはこちらの記事を参照させていただきました

第598回 systemdユニットの設定を変える:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

eBPF+USDTでphpをトレースしてみる、bpftaceとRbBCCでやる

最近プロダクション環境での諸々の調査に使いたいというモチベーションで BPF Performance Tools (Book) 読み進めつつ、提供されているbcc-toolsを試したりbpftraceでlibほげほげの関数パラメータを抜いたりしていた、のだがUSDTを使用したトレースはうまく動かせず悶々としていた。

正直USDTはパッケージ入れておけばいきなりproductionでさくっと使える、という感じでもなく当初の目的からはやや外れているのだが、機構があるのに動かせないというのはなんとももどかしいので、諸々試行錯誤してとりあえず動いた、というころまでの記録を残しておく。対象はphpで。

なぜphpなのかというと、プライベート的にもお仕事的にも馴染みがありワンチャン使える可能性もあるのでは・・という期待と、ツールを試す過程で標準パッケージのphp

$ tplist-bpfcc -l /usr/bin/php
b'/usr/bin/php' b'php':b'request__startup'
b'/usr/bin/php' b'php':b'request__shutdown'
b'/usr/bin/php' b'php':b'compile__file__entry'
b'/usr/bin/php' b'php':b'compile__file__return'
b'/usr/bin/php' b'php':b'function__return'
b'/usr/bin/php' b'php':b'function__entry'
b'/usr/bin/php' b'php':b'execute__entry'
b'/usr/bin/php' b'php':b'execute__return'
b'/usr/bin/php' b'php':b'error'
b'/usr/bin/php' b'php':b'exception__thrown'
b'/usr/bin/php' b'php':b'exception__caught'

を観測したためこれはもしや--enable-dtraceされてるのでは・・となり、試してみたくなったため。

結果的にUbuntuなら先日リリースされた20.04 LTSのイメージで特に独自ビルドなどは頑張らなくてもいくつかのパッケージインストールのみで(一応)動かすこと自体はできることがわかった。

環境

AWS上でUbuntu 20.04 LTS (Focal Fossa) のインスタンスを立てて動かした。

ami

$ echo `curl -s http://instance-data/latest/meta-data/ami-id`
ami-0c1ac8728ef7f87a4 # ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200423

php

ii  php7.4-cli                       7.4.3-4ubuntu1                    amd64        command-line interpreter for the PHP scripting language

ruby

ii  ruby2.7                          2.7.0-5ubuntu1                    amd64        Interpreter of object-oriented scripting language Ruby

bpf関連

ii  bpfcc-tools                      0.12.0-2                          all          tools for BPF Compiler Collection (BCC)
ii  bpftrace                         0.9.4-1                           amd64        high-level tracing language for Linux eBPF
ii  libbpfcc                         0.12.0-2                          amd64        shared library for BPF Compiler Collection (BCC)
ii  python3-bpfcc                    0.12.0-2                          all          Python 3 wrappers for BPF Compiler Collection (BCC)

サンプルスクリプト

手頃なものがなかったのではるか昔書いた雑スクリプトの先頭に(pidをアタッチする時間を稼ぐために)sleepを追加して試した、今みるとコード的にもなかなかあれだが、まあサイズ的にもコンパクトでいいかな、ということで・・。

https://github.com/egmc/dscheck

実行条件

  • 環境変数 USE_ZEND_DTRACE=1 を設定してDTrace Supportを有効にしたうえでphpスクリプトを実行する
  • ↑で実行したプロセスのpidを対象にトレースを実行する

phpの場合、--enable-dtraceでビルドしただけでは不十分で USE_ZEND_DTRACE=1 で実行しないとDTrace Supportが有効にならない模様。

git.php.net

. Disabled PHP call tracing by default (it makes significant overhead). This may be enabled again using envirionment variable USE_ZEND_DTRACE=1.

上述の通りfocalの標準パッケージは--enable-dtrace自体はされてるようなのでこれでいける

$ /usr/bin/php -i |grep -i dtrace
DTrace Support => available, disabled
$ USE_ZEND_DTRACE=1 /usr/bin/php -i |grep -i dtrace
DTrace Support => enabled

試しに --enable-dtrace なしでビルドしたphpは以下のようになっていた

$ /usr/local/php7.4-no-trace/bin/php -i |grep -i dtrace
DTrace Support => disabled

bpftaceでトレースする

上述のphpスクリプト$ USE_ZEND_DTRACE=1 php dscheck.php)を実行させつつ、別窓でトレースを実行してテスト

probeのパラメータはここで何が取れるのか確認

autoload処理、関数呼び出しが観測できる、arg1はlineno

# BPFTRACE_STRLEN=200 bpftrace -p `pgrep php` -e 'usdt:/usr/bin/php:execute__entry { printf("%s, %d\n", str(arg0), arg1); }'|head -n10
Attaching 1 probe...
/home/ubuntu/dscheck/vendor/autoload.php, 5
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 74
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 21
/home/ubuntu/dscheck/vendor/composer/autoload_real.php, 11
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php, 446
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 38
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 30
/home/ubuntu/dscheck/vendor/composer/autoload_static.php, 32
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php, 302

compile__file__entryスクリプトコンパイルを覗いてみる(arg0が char *compile_file のはずだがこちらは空になっている・・

# BPFTRACE_STRLEN=200 bpftrace -p `pgrep php` -e 'usdt:/usr/bin/php:compile__file__entry { printf("%s\n", str(arg1)); }'
Attaching 1 probe...
/home/ubuntu/dscheck/vendor/autoload.php
/home/ubuntu/dscheck/vendor/composer/autoload_real.php
/home/ubuntu/dscheck/vendor/composer/ClassLoader.php
/home/ubuntu/dscheck/vendor/composer/autoload_static.php
/home/ubuntu/dscheck/vendor/composer/../swiftmailer/swiftmailer/lib/swift_required.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Parser.php
/home/ubuntu/dscheck/vendor/composer/../../src/DsCheck/DsCheck.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Inline.php
/home/ubuntu/dscheck/vendor/composer/../symfony/yaml/Symfony/Component/Yaml/Unescaper.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/swift_init.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/../mime_types.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/preferences.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php
/home/ubuntu/dscheck/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php

なおpidを指定せずにトレースしようとすると以下のように怒られる

# BPFTRACE_STRLEN=200 bpftrace -e 'usdt:/usr/bin/php:compile__file__entry { printf("%s\n", str(arg1)); }'
Attaching 1 probe...
Error finding or enabling probe: usdt:/usr/bin/php:php:compile__file__entry

RbBCCでトレースする

udzuraさんのこちらのエントリでrubybccできるぞ、というのを試してみたく

udzura.hatenablog.jp

こちらのサンプルを写経させて頂き、probe対象を変更してfunction名を取得するだけのサンプルを試してみた。 (というか実際のところはところこちらを先に動かして、あれ、とれるやんけ・・となりbpftrace側でも同様にトレースできることを確認出来たという流れ)

require 'rbbcc'
include RbBCC
pid = ARGV[0]&.to_i || raise("Usage: #{$0} PID")

bpf_text = <<BPF
#include <uapi/linux/ptrace.h>
struct data_t {
    u64 ts;
    char function[128];
};
BPF_PERF_OUTPUT(events);

int do_trace_function_entry(struct pt_regs *ctx) {
    struct data_t data = {};
    data.ts = bpf_ktime_get_ns();
    bpf_usdt_readarg_p(1, ctx, &data.function, sizeof(data.function));
    events.perf_submit(ctx, &data, sizeof(data));
    return 0;
};
BPF

u = USDT.new(pid: pid.to_i)
u.enable_probe(probe: "function__entry", fn_name: "do_trace_function_entry")

b = BCC.new(text: bpf_text, usdt_contexts: [u])
puts("%-18s %s" % ["TIME(s)", "function"])
start = nil
b["events"].open_perf_buffer do |cpu, data, size|
  event = b["events"].event(data)
  start ||= event.ts
  time_s = ((event.ts - start).to_f) / 1_000_000_000
  puts("%-18.9f %s" % [time_s, event.function])
end
Signal.trap(:INT) { puts "\nDone."; exit }
loop { b.perf_buffer_poll }

こんな感じでトレースできる

# ruby phpusdt.rb `pgrep php` | head -n20
TIME(s)            function
0.000000000        main
0.000193415        main
0.000223335        getLoader
0.000248361        loadClassLoader
0.000652416        main
0.000740099        main
0.000760310        getInitializer
0.000781820        Composer\Autoload\{closure}
0.000799492        register
0.000818768        composerRequirea069f98d7345659050bcc164f0155069
0.000888641        main
0.000979718        main
0.000999792        registerAutoload
0.001022639        loadClass
0.001038429        findFile
0.001054807        findFileWithExtension
0.001089370        Composer\Autoload\includeFile
0.002951398        main
0.002981065        __construct
Possibly lost 227 samples

pythonバインディングもそれほどコード書くわけではないのだけど、gemでさくっと自前のbccツールを、みたいなのが出来るのはいいかもしれない・・。

ということで

なんとかphpでUSDTを使用したbcc/bpftraceでのトレースを実行することができた。

少なくともfocalではphpの再ビルドせずに使うことはできるものの、依然条件は厳しく、PHPであればnewrelic等のAPM使うなど、そこまでこれでなにかを観測したいという強いモチベーションもなく、どうかなーというところですが。

compile__file__xxx でopcodeのキャッシュ状況みるとかワンチャンどうかなーと思いつつ、実際phpスクリプトが動いてる環境はmod-phpであったりphp-fpmであったりするので、そのあたりは結局動かせておらず

ついでにメモ

github.com

bccのレポジトリにも各言語のトレースサンプルが存在している、がこちらは

bpf_probe_read_user missing (added in Linux 5.5).

ということでカーネル5.4.0なfocalでは動作しなかった。