Islands in the byte stream

Technical notes by a software engineer

BPF CO-RE は GitHub Actions の Ubuntu 20.04 インスタンスでテストできそう

BPF CO-RE (CO-RE: Compile Once , Run Everywhere) を実行するにはLinux kernelがBTF (BPF Type Format) つきでビルドされている必要があります。

Ubunt 20.04 の最初のリリースではBTFは有効になっていませんでしたが、アップデートで配信されているカーネル 5.13.0-39-generic ではBTFが有効なのでカーネルのアップデートをするだけでBPF CO-REが使えます。

GitHub Actionsの場合、デフォルトのカーネルではなくAzureのためのカスタムカーネルですが、こちらも最近のインスタンスBTFが有効になっているので、特別なことをしなくても BPF CO-RE をテストできそうです。

github.com

  • ci.yml - workflow file、ただし実際には make を呼ぶだけ
  • Makefile - 依存関係のインストールやビルドなど

これで BPF CO-RE を使ったツールも開発し放題ですね!

Firefoxの「プライバシーとセキュリティ」の設定を変えたらアプリの再起動が必要

追記: これをしてもはてなブログのヘッダでログイン状態にならない現象が起きてしまうようです。もはやFirefoxのバグなのかはてなブログのバグなのかわかりませんが…。


Firefoxの「プライバシーとセキュリティ」を変えると次のようにタブの再読み込みを行うUIが出ますが、少なくともFirefox v98現在はタブの再読み込みだけではなく Firefox自体の再起動が必要です

「これらの変更を適用するにはタブを再読み込みする必要があります」「すべてのタブを再読み込み」

そうしないと少なくとも一部のクッキーの挙動などが変わり、「はてなブログ」のヘッダではてなアカウントがログイン状態にならないようです(はてなブログFirefoxのプライバシー設定が「厳格」だとヘッダがログイン状態になりません)。

このせいでずっとはてなブログFirefox未対応だと勘違いしていたのですが、実際にはどうもFirefoxのバグっぽいなということがわかった次第です。

GitHub Pull-ReqでCIの完了をデスクトップ通知するChrome拡張 "WatchRaptor" を作った

Chrome Web Store

chrome.google.com

使い方ですが、GitHub PR pageに次のようにCI statusにcheckboxが現れるので、完了通知がほしいCI statusにチェックをつけるだけ。

f:id:gfx:20220118214310p:plain

チェックされたCI statusが完了(success or failure)になると、次のようなデスクトップ通知が出ます。この完了したときの通知をクリックすると該当のGitHub PR pageのタブをアクティブにします。

f:id:gfx:20220120093634p:plain

tab idごとにcheck状態をもっているので、リロードしてもtabごとのcheck状態は維持されます。

現在(v0.9.2)の機能はこれだけです。既知のバグとして、GitHubのPR画面で差分をみたりdiscussionに戻ったりしているとcheckboxが出ないことがありますが、そういう場合はリロードするとcheckboxが出ると思います。そのうち直します。

Repository

github.com

実装はTypeScript、UI(といってもcheckboxだけですが)はReact、パッケージングはwebpackを使ってます。構造がちょっと面白い、そのあたりの詳細の話もそのうち書きます。

なお、この "WatchRaptor" (ウォッチラプトル)という名前ですが、子供が恐竜にハマっているので "watchdog" (「番犬」)をもじって恐竜っぽい名前にしてみたという感じです。

M1 Mac + Intel NUC (mini PC) で快適AMD64 Linux生活

2021年現在、M1 MacでDockerを使うのが大変という話がちょいちょいあります。

個人的な事情でいうと、私は現在、AMD64 Linuxでしか動かせないソフトウェアを開発していて、Dockerもちょいちょい使う、という感じです。なのでAMD64 MacVMWareを使ってUbuntu VMを使って開発しています。

一方で、新規で購入できるMacbook ProがM1しかない、デスクトップマシンとしてWindowsUbuntuを使うことにまだ踏ん切りがつかない(なんだかんだで10年Macで仕事をしてきているので…)ということもあり、どうしたものかと思っていましたが、会社が開発機としてIntel NUCを用意しているというので申請して導入したことで、一気に諸問題が解決しそうだなというところに至りました。

Intel NUCはIntelが作っているmini PCで、サイズ的には幅12cm、 奥行き12cm、高さ4-6cm(具体的な大きさなモデルによる)くらいなのであまり物理的な場所をとりません。この中に開発環境を作ってSSHなりvscode remoteなりで入ればいいというわけです。私の場合はかれこれ2年ほどVMWareUbuntu VMを作ってその中にSSHして開発していたので、開発体験的にもあまり変わらないはずです。

今回は NUC10i5FNH (NUC 第10世代 with Core i5)をセットアップしました。

