Dartのパフォーマンス観測を試みる

Dartでサーバ実装をしてみたいぞ!という前提。

基本的にはDart DevToolsやDart VM serviceが使える。
dart run --observe bin/server.dart
と、--observeオプション一つでこれらのツールが有効になる。

これらのツールが優れものでGUIでCPU Profiler、Memory Profilerなど提供してくれる。

Dart DevTools
Dart VM serviceのObservatory

しかし、逆にGUIでしか数値を得られないのだ。
とにかくパフォーマンスの数字は何処かへ連携してみたい気持ちなのでGUIだとなにかと困ってしまう。
なのでどうにかGUIなしでDartのパフォーマンスメトリクスを取得するほうほうを探っていく。

Dart DevToolsの挙動を探る

Dart DevToolsはBrowserで動くツールで、そこにメトリクスが出てきているわけなのでどうにか取得できるはずなのだ。
WEB APIが公開されているか、RailsのTurboのようにHTMLを一部入れ替えているか…
後者の場合メトリクス取得は難儀を極めそうだったが、幸い前者だった。

徐にDart DevToolsを開いているChromeでネットワーク確認をするとなにやら怪しいWebSocketのやりとりが…!

試しにCPU Profilerを起動しながら様子を見ているとどうやら大当たりだった。
DartのパフォーマンスメトリクスはWebSocketを通して、JSON-RPCでやりとりがされていることがわかった。

試しに手元のWebSocketクライアントからメッセージを送ってみると容易に情報が取れた。

Dart SDKのソースコードから探索する

なんとなくメトリクス取得のためにWebSocketやJSON-RPCが使われているのがわかってきたところで、GitHubを探索してヒントの収集を図った。

だらだらと検索結果を見たりしていると vm_service なるパッケージを発見した。なんか名前的にメトリクスが取れそうなのだ。
この vm_service sdkリポジトリ内にいるパッケージだった。
https://github.com/dart-lang/sdk/tree/main/pkg/vm_service
パッケージの中を見てみると、「まあまず読んでくれ。」なドキュメントが。
https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md
Dart VM Service Protocolなるものを発見した。JSON-RPCの定義なんかも含んでいる。
このProtocolに沿ったWebSocket APIが--observeオプションで公開されていて、Protocolを通じて取得したデータを元にDart DevToolsなんかは表示をしているんだな。

また、ドキュメント上には Dart Development Service(DDS) なる気になるワードも登場してきた。
こちらもDart SDKのリポジトリに存在していて、Dart VM Service Protocolの拡張のようだった。
https://github.com/dart-lang/sdk/blob/master/pkg/dds/dds_protocol.md
このddsパッケージで実装されたサーバがDart DevToolsの本体ということがわかった。
https://github.com/dart-lang/sdk/blob/main/pkg/dartdev/lib/src/commands/devtools.dart#L108

ここまででそれとなく、Dartのランタイムがパフォーマンスメトリクスを公開している方法がわかってきた。

vm_serviceパッケージ

vm_serviceパッケージにクライアント実装が提供されているので、これを試してみる。

examplesにあるコードを参考に試しにDart VMの情報を実装してみる。

import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:vm_service/vm_service_io.dart';

void main(List<String> args) async {
  final serviceClient = await vmServiceConnectUri("ws://0.0.0.0:8181");
  print('socket connected');
  vm_service.VM vm = await serviceClient.getVM();
  print('hostCPU=${vm.hostCPU}');
  await serviceClient.dispose();
  await serviceClient.onDone;
}

実行すると見事hostCPUの名前が取れた。

❯ dart run --disable-service-auth-codes  --observe bin/observe.dart
The Dart VM service is listening on http://127.0.0.1:8181/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2Fws
socket connected
hostCPU=Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz

オプションでつけている --disable-service-auth-codes はその名の通りauth-codesを無効にするオプションだ。
dart run --help -v コマンドで説明が出てくるオプションでこれが有効だと起動時に毎度変わるauth-codesがDart VM Serviceへの接続時に要求される。

ここまででDart VM serviceへの接続がvm_serviceパッケージで容易にできることがわかった。

パフォーマンスメトリクスの取得を試みる

早速vm_serviceパッケージを使って実装してみた。
3秒に1回発火するTimer内でmemory, stack, cpuを取得してみた…が、
ログに書き込まれた量は半端じゃなかった。

void startVMObserve(Object dismiss) async {
  final serviceClient = await vmServiceConnectUri("ws://0.0.0.0:8181");
  print('socket connected');
  vm_service.VM vm = await serviceClient.getVM();
  print('hostCPU=${vm.hostCPU}');

  Timer.periodic(Duration(seconds: 3), (timer) async {
    final now = DateTime.now();
    for (var isolate in vm.isolates!) {
      final memory = await serviceClient.getMemoryUsage(isolate.id!);
      final stack = await serviceClient.getStack(isolate.id!);
      final cpu = await serviceClient.getCpuSamples(
          isolate.id!,
          now.microsecondsSinceEpoch,
          now.add(Duration(seconds: 3)).microsecondsSinceEpoch);
      print(
          "isolate(${isolate.id}) ${memory.json.toString()}, stack=${stack.toJson()}, cpu=${cpu.toJson()}");
    }
  });
}

メトリクスをどうにか取得できる方法はわかってきた!..かもしれないので、次はメトリクスを外部のサービスに出力してみたい。

この記事が気に入ったらサポートをしてみませんか?