Perfetto は Android platform で使われている tracing フレームワーク。Android platform 以外でも単独でアプリケーションに組み込んで使うことも想定して作られていて、例えば Chrome が使っている。この Perfetto を Rust で書いたアプリケーションから使ってみようと試している。
現状のプロトタイプ実装で HTTP/1.1 のリクエストを投げてレスポンスを解釈する async タスクを以下のように書くと:
async fn h1_get_task(host: String, port: u16) -> anyhow::Result<()> {
let track = Track::with_name(&host);
trace_event!(CATEGORY_BENCHMARK, "Request", track, EventType::SliceBegin);
let mut client = Client::connect(host.clone(), port, track).await?;
client.send_request().await?;
let response = client.recv_response().await?;
trace_event!(
CATEGORY_BENCHMARK,
"Request",
track,
EventType::SliceEnd,
|event| {
trace_event_set_string!("host", &host);
trace_event_set_string!("status", response.status().as_ptr());
}
);
Ok(())
}
struct Client { /* ... */ }
impl Client {
async fn connect(host: String, port: u16, track: Track) -> Result<Self> {
let _s = trace_event_span!(CATEGORY_BENCHMARK, "Connect", track);
// ...
}
async fn send_request(&mut self) -> Result<()> {
let _s = trace_event_span!(CATEGORY_BENCHMARK, "Send", self.track);
// ...
}
async fn recv_response(&mut self) -> Result<Response> {
let _s = trace_event_span!(CATEGORY_BENCHMARK, "Recv", self.track);
loop {
match self.state {
State::ReadingHeaders => self.read_headers().await?,
State::ReadingBody => self.read_body().await?,
State::Done => break,
}
}
// ...
}
async fn read_headers(&mut self) -> Result<()> {
let _s = trace_event_span!(CATEGORY_BENCHMARK, "ReadHeaders", self.track);
// ...
}
async fn read_body(&mut self) -> Result<()> {
let _s = trace_event_span!(CATEGORY_BENCHMARK, "ReadBody", self.track);
// ...
}
}
こんな感じのトレースが得られる。
trace_event!
と trace_event_span!
が C++ の TRACE_EVENT()
に相当する。スコープを扱うのに _s
に代入しているのはいびつだけれど、これは Attribute macros で対処できる目途が立っている。
Perfetto には track という概念がある。分散環境を意識した用語でいえば span と同等のもの。 Android での tracing を主な用途とする Perfetto では TRACE_EVENT()
で記録される event はデフォルトでスレッドと1:1に対応する track に紐づけられる。
一方 Async Rust では .await
の前後でスレッドが異なるエコシステム (tokio) で trace を記録したいことがほとんど。 C++ と同じ感じで track を省略して書くと UI が span を期待通りに表示してくれない。プロトタイプでは明示的に track を要求するようにしている。似た様な問題意識を持つ tracing crate はこのあたりを隠蔽しようと悪戦苦闘している様子だが見込みは薄そう。
Event を引数付きで記録したい場合は event にメタデータを付与するコールバックを渡す。TRACE_EVENT()
のように記録したいパラメータを inline で渡せるマクロを提供できるかもしれないが、自分はマクロに詳しくなりたいわけではないので深掘りしていない。
実用には至っていない。困っている点を二つ羅列しておく。
- 動的に決まる key-value ペア (HTTP response headers とか) を trace event として記録する方法が分からない。Perfetto はオーバーヘッドを減らすべく key を internalize する。Internalize するときに使われるのは key のアドレスのようだ。つまり key は静的にアロケートされているか、動的に確保されたときは同じアドレスが使われていてはいけない。スタックに一時的に key を保存するようなナイーブなやり方だとこの前提を崩してしまい、期待した動きにならない。
TRACE_EVENT()
はperfetto::TracedValue
を引数に受け取る lambda を受け付けるように読めるが、マクロを使わずにTraceForCategory()
を直に叩くとコンパイルに失敗する。これは単純に自分の C++ 力が足りないだけ。