先月の 2026/09/07 に Grafana Meetup Japan #10 に参加しました。 私は xk6 で k6 にプロトコルを追加する話をしました。資料はSpeaker Deckに残してます。
全体の感想
当日の事前検証や案内など運営の方々に助けてもらいました。ありがとうございました。
民間ロケット開発を支えるGrafana
今回のメインセッションの民間ロケット開発でのGrafana活用は、単純にロケット開発というだけで楽しくなりました。
宇宙開発では、飛んでいるときはメトリックを絞らないといけない一方、地上の実験では大量に取得したい、という違いがある。 その状況でも、Grafanaを中心に自作しており統一的に扱えて嬉しいという話が面白かったです。 特に、軌道などの可視化も自作プラグインで対応されていて基盤となるGrafanaにダッシュボードとしての成熟を感じました。
地上局など物理施設に関するあらゆるメトリックを集めていて、トラブル時に現地に向かう前に分析できるのが良いと話されていたのも印象に残ってます。 物理作業や雪での移動が伴う場面では、リモートでの詳細な分析の価値がずっと大きい。 オブザーバビリティの重要性をあらためて認識したしIoT的な話で訴求できるところを作れないものかと思ってしまいました。
xk6によるプロトコル追加: テストライフサイクルと文字列変換の注意点
自分の発表はモバイルコアの試験で使っているk6拡張の話。
k6はGrafana Labsの負荷試験ツールです。VUという仮想ユーザー(それぞれが独立したJavaScript(JS)実行環境)が並列で処理を行います。 xk6はk6拡張の開発ツールキットです。
xk6 単体で扱うセッションは初めてということで内容に悩みました。 発表後に聞いたのですが、xk6を知らなかったり使い方を知らなかった方もいたようで意義はあったと思います。
ただk6自体の入門やランタイム内のVUまわりの参照関係・ライフサイクルの説明に時間を割きすぎて、 プロトコル追加や利用や改善の話ができなかったのは反省しています。
発表の概要
詳しくは資料を読んでもらえば良いので、ここでは要点だけ残します。
携帯電話網のコアネットワークの試験はシーケンスが複雑で、またインターネットでは馴染みの薄いプロトコルも多く登場します。 複雑なシナリオをJSで書けるk6は便利です。しかし使いたいプロトコルはサポートされていない。 そこでxk6を使って拡張を提供するとモバイルコアの世界でk6エコシステムで楽ができて嬉しい。 という背景です。
Diameter追加する k6のJS拡張(xk6-diameter)を作っていたので、 xk6でプロトコル追加する際の注意点を紹介することにしました。
プロトコルを追加するときに気を付けることは3点。
- 共有セッションの管理方法
- 共有セッションの提供 API
- JavaScript/Go 境界
xk6 のはじめかた
はじめるのは簡単。拡張の雛形はxk6 newで作れます。
go install go.k6.io/xk6@latest xk6 new -p example github.com/masu-mi/xk6-example make # = xk6 build --with github.com/masu-mi/xk6-example=. ./k6 version
-pで指定した名前(ここではexample)から、次の2 つが決まる。
- JSのインポートパス:
k6/x/example(スクリプトではimport example from 'k6/x/example') - Goのパッケージ名:
example
モジュールパス (github.com/masu-mi/xk6-example) とは別なので、リポジトリ名にxk6-を付けてもJS 側の名前はすっきりしたままにできる。
共有セッションの管理方法
プロトコルによっては1つのL4セッションで複数のメッセージを流したい場面があるため、プロトコル追加する拡張モジュールでは共有セッションを提供したい。 ところがk6のテストスクリプトは複数のJSランタイムが協調して処理を実行します。 例えば、グローバルスコープで値を評価するランタイムとシナリオ処理を実行するランタイムが別になります。 JS 側では変数の値をどこで初期化できるか? など考える必要があります。 Go側では複数のランタイムで共有するため管理が必要になります。
k6のテストは init → setup → VU code → teardown → handleSummary と5ステージで構成されています。 (テストライフサイクルの公式資料ではhandleSummaryの記載が省かれてます)
ref. Test lifecycle
- 全ての VU は分離されていて、グローバル変数の状態は共有されない
- setup / teardown / handleSummary はそれぞれ別環境
setup()でグローバル変数に値を入れてもVU codeでは使えない
- VUから値を取り出すとJSONパースが挟まるので、
setup()の返り値でGoオブジェクトは渡せない
そのためDiameterセッションのようにVU間で共有するリソースはグローバルスコープで確保しつつGo側で重複排除などプロトコルに適した管理を提供する必要があります。
そこで xk6-diameter では UUIDv7 を識別子に明示的に与えてテーブル管理するようにしました。 簡略なオブジェクト図は次のセクションで載せておきます。
| API | 内容 |
|---|---|
| NewConn(attr) | 共有セッションの生成; ID: UUIDv7 |
| GetConn(id, attr) | 共有セッションの取得; ID: UUIDv7 |
| EnsureConn(name, attr) | 共有セッションの確保、名前をキーにユニーク性を担保 |
共有セッションの提供API
共有セッションを通じて通信機能を提供します。そこでの課題を整理します。
メッセージの振り分け
Diameterはピア間に1 本のセッションを張り、その上で複数メッセージを流します。 1セッションで複数のリクエスト・アンサーが流れるため、モジュールでは受け取ったアンサーの送り先を決める必要があります。 そこでCorrelation IDを決めて返送先をテーブルで管理します。 またシナリオによってはリクエストを受け取ることもあります。その振り分けも設定可能だと便利です。
- Correlation IDを決めてアンサーの返送先をテーブル管理する
- プロトコルと用例を考慮してフィルターによるリクエストのコンシューマーを定義する
メトリック集計
k6は性能検証ツールなので各種メトリックを収集したくなります。拡張モジュールとしてもメトリックを提供できます。 メトリック収集用APIはk6全体でのサービスで記録要求をそれぞれのVUから送信します。 この時、共有セッションの生成元VUのコンテキストで送信しようとすると送信できないことがあります。 初めて共有セッションを作成したVUが停止済みでコンテキストがクローズされている場合です。 (多くの場合でinitステージのエフェメラルVUなので送信されません)
かといってcontext.Background()を使うと共有セッションはk6管理外のゴルーチンで動くため
k6 のサービス停止後にメトリック送信要求を出そうとしてpanicとなる可能性が残ります。
そこで共有セッションを2階層にわけます。共有セッション自体とVUなど呼び出しコンテキストを紐づけるハンドラです。 メトリックの記録についてはハンドラ側で行うことで呼び出しコンテキストで集計記録を取れるので安全に扱えます。
- 共有リソース(セッションなど)は ID を与えてテーブル(Pool)で管理
sync.Mutexで守るEnsureConn(name, params)のような再利用前提のAPIも準備する
- アンサーの送り先はCorrelation IDを用いてテーブル管理する
- 共有リソースからどのIterationに返却するかの判断が必要
- Diameterならhop-by-hop IDが使える
- 計測はVUコンテキストと紐づくハンドラ(ConnHdr, 図のClientHdr)で行う
metrics.PushIfNotDone()には呼び出し元 VU のVU.Context()が適切
前のセクションと組み合わせて概念的なオブジェクト図にすると下のようになります。

| エンティティ | 役割 |
|---|---|
| SharedClient | 共有セッションに対応 |
| Pool | 共有セッションを管理するテーブル |
| CorrelationTbl | 共有セッションで流れるアンサーの返送先管理に使うテーブル |
| ClientHdr | ハンドラ(JS側のClientに対応); 共有セッションに呼び出しコンテキスト(VU)を組み合わせる処理を実施 |
JavaScript/Go 境界
DiameterのOctetString AVP (Attribute-Value Pair)には、TBCDのようにバイト列として使われているものがあります。
これを名前に引っ張られてJSの文字列 "\x00\xF1\x10"で書くと、JS(UTF-16)からGo(UTF-8)への変換で壊れてしまいます。

3バイトのつもりが4バイトで送られてしまう。
- バイト列として使う文字列がないか確認する
- バイト列・文字列の両方を受け取れる変換層を用意する (引数は
interface{}などで受ける)
発表後のアップデート
発表のあとbbsakura/xk6-diameterに次の対応を入れました。
- Promise 対応
- シナリオでは非同期なやりとりが必要となるため
- モジュール内でのエラーをJS側へスロー
- Go側で勝手に判断せずスクリプト側で対処するため




