Intel NUC 10 Performance kit NUC10i5FNH Product Specifications
(Recommended Customer Price: $414.68 - $417.14)

この筐体はSSDもRAMもついてないので、適当に入手して自分でつける必要があります。

Ubuntuを入れるときはUbuntuのサイトにIntel NUCを使うときのやり方や、必要な周辺機器があります。ただし、私はUbuntu DesktopではなくUbuntu Serverを入れました。どのみちSSH経由でしか使いませんからね。

Install Ubuntu Desktop on the Intel® NUC | Ubuntu

起動ディスクの作り方が最初わからなくてハマっていましたが、上記サイトのインストラクションをよく読んでいないだけでした…。Macで起動ディスクを作る場合はこれまた下記のとおりUbuntuのサイトにやり方が書いてあります。

Create a bootable USB stick on macOS | Ubuntu

なお、セットアップ時に日本語キーボードを認識できずUSキーボードと検出されてしまったのですが、どうせSSHするだけなので特にがんばって日本語キーボード対応したりはせずスルーしました。

まだセットアップしたばかりなので本当のところはよくわかりませんが、会社が開発機としてIntel NUCを支給できるならこれはベストの開発環境といえるのではないかと思っています。

set -eのもとで特定のコマンドの終了ステータスを変数に入れるシェルスクリプトのスニペット

課題編

シェルスクリプトで「あるグローバルな状態を変える操作を行い、その結果をチェックし、状態をもとに戻す」みたいなタスクをするときに「その結果をチェックし」のところでコマンドの終了ステータスを変数に入れて置きたいみたいなことがあります。例えば、次のようなコマンド操作です。

set -e

# グローバルな状態を変える操作を行う
git merge --no-ff --no-commit $main_branch || true

# 結果をチェックしてexit codeを変数に入れる
git diff --cached --exit-code --quiet ; code=$?

# グローバルな状態をもとに戻す
git merge --abort

# 上位プロセスに結果を渡す
exit $code

スクリプト全体には set -e (コマンドが失敗するとシェルスクリプトが即座に終了する)を効かせていると、このままだと git diff --exit-code ... で差分があるときに終了ステータス1を返してシェルスクリプトが終了してしまいます。これをシェルスクリプトを終了させるに終了ステータスを得たいというわけです。

解答編

やりかたは色々ありました。

-e の挙動はポータブルではなくシェルによって挙動が違うので set +e での無効化が必要

どれでも正しく動くのでいいと思いますが、 code=$(set +e ; command ; echo $?) はポータブルで比較的メジャーな構文のみ使い、グローバルの状態に依存したりグローバルの状態を一時的にでも変えたりせず、意図が明確なので今回はこれでいこうかと思います。

Findy Engineer Labに Individual Contributor の話を寄稿しました

engineer-lab.findy-code.io

詳細は記事を読んでいただきたいのですが、おかげさまで「ICという役割を初めて知った」「自分もIC的な働き方をしたいのだと分かった」という感想が複数みられたので、この記事を書いた甲斐はあったなあと思います。

一方でやや誤解というか説明が不十分だったかなと思うのが、「ICを役割としておいている企業では、ICはプレイヤーのデフォルトの役割である」ということです。Fastlyではそうですし、たぶんICを置いている他の企業でもそうです。ICはスタープレイヤーだけに許された特別な働き方では決してありません。私はIC的に働いてきて今後もICで行きたいと思いますが、ICと管理職(ピープルマネージャ)を行き来するという選択をとってもいいですし、むしろICとピープルマネージャを行き来するほうがICとしての成長に繋がりますよという主張もあります。

ともあれ私としてはIC ladder(ICという出世の階段)を置いてくれたほうが働きやすい(=今後の転職の選択肢になり得る)し、そういう会社が増えてくれると良いなと思う次第です。

Fastlyに入社して2年たちました

在職エントリってやつです。

gfx.hatenablog.com

2019年9月9日に現職に転職して今月で2年たちました。ウェブ系でRailsやReactなどをやっていた前職から活動分野を大きく変えて、C言語などでミドルウェアを開発する現職にきて、さらに英語が共通語の環境ということでうまくやっていけてるか心配でしたが、なんとかギリギリやっていけてるという感じです。

IC(Indivisual Contributor; マネージメントをしない専門職)としては、技術力が高ければ高いほど、専門知識があればあるほどダイレクトに仕事の成果に直結する会社なので、そのあたりは非常に楽しいです。「毎日楽しく開発したい」というのが私が仕事にもっとも期待することであり、それが達成できているかぎりは現職にいるでしょう。

2017年9月に生まれた子供(ハンドルネーム: mfx)もいまや4歳になり、どんどん口達者になってきました。またコロナ禍によって生活スタイルも激変し、オフィスが閉鎖されて100%自宅作業となって1.5年経ちました。仕事面でも生活面でも、振り返ると大変な2年間だったなあと思います。禍福は糾える縄の如し…。

ともあれ、次の一年も無事に過ごせますように🙏

GitHub ActionsからGitHub wikiを更新する

GitHub ActionsからGitHub wikiを更新したいことがたまにあります。たとえば、何かのメトリクスを見やすく整形したものなど、repositoryのデータを何らかの形で加工したドキュメントを作りたいときです。コード生成したmarkdownドキュメントをコミットしてもいいですが、それよりはシンプルで運用が楽です。

今回は、GitHub repoで管理する原稿の文字数(など)を継続的に見れるページを作ると便利かなと思って作りました。自分一人だったらローカルで適当なツールを叩けばいいですが、同repoを見れる編集者にも共有したいとなると独立したページがあるほうが便利ですからね。

リポジトリはこんな感じです。

github.com

基本的には、 actions/checkout を使って "${{ github.repository }}.wiki" をcloneして編集してpushするだけです。 actions/checkout を使うことで適切なGitHub access tokenが生成されて使われるので、private repoでもcloneしたりcommitしたりできます。

git configの user.email が次のようになっており、少しトリッキーに見えるかもしれません。

git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"

しかし、このように設定することで実際のアカウントのメールアドレスを使うことなくGitHubのユーザーと適切にひも付きます。まあ、これはGitHub wikiに限らずGitHub Actionsからcommitするとき常に有効なハックですが。あとはまあ、YAMLに書いてあるとおりです。

Enjoy GitHub Actions!

Kindle Fire HD 10" が固定レイアウト書籍やPDFを読むのにとてもよい件

クリスマスプレゼントで妻からもらったKindle Fire HD 10"が思いの外よくて快適だなという話です。

Kindle Fire HD 10" (amazon.co.jp)

これまで電子書籍リーダーとしてはE-InkのKindleをここ数年ずっと使っていて、それはそれで軽量&電池長持ち&目に優しい仕様で十分に満足しているのですが、E-Ink端末は画面が小さくて固定レイアウトの書籍がとても読みづらいのです。というか、あまりに読みづらいので買っても読まないし、だんだん買わなくなってきたというのが実情でした。

物理書籍にはペラペラめくりやすいなどのいい面があることは重々承知しているのですが、とにかく物を増やしたくないし、手軽に手に取れる端末とちがって物理書籍を読む心理障壁もだんだん上がってきたなかで、(統計はとってませんが感覚として)電子書籍のほうが読了する率が上がってきていました。ここ数年は、物理書籍を年間10冊も買わないみたいな生活です。かといって固定レイアウトの書籍が読みづらいため読めないということをよしてとして受け入れているわけでもなく、ずっと心に引っかかっていたのでした。

そんな折に今回手に入れた Kindle Fire HD 10" は、E-InkのKindle端末を補間するデバイスとしてとてもよいです。大画面で固定レイアウトでも十分に読みやすいし、値段も約2万円で、これはほぼ同サイズのiPad (8th gen) の 3.5万円よりはるかに安価。Android OSがベースなのにGoogle Play storeが使えないせいで利用できるアプリには制限がありますし、1passwordアプリもありません。でも書籍しか読まないので制限があるのはそんなに大きな問題ではないんですよね。

これは久しぶりに使いでのある端末を手に入れたなあということで、大変満足しています。

h2ologにみるBPF toolsの構造と設計

最近はお仕事で h2olog を開発しています。これはC++で書いたBPF toolsで、H2Oに組み込まれたUSDTが出力するイベントをトレースするためのツールです。

具体的にはこんな感じで使ってH2OプロセスをトレースしてJSON-Linesにして出力します:

# QUICを有効にした H2O serverが起動しているとして
$ sudo h2olog quic -p "$(pgrep -o h2o)"
{"type":"accept","seq":1,"conn":2,"time":1594471121930,"dcid":"9546e6940ebeec5f"}
{"type":"crypto-decrypt","seq":2,"conn":2,"time":1594471121930,"pn":0,"decrypted-len":1236}
{"type":"quictrace-recv","seq":3,"conn":2,"time":1594471121930,"pn":0}
{"type":"stream-receive","seq":4,"conn":2,"time":1594471121930,"stream-id":-1,"off":0,"len":212}
...

さて、BPFのためにUSDTを定義するプログラムは、次のようにD言語でUSDTを定義します。

https://github.com/h2o/h2o/blob/master/h2o-probes.d

provider h2o {
    probe h1_accept(uint64_t conn_id, struct st_h2o_socket_t *sock, struct st_h2o_conn_t *conn);
    probe h1_close(uint64_t conn_id);
    // ...
}

これらは関数に見えますが、実際にはむしろ probe イベント名(このイベントの構造) という意味のデータスキーマです。そして struct st_h2o_socket_t * などのデータ構造は、USDTを提供するプログラム自身が定義するデータ構造です。つまり、BPF toolsはトレース対象のプログラムのデータ構造を知っていなければいけないのです。

(ところで、USDTをうまく加工するためのBPF toolsを作るときは、USDT定義ファイルからBPFとBPF toolsの一部を生成するコードジェネレータを用意することになります。ひとつひとつのUSDTに対応するPBF program用のイベントハンドラとそれを(たとえば)JSONに加工するBPF tools側のロジックを手動でメンテナンスするのは、あまりにもコストが掛かりすぎるからです。)

初期の h2olog (toru/h2olog) は、データ構造については妥協してH2Oのソースコードから必要な定義だけを抜きだしたヘッダファイルを用意していました(quic.h)。しかし、この方法はMVPを開発する際には十分でしたが、長期的な開発を見据えるといくつかデメリットがあります。つまり、同期を忘れるとリグレッションが容易に起こりうるし、もともとの構造体が複雑だと(実際複雑なのです!)、「必要な定義だけ抜き出す」というだけでも精査するコストがかかります。

BPF toolsがトレース対象のCヘッダファイルにアクセスするのは、BPF tools自体をC++で書くならば簡単です。そこでBPF toolsが使うBPF programが、トレース対象のデータ構造を参照できればこの問題は解決です。そしてsのための方法をいくつか検討しました。

まず、C structのパーサーを用意してトレース対象のヘッダファイルから情報を抜き出すという方法があります。h2ologでいえば、最初はこの方法でコードジェネレータが quic.h をパースしていました。しかし、実際にH2Oのヘッダファイルをパースするとなると、様々なtypedefやネストした構造体をパースするために、ほとんど完全なCの構造体のパーサを再発明することになります。これは現実的ではありません。

次に、H2Oのヘッダファイル全体をBPF programに埋め込む、または BPF program でH2Oのヘッダファイルをincludeする、という方法です。BPF programの文法はCなので、これは一見うまくいきそうでしたが、結局コンパイルできませんでした。BPF programは構文がCというだけで、まったく異なるコンパイルターゲットです。たとえば、BPF programからはCの標準ライブラリやPOSIX/Linuxの標準ライブラリも使えません。つまりH2Oの構造体がCの標準ライブラリにある構造体を含む場合、BPF programにincludeしてもコンパイルできないのでした。

さらに、pahole(1)でバイナリのデバッグ情報 (DWARF) からCの構造体を再構築する方法 (h2o/h2o#2365-651525032) も検討されました。paholeはDWARFを読んでCのソースコードを出力するため、うまくいけばコードジェネレータとその生成コードは一番シンプルになると思われたのですが、pahole自体は「特定の構造体とその依存関係だけをCコンパイラでコンパイル可能なソースコードとして出力する」という機能はありません。結局paholeの出力したCの構造体をパースする必要があり、今回の目的に使うのは不可能ではないにせよかなり困難でした。

最後に、必要な構造体から情報を抜き出すCプログラムを生成して情報を吐き出させる、という方法です (h2o/h2o#2365-651448880)。これはBPF programを生成するコードジェネレータのためにさらに別のコードジェネレータでCプログラムを生成する必要があるため、コードジェネレータは複雑になります。しかしこのコードジェネレータの二重構造以外はコードジェネレータをシンプルに保てるうえ、長期的にも安定した動作が見込めるため、最終的にはこの方法を採用しました。

pull-req: https://github.com/h2o/h2o/pull/2372

最終的なh2ologの設計はこんな感じになりました

  • gen-quic-bpf.py は、USDT定義ファイル(*.d)を入力にして C の BPF program と C++ の BPF tools を生成する、Python製の code generator
    • gen-quic-bpf.pygenerated-quic.cc を生成する、これが BPF tools の一部で、中に BPF program を文字列リテラルで持つ
    • generated-quic.cc はrepoにコミットする。そのほうが gen-quic-bpf.py の変更の影響を把握しやすいので
  • gen-quic-bpf.py はさらに、「BPF programの一部を生成するコードジェネレータ(C++製のcode generator)」を生成する
    • これはh2ologの実行中にコードを生成する。生成コードは h2olog(1) に -dd を与えることで見られる

BPF toolsプログラムにだけ注目すると、トレース対象プログラム(ここではh2o)と同じコンパイラでコンパイルしたBPF tools(ここではh2olog)が、トレース対象プログラムのデータ構造へのアクセス方法を実行時に構築してBPF programに渡すということです。

h2ologのソースコードはこちらです。

ここではh2ologについてのみ触れましたが、特定のプログラムに特化したBPF toolsを開発するときはいずれにせよこういう設計になると思います。