git tips: git push $remote HEAD で現在のブランチを$remoteにpushできる

git tips: git push $remote HEAD で現在のブランチを$remoteにpushできて、コマンドラインでgitを使うときはよく使います。

ところで、この HEAD の実体はファイル(.git/HEAD)なので、ファイルシステムが大文字小文字を無視する(case-insensitive; e.g. macOSのデフォルト)ときは、 git push $remote head でも動いてしまいます。しかしこれを習慣にしていると、大文字小文字を無視しないファイルシステムで動かずハマることがあります(というかまあ、ハマってしばらく悩みました)。普段から HEAD とタイプする習慣にしておくとよいと思います。

BCCでBPF toolsの開発をするための基礎知識

最近仕事で h2olog を開発しています (H2O の QUIC 層をトレースしよう | Toru Maesaka) 。これはh2oのUSDT からデータを取り出すBPF toolです。その関係でBPFとかBCCをいろいろ触ったので、BPF toolsの開発に必要なことを書いておきます。

なおBCCまわりはまだ細かいところの作りが荒いので、思わぬトラブルがあるかもしれません。幸いpull-reqのレビュー&マージもかなり早いので、気になったところはどんどん直していっています。

ToC

開発環境のセットアップ

Linuxマシンが必要です。Ubuntu 18.04 以降だと簡単にインストールできましたが、Ubuntu 16.04だとビルドまわりでトラブルことが多いです。BPFはLinux 4.xのときに大きく開発が進んだ機能なので、カーネルだけ新しめのものにすることも必要かもしれません。

BCCのビルドはINSTALL.mdにあるとおりです。

トレース対象のプログラムのdtrace supportを有効にする

H2Oの場合は、SystemTap package (Linux用のdtrace)がインストールされていると自動でDTrace supportが有効になります。

トレース対象のプログラムのUSDTを確認する

BCC toolsに付属している tplist(8) (tplist-bpfcc という名前でインストールされる) で確認できます。

USDTは library (path to object), provider, probe という構成要素で、たとえばh2oの場合、h2oバイナリに組み込まれたUSDTは次のように確認します。

# h2o project dirで build/ でビルドした場合
$ sudo tplist-bpfcc -l build/h2o
build/h2o picotls:new_secret
...
build/h2o quicly:new_token_acked
...
build/h2o h2o:h1_close
...

h2o binaryにはpicotls, quicly, h2oというproviderのもとでそれぞれいくつかUSDTがある、ということがわかりますね。

生きているプロセスのUSDTは次のようにして確認できます。

$ sudo tplist-bpfcc -p $(pgrep -o h2o)
...
/proc/63337/root/usr/lib/x86_64-linux-gnu/libc-2.30.so libc:memory_mallopt_mxfast
/proc/63337/root/usr/lib/x86_64-linux-gnu/libpthread-2.30.so libpthread:pthread_start
...

h2oバイナリのほか、リンクされているライブラリのUSDTも確認できました。

開発する

あとは https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md を眺めながら開発するだけ!

実際のコードはこんな感じです。

https://github.com/toru/h2olog/blob/master/h2olog

BCCPython bindingを使っているので、Python code中の文字列リテラルのなかのC言語がメインロジックという、一見するとちょっとつらい感じのコードになってますが、まあC言語なのでsyntax highlightや補完がなくても意外となんとかなります。

h2ologはC++で書き直されました。C++のなかの文字列リテラルにCでBPFプログラムを書くという構造になってます。

用語集

BPF

Berkley Packet Filter。現在は eBPF (extended BPF) と統合され汎用的なトレース技術となっている。Linux kernelに組み込まれた機能。トレーサープログラム (BPF tools) が対象プロセスにアタッチして対象プロセス内で発生するイベントを取得する。

BPFはカーネルに組み込まれているので、バグ修正や新機能追加の恩恵をうけるにはカーネルのアップデートが必要。

BPF module (BPF program)

Linux kernelに組み込まれたBPF virtual machineで実行されるプログラムで、バイトコードで表現される。

BCC (iovisor/bcc)

BPF Compiler Collection。BPF moduleのフロントエンドのひとつで、C言語で書いたコードをLLVMをつかってBPF moduleにコンパイルする。C言語とBPF moduleには表現力に大きな違いがあるため、実質的にはC言語のサブセットとなっている。たとえば、初期化されていない可能性のある変数へのアクセスや一部のループはBPF verifierによって弾かれる。また、関数のスタックが非常に小さい(512バイト)。

BPF verifier

BPF moduleのverifier。kernel内で任意のコードを実行させるため安全性を確認する必要がある。

eBPF

extended BPF。オリジナルのBPFを拡張して汎用性を高めたもの。現在は "BPF" といえばオリジナルのBPFではなくeBPFのこと。 なお、eBPF以前のBPFは cBPF (classic BPF) と呼ばれることもある。

USDT

User Statically-Defined Tracing。 ユーザースペースで静的に定義したトレースポイント。Linuxには様々なトレーシング技術があり、そのなかの一つという位置付け。

USDTはD言語で構造を宣言し、それをdtrace(1)でCヘッダファイルに変換してCプログラムに手動で埋め込む。

bpftrace (iovisor/bpftrace)

BPFのフロントエンドのひとつで、DスクリプトでBPF moduleを書く。ツール・スクリプト言語ともにDTrace互換らしい…が、h2oのdtrace単体テストをみるかぎり分岐もそこそこあるため、まったく同じように使えるわけではない。

https://github.com/h2o/h2o/blob/master/t/90dtrace.t

IO Visor

Linux Foundation傘下のプロジェクト。BCCやbpftraceを開発している。

SystemTap

Linux用のdtrace(1)を提供するプロジェクト。このdtrace(1)を使って、D言語で書いたUSDT (*.d) を使うためのCヘッダファイルを生成する。このあたりの作業フローはmacOSのdtrace(1)とほぼ同じ。

https://sourceware.org/systemtap/

参考文献

Ubuntuで任意のバージョンのLinux Kernelにする

追記: ツールはこちらの getkernel がメンテされているのでこちらを使うほうがいいでしょう! ubuntu用のカーネルをとってくるやつ - w_tl00’s blog


Ubuntuなどのディストリは、ディストリに同梱されたカーネルを別のバージョンに差し替えることができます。開発者の視点では、たとえばLinuxのBPFを使ったツールを開発しているときはカーネルのバージョンを本番に合わせたほうがよかったりします。

Ubuntu用のカーネルのパッケージは https://kernel.ubuntu.com/~kernel-ppa/mainline/ のあたりにあって、適切なアーキテクチャのものをダウンロード&インストールするだけなんですが、ダウンロードするのがわりと面倒くさいのでスクリプトを書きました。

./get-kernel 4.19.106 などとするとカレントディレクトリにパッケージがダウンロードされるので、あとは sudo dpkg -i *.deb でインストールしたあと sudo update-grub && sudo reboot するだけです。

(repo: https://github.com/gfx/get-kernel )

#!/usr/bin/env perl
use strict;
use warnings FATAL => "all";
use feature 'say';

die "usage: $0 kernel-version\n" unless @ARGV;

my $kernel_version = shift(@ARGV);

my $base_url = "https://kernel.ubuntu.com/~kernel-ppa/mainline/";


# e.g.
# "4.19.100" => "4.19.100-0419100"
# "4.9-rc1" => "4.9.0-040900rc1"
sub make_version_id {
        my($v) = @_;

        my @extra;
        if ($v =~ s/-([\w-]+)\z//xms) {
                push @extra, $1;
        }

        my(@parts) = split /\./, $v;
        if (@parts == 2) {
                push @parts, "0";
        }

        return join(".", @parts) . "-" . join("", map { sprintf "%02s", $_ } @parts, @extra)
}

sub uniqstr {
        my %seen;
        return grep { !$seen{$_}++ } @_;
}

chomp(my $arch = `dpkg --print-architecture 2>/dev/null` || "amd64");

my $detail = `curl --fail -sL $base_url/v$kernel_version/`;
if(not defined $detail or $? != 0) {
        die "Invalid kernel version '$kernel_version'. Find a correct one in $base_url\n";
}

my $version_id = make_version_id($kernel_version);

my(@paths) = uniqstr(($detail =~ m{
        <a\s+href="(
                        linux-[\w-]+ # package name
                        -(?:\Q$version_id\E)
                        (?:-generic)? # build variant (none and "generic")
                        _(?:\Q$version_id\E)
                        \.\d+ # timestamp
                        _(?:all|$arch)
                        \.deb
        )"}xmsg));

foreach my $path(@paths) {
        say "Downloading $path";
        system "curl", "-LO", "$base_url/v$kernel_version/$path";
}

say "Done. Try `sudo dpkg -i *.deb && sudo update-grub && sudo reboot`";

フィルターバブル vs QoL至上主義

フィルターバブル (filter bubble) とは、「インターネットの検索サイトが提供するアルゴリズムが、各ユーザーが見たくないような情報を遮断する機能」(フィルター)のせいで、まるで「泡」(バブル)の中に包まれたように、自分が見たい情報しか見えなくなること。 Wikipedia/フィルターバブル

世界の真の姿をみるためにはフィルターバブルに陥らないようにしなきゃな!と数年前までは思ってたんですが、最近はQoL至上主義に傾いてきて、SNSなどでもミュートをどんどん活用するようになりました。

自分と主義主張が異なるという理由でミュートしたりはしません。また「この話に付き合うのは大変だけど付き合うことによって世界を少しでも良くできる」と思えれば全然いいんです。しかしそうでない場合は議論自体まったくの無駄かもしれません。

どうやっても分かり合えない人たちはいるし、そういう人たちを議論によってコントロールすることはできません。そもそも他人の意見を自分がコントロールできると考えることすらおこがましいのかもしれません。だから、自分がコントロールできることにだけ時間を使いたいと思っています。

Google公式のLinux用Chromeバイナリのバージョンを調べる

UbuntuなどにむけたChromeバイナリのインストール方法をググると以下のようなコマンドを指示されるわけですが

wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 
sudo apt update

これをせずに、できればコマンドラインで配布されているChromeバイナリのバージョンを調べたかったんですが、 https://dl.google.com/linux/chrome/deb/ は404だしぐぐってもあまり情報はないしどうしたものかと思ってました。が、どうやら特定のプロトコルにしたがって Packages fileを探すとよさそう、というところであれこれ探すとありました。

curl -s https://dl.google.com/linux/chrome/deb/dists/stable/main/binary-amd64/Packages | grep Filename:

Filename: pool/main/g/google-chrome-beta/google-chrome-beta_80.0.3987.78-1_amd64.deb
Filename: pool/main/g/google-chrome-stable/google-chrome-stable_79.0.3945.130-1_amd64.deb
Filename: pool/main/g/google-chrome-unstable/google-chrome-unstable_81.0.4040.5-1_amd64.deb

参考文献:

2019年はキャリアの方向性を大きく変えた年だった

2019年のまとめです。

前半3Qはウェブアプリのバックエンドやフロントエンドを開発し、最終1Qで転職してC言語でHTTPサーバーを開発するという感じに大きく方針を転換した年でした。

転職にあたっては「毎日楽しく開発をしたい」「生涯現役のプログラマーでいたい」というのを基本的な軸として今後のことを考えていて、これから中長期的に投資するのはどういうスキルにするべきかなあと考えた結果です。

また、それはそれとして2017年から子育てをしていて、子供が保育園から風邪をもらってくると私と妻が仕事を休んで世話をし、そうこうしてる間に親(私 or 妻)に風邪がうつってしばらく仕事を休む、といった感じで思うように仕事を進められない1年でもありました(これは2018年からそういう状況)。来年はもうちょい丈夫に過ごせるといいなあと願ってやみません。


あとはざっくり今年書いたブログ記事を眺めると

gfx.hatenablog.com

このあたりが印象深いです。

完全にフロントエンドエンジニアって感じですね。

いまは打って変わってHTTP/3とかRustの勉強をしています。Rustは会社の新規プロダクトでよく採用されているので、いよいよRustを仕事で使うときが来たかとワクワクしています。

JSConf JP 2019で "How to Boost Your Code with WebAssembly" という話をしました

abstract: https://jsconf.jp/2019/talk/fuji-goro

Wasmを触り始めるにはまだ少しはやくて、おそらく2020年にはリリースされるであろうSIMDなどがほしいところです。とはいえ、JSの最適化コンパイラ(スライドではV8のTurboFanにだけ触れていますがほかのJSエンジンでも基本的には同じ傾向なはず)に頼らず安定したパフォーマンスを出せるというのは大きなメリットなので、その方面だと現在の状況でも考慮に値する可能性はあります。

ところでスライドでも触れてますが、eBayのバーコードスキャナ事例は大変興味深いです。

ここのエントリでも次のように書かれていて

This is sort of expected, as JavaScript can indeed be equally fast as native code, but only when it is in a “hot path,” i.e. heavily optimized by JIT compilers.

「JSはネイティブコード並の速度を出せるが、それはホットパスでJITコンパイラが高度な最適化をできる場合にかぎる」ということです。そしてプロダクションでは最適化コンパイラが必ずしも効果がなく結果が不安定だったと。

JSのベンチマークはしばしばこの「ホットパス」(スライドではホットスポット)だけを対象にしてしまうので、Wasmの真の力は V8 の --no-opt オプションを使ってベンチマークしてみるとよくわかるということですね。

Fastly に入社しました

2019年9月9日からFastlyに入社しています。勤務地は東京です。今後ともよろしくお願いいたします。

前職の Bit Journey, Inc. では3年ほどKibelaのサーバーサイドやフロントエンドアプリの開発に関わりました。Bit Journey在職中に子供がうまれ、現在も夫婦で分担しながら子育てをしていますが、この子育て初期という大変な時期*1にBit Journeyで気持ちよく働けたのはたいへんな僥倖でした。ここで改めて感謝いたします。

さて、Fastlyは方向性を変えて、ウェブアプリではなくVarnishやH2Oなどのミドルウェアの開発に関わります。

Kibelaは自分が数年のあいだ心血を注ぐにふさわしいサービスでしたし、実際のところ大いに開発を楽しみました。しかし、しばらく今後のキャリアの方向性を考えた結果、かねてから経験してみたいと思っていた低レイヤーなソフトウェア開発をしてみたいと決意しました。また、クラウド全盛のいまの時代だからこそ、クラウドサービスを提供する側を経験したいということもあります。

Fastlyはまさにそういうサービスであり、仕様と実装の双方をもって「インターネットを速くする」ことが価値であるCDNサービスを提供しています。興味関心のある領域で毎日楽しく開発をして、その結果としてインターネットを速くできるのであれば、ソフトウェアエンジニアとしてこれにまさる幸せはありません。この会社で過ごすこれからの時間も楽しみたいと思っています。

*1:保育園に通いはじめると頻繁に風邪をひくようになり、ひどいときには1~2週間ごとに子供が体調をくずしてそのたびに一週間ほどまともに仕事をできなくなります。

NodeJS v12.9.0 で writev(2) へのインターフェイスが追加された

writev(2) は バイト列の配列を1つのシステムコールでまとめて書き込めるやつです。

NodeJS的には、ArrayBufferView (典型的にはtyped array)の配列を fs.writev() (または fs.writevSync()FileHandle#writev())に渡せるようなインターフェイスになってます。もっとも、このwritev(2)へのインターフェイス自体は2015年にWriteStream用のprivate APIとして実装されて*1、今回はそれをfs moduleで正式に使えるようにしたってだけみたいですけどね*2

例:

"use strict";

const fs = require("fs");

const te = new TextEncoder();
const iov = [
  te.encode("Hello, "),
  te.encode("world!"),
  te.encode("\n"),
];

const result = fs.writevSync(process.stdout.fd, iov);

出力:

Hello, world!

細かなバイト列の断片を連結せずに一度のシステムコールでwriteできるので、状況によってはかなりの数のシステムコールを減らせる可能性があります。サーバーの実装ではよく使いそうですね。

Emscripten & WebAssembly Night #8 で「AssemblyScriptでライブラリコードの高速化をしてみる」という発表をした

発表の機会をいただきありがとうございました。会場を提供していただいたメルカリさんにも感謝いたします。

「AsssemblyScriptはTypeScriptのサブセットだから実質TypeScriptを書くだけでパフォーマンスアップ!」みたいな言説をみるにつけ、「ええ〜ほんとか〜??ほんとにやってみて言ってるんか〜??」と思っていたので、小規模とはいえ実際にやってみて検証&考察できたのはよかったなあと。

speakerdeck.com

特に、最適化JITの効きずらい状況(たとえばスタートアップタイムの高速化)での高速化に寄与する可能性を示唆できたのは大きな発見です。V8チームの目下の関心事はスタートアップタイムのようですし(ただしJSのダウンロード&パースなどの時間も含む)、FacebookもHermes Engineという新しいJSエンジンを開発してまでスタートアップタイムを改善しようとしています。結論としては、いま着手する必要はなくて「まだ早い」になりましたが、WebAssemblyが今後注目の技術であることには違いありません。

ただ、少なくとも現状だと、AssemblyScriptを学ぶ意味はあまりないと思います。もし同じことをするにせよ、ランタイムの小さいCまたはRustが現実的な選択肢になるのではないでしょうか。今回のコードもAssemblyScripは捨ててRustで再実装してみようとは思っています。