Upgrade to Pro — share decks privately, control downloads, hide ads and more …

xk6によるプロトコル追加 -テストライフサイクルと文字列変換の注意点-

Avatar for KANAI Masumi KANAI Masumi
September 07, 2026
130

xk6によるプロトコル追加 -テストライフサイクルと文字列変換の注意点-

Grafana Meetup Japan #10 2026/09/07 に登壇させていただきました。
https://grafana-meetup-japan.connpass.com/event/403601/

言及した k6 拡張
- https://github.com/masu-mi/xk6-example
- https://github.com/bbsakura/xk6-diameter

Avatar for KANAI Masumi

KANAI Masumi

September 07, 2026

Transcript

  1. 自己紹介 • 金井真澄 (@masu_mi) • 2021年2月 BBSakura Networks 株式会社 中途入社

    ◦ 入社前、モバイルコアおもしろそうだな ◦ 入社後、モバイルコアむずかしい • つくば市在住、こども2人 • 新卒はウェブ企業 ◦ • メディアサービス・オブジェクトストレージ開発 学生時代は、完全WKB解析と非断熱遷移
  2. 会社紹介 持分比率 100 % 49 % 51 % 2019.08~ IX事業

    ネットワーク ソリューション ソフトウェア事業 ネットワーククラウド サービスの開発 ソフトウェア ソリューション 3
  3. 本日のながれ • k6/xk6 について • ターゲットシステム(モバイルコアの一部)を説明 ◦ 実施したいテストシナリオ ◦ 欲しいテストスクリプト例

    • k6 テストライフサイクル • 注意点 ◦ テストライフサイクル: スコープ ◦ 共有セッション: 振り分けと計測 ◦ JavaScript/Go 境界: エンコーディング
  4. k6/xk6 紹介 • • • k6 は性能検証ツール ◦ シナリオを JavaScript

    で書く ◦ コマンドは Goのシングルバイナリ ◦ Go によって拡張するプラグイン機構を備えている ▪ JavaScript, Output, Subcommand, Secret xk6 は拡張プラグインの開発ツールキット k6/xk6 どちらもオプション・サブコマンドが充実している ref. https://github.com/grafana/k6
  5. k6 テストの構造 • • • VU ◦ 仮想ユーザー ◦ JavaScript実行環境

    イテレーション ◦ JavaScript関数 ◦ 処理シーケンス(func) シナリオ ◦ 1テストに複数シナリオ ◦ 負荷パターン ▪ VU数 ▪ イテレーション ▪ 呼び出しパターン ▪ 開始・停止時間 VU iteration VU iteration VU iteration シナリオ k6 run test.js
  6. 一般的なユースケース紹介: --template browser export default async function() { let checkData;

    const page = await browser.newPage(); try { await page.goto(BASE_URL); checkData = await page.locator("h1").textContent(); check(page, { header: checkData === "Looking to break out of your pizza routine?", }); await page.locator('//button[. = "Pizza, Please!"]').click(); await page.waitForTimeout(500); await page.screenshot({ path: "screenshot.png" }); checkData = await page.locator("div#recommendations").textContent(); check(page, { recommendation: checkData !== "", }); } catch (error) { fail(`Browser iteration failed: ${error.message}`); } finally { await page.close(); } sleep(1); } 簡単に生成できる k6 new --template 'browser'
  7. 一般的なユースケース紹介: --template browser export default async function() { let checkData;

    const page = await browser.newPage(); try { await page.goto(BASE_URL); 1 checkData = await page.locator("h1").textContent(); check(page, { header: checkData === "Looking to break out of your pizza routine?", }); await page.locator('//button[. = "Pizza, Please!"]').click(); await page.waitForTimeout(500); await page.screenshot({ path: "screenshot.png" }); checkData = await page.locator("div#recommendations").textContent(); check(page, { recommendation: checkData !== "", }); } catch (error) { fail(`Browser iteration failed: ${error.message}`); } finally { await page.close(); } sleep(1); } 2 3 4 1. 2. 3. 4. ページ開く h1 内容確認 Pizza, Please! ボタンをクリック div#recommendations != “” 確認 options で chromium 指定されている
  8. Diameter 簡略説明 • • • Request/Answer メッセージを使う ◦ 1組の Request/Answer

    を transaction と呼ぶ ◦ メッセージは Command, Header, AVP などで構成される 認証・認可、課金のフレームワーク ◦ Command・AVP(Headerみたいなもの)を追加可能 ◦ さまざまな認証・認可アプリケーションが動く Diameterピア間は単一SCTP(, TCP) セッションを貼りっぱなし ◦ Diameter Sessionと呼ぶ ◦ 複数のメッセージが流れます ref. RFC6733 MME HSS
  9. Diameter 簡略トポロジー MME DEA DRA MME DEA DRA DEA HSS

    HSS MME DEA DRA diameter/[tcp|sctp] MME DEA ref. GSMA IR.80
  10. アタッチシーケンス(抜粋)からテスト例を作る モバイル端末の接続前の認証や位置登録のシーケンス(の一部) テスト例 • • k6 as DEA ◦ TUS:

    HSS ◦ アタッチシーケンス ◦ VU: n, session: 1 MME TUS: DEA ◦ CLR のルーティング ◦ VU: n, session: nH, nM DEA HSS HSS AIR AIA ULR k6 as HSS, MME ◦ MME CLR CLA ULA
  11. アタッチシーケンス(抜粋)からテスト例を作る モバイル端末の接続前の認証や位置登録のシーケンス(の一部) テスト例 • • k6 as DEA ◦ TUS:

    HSS ◦ アタッチシーケンス ◦ VU: n, session: 1 MME TUS: DEA ◦ CLR のルーティング ◦ VU: n, session: nH, nM DEA HSS HSS AIR AIA ULR k6 as HSS, MME ◦ MME CLR CLA ULA
  12. xk6 のはじめかた 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 JS インポートパス → “k6/x/example” Goパッケージ名 → example
  13. Diameter 拡張でサポートして欲しいこと • グローバルな設定変更を行いたい ◦ • Diameter セッションをVU・Iteration間で共有したい ◦ •

    Diameter では辞書によって拡張できるため ユーザーメッセージを単一セッションに流すため Diameter Request/Answer を適切な Iteration に届けたい ◦ セッションが共有されているので Answer を Request 元に届ける工夫が必要
  14. スクリプト例: 無理 import * as test from 'k6/x/diameter'; let cli;

    export function setup() { cli = diameter.NewClient({}); } export default function () { const resp = cli.Request({cmd: "AIR"}); check(resp, { ignore: (resp) => true }); const resp = cli.Request({cmd: "ULR"}); check(resp, { ignore: (resp) => true }) }
  15. スクリプト例: 無理 import * as test from 'k6/x/diameter'; let cli;

    export function setup() { cli = diameter.NewClient({}); } export default function () { const resp = cli.Request({cmd: "AIR"}); check(resp, { ignore: (resp) => true }); const resp = cli.Request({cmd: "ULR"}); 理由は後述 check(resp, { ignore:setup() (resp) => 実行はテスト実行と分離される true }) }
  16. スクリプト例: 現状 import * as test from 'k6/x/diameter'; let cli;

    export default function () { if (cli == null) { cli = diameter.EnsureClient("to-hss", {}); } const resp = cli.Request({cmd: "AIR"}); check(resp, { ignore: (resp) => true }); const resp = cli.Request({cmd: "ULR"}); check(resp, { ignore: (resp) => true }) }
  17. スクリプト例: ちょっとだけ優しい import * as test from 'k6/x/diameter'; let cli

    = diameter.EnsureClient("to-hss", {}); export default function () { const resp = cli.Request({cmd: "AIR"}); check(resp, { ignore: (resp) => true }); const resp = cli.Request({cmd: "ULR"}); check(resp, { ignore: (resp) => true }) }
  18. テストライフサイクル init Setup init ローカルファイル、モジュール、ライフサイクル関数宣言 Setup VUで共有するデータ生成 VU codes それぞれのVUが、ユーザー定義テスト関数の実行

    Teardown テスト完了後の後処理 VU codes Teardown VU iteration VU iteration VU iteration シナリオ k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  19. 実験用スクリプト export const options = { }; console.log("at init; id:

    " + __VU); export function setup() { console.log("at setup; id: " + __VU); return 1; } export function teardown() { console.log("at teardown; id: " + __VU); return 1; } export default function () { console.log("in default; id: "+ __VU) }
  20. 実験用スクリプト export const options = { }; console.log("at init; id:

    " + __VU); VU の IDが埋め込まれている export function setup() { console.log("at setup; id: " + __VU); return 1; } export function teardown() { console.log("at teardown; id: " + __VU); return 1; } export default function () { console.log("in default; id: "+ __VU) }
  21. 実行結果 VU{ id = 0 } が 4回 init と出力されている

    => グローバルスコープの評価が走る 場所 • k6 ロゴ前の一番最初 • setup() 直前 • teardown() 直前 • TOTAL RESULTS 直前
  22. テストライフサイクル init Setup VU codes Teardown k6 run test.js ref.

    https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  23. テストライフサイクル init Setup VU codes Teardown handleSummary k6 run test.js

    ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  24. テストライフサイクル: init Bundle • 複数のVU で共有される • JS 実行環境の一部 •

    JS 仮想マシン(sobek)を含む init Setup VU codes Bundle Teardown handleSummary k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  25. テストライフサイクル: init init VU(0) _.instantiate(vuImpl, 0) options b.Options _.populateExports() Setup

    VU codes Bundle Teardown handleSummary k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  26. テストライフサイクル: init init VU(0) _.instantiate(vuImpl, 0) options b.Options _.populateExports() Setup

    VU codes Teardown handleSummary instantiate Bundle • VU 生成 • グローバルスコープの評価 ◦ モジュールの import ◦ グローバル変数の値が決まる ◦ ファイル open される k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  27. テストライフサイクル: init init VU(0) _.instantiate(vuImpl, 0) options b.Options _.populateExports() Setup

    VU codes Teardown handleSummary options Bundle • シナリオなどがテスト全体の定義を含む • 単なるデータで、Goオブジェクトへの参照なし VU(0) • エフェメラルで評価環境は消える k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  28. テストライフサイクル: init options init Setup VU codes Bundle Runner Teardown

    handleSummary k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  29. テストライフサイクル: init options init Setup VU codes Bundle Runner Runner

    • VU ファクトリ Bundle • 最初の評価後に以下のようにロック ◦ ファイルオープン不可 ◦ モジュール import 不可 Teardown handleSummary k6 run test.js ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  30. テストライフサイクル: init options init Setup VU codes Teardown handleSummary Bundle

    Runner lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  31. テストライフサイクル: init init Scheduler: テスト全体の管理 ExecutionState: VUプール Executor: iteration on

    VU指示 (シナリオ担当) options Setup VU codes Teardown handleSummary Bundle Runner lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  32. テストライフサイクル: init options init Setup VU codes Teardown handleSummary Bundle

    Runner VU(1) VU(2) lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  33. テストライフサイクル: setup options init Setup VU codes Teardown handleSummary VU(0)

    Bundle Runner .runFn() .runPart() data VU(1) VU(2) lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  34. テストライフサイクル: setup options init Setup VU codes Teardown handleSummary VU(0)

    Bundle .runFn() .runPart() data VU(1) VU(2) lib.ExecutionState data Runner • VU codes ステージ以降のVUに渡すデータ • 単なるデータでGoオブジェクトへの参照なし • VU(0)はエフェメラルで評価環境は消える lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  35. テストライフサイクル: setup options init data Setup VU codes Teardown handleSummary

    Bundle Runner VU(1) VU(2) lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  36. テストライフサイクル: VU codes options init data Setup VU codes Teardown

    handleSummary Activated Bundle lib.ExecutionState Runner VU(1) VU(2) _.RunOnce() lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  37. テストライフサイクル: Teardown options init Setup VU codes Teardown handleSummary VU(0)

    Bundle Runner .runFn() .runPart() data VU(1) VU(2) lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  38. テストライフサイクル: handleSummary options init Setup VU codes Teardown handleSummary VU(0)

    Bundle Runner .runFn() .runPart() data VU(1) VU(2) lib.ExecutionState lib.Executor k6 run test.js execution.Scheduler ref. https://grafana.com/docs/k6/latest/using-k6/test-lifecycle
  39. Bundle = VU のテンプレート 実行関数, 環境変数, シナリオ名, busy chan, ...

    環境レコード 評価環境 モジュール VUActivation BundleInstance モジュールリスト open file list バイトコード/ソースコード JS VM(sobek) VU(Activated) VU(initialized) Bundle Runner ref. https://github.com/grafana/k6/blob/00a9a1b7f552d6bb4337278b10ae25aac0f4e666/internal/js/bundle.go
  40. Bundle は最初の評価後にロックされる 実行関数, 環境変数, シナリオ名, busy chan, ... 環境レコード 評価環境

    モジュール VUActivation BundleInstance モジュールリスト open file list バイトコード/ソースコード JS VM(sobek) VU(Activated) VU(initialized) Bundle Runner ref. https://github.com/grafana/k6/blob/00a9a1b7f552d6bb4337278b10ae25aac0f4e666/internal/js/bundle.go
  41. テストライフサイクル振り返り • • • 全てのVUは分離されている ◦ グローバル変数の状態などは共有されていない ◦ 実行環境の一部をBundleとして共有して効率化している ◦

    ファクトリ関数経由でBundleを参照したVU生成を行う ライフサイクル関数は エフェメラル VU が生成・破棄されている ◦ setup() でグローバル変数に値を格納しても VU Codes で利用不可 ◦ setup, Teardown, handleSummary それぞれ別環境 VU から値を取り出すとJSONパースが挟まる ◦ setup() 返り値で Goオブジェクトへの参照は使えない
  42. VU間でリソース共有するとき • Go側 ◦ 共有リソース(Client, Connなど)にIDを与えて識別可能にする ◦ リソースプールで管理 ▪ ◦

    • sync.Mutex ロックされるシンプルなマップ リソースプールを利用するAPIを提供 ▪ new Client(params), Client(params) ▪ GetClient(id), EnsureClient(name, params) JS側 ◦ Iteration の中で null チェックによりオブジェクトを取得する(必要に応じて生成) ◦ グローバルスコープで取得する(必要に応じて生成)
  43. セッション共有で要求される機能 • • • • Transaction の対応 ◦ Answer を

    Request 元に戻す ◦ Diameter は hbh-id(, e2e-id, Session-Id) が使える Server 機能 ◦ Request を受け取る ◦ Diameter では Code, IsRequest で判別できる ◦ リスナー, マッチャーなど要検討 レイテンシーなどを記録 非同期実行 ◦ Answer 待ちを分離 ◦ go.k6.io/k6/js/promises 利用を検討
  44. 共有セッションで計測を提供するとき Transaction 対応 • Correlation Table で hbh-id を使い Answer

    を Request元へ送る Client メトリックの記録 • • • metrics.PushIfNotDone() 呼び出し元 VU.Context() 利用 ◦ panic 防止 共有リソースラッパーを準備 ◦ ClientHdr Pool SharedClient CorrelationTbl JS Module VU ClientHdr Go
  45. 共有セッションで計測を提供するとき Transaction 対応 • Correlation Table で hbh-id を使い Answer

    を Request元へ送る Client メトリックの記録 • • • metrics.PushIfNotDone() 呼び出し元 VU.Context() 利用 ◦ panic 防止 共有リソースラッパーを準備 ◦ ClientHdr Pool SharedClient CorrelationTbl JS Module VU ClientHdr Go
  46. 共有セッションで計測を提供するとき Transaction 対応 • Correlation Table で hbh-id を使い Answer

    を Request元へ送る Client メトリックの記録 • • • metrics.PushIfNotDone() 呼び出し元 VU.Context() 利用 ◦ panic 防止 共有リソースラッパーを準備 ◦ ClientHdr Pool SharedClient CorrelationTbl JS Module VU ClientHdr Go
  47. 文字列変換の注意点 テストスクリプトで AVP を指定したい • • Diameter AVP に型指定がある ◦

    OctetString(ascii/binary mixed, raw, …), UTF8String ◦ Integer32, Integer64, Float64, … ◦ Address, Time, Grouped, … OctetString AVP を文字列(string)で設定したい ◦ • OctetString をバイト列として使っているAVPがいる JavaScript, Go で文字列のエンコーディングが異なる ◦ JavaScript: UTF-16, Go: UTF-8
  48. Octet-String トラブル例: (TBCD, PLMN-list) • MSISDN はTBCD エンコーディング • Visited-PLMN-Id

    は PLMN-list エンコーディング(特殊なTBCD的な動き) PLMN: 001-01 MCC: 001 MNC: F01 0 0 1 F 0 1 0 0 F 1 1 0 2桁は3桁目を空=F ニブル(4bit)として詰める { 0x00, 0xF1, 0x10 } as Binary PLMN: public land mobile network MCC: mobile country code (3桁) MNC: mobile network code (2-3桁)
  49. Octet-Stringの具体例: 文字化け OctetString という名前に引っ張られ、バイナリを文字列で書いてしまう。 { 0x00, 0xF1, 0x10 } as

    Binary “\x00\xF1\x10” in JS as UTF-16(LE) \u00F1 NULL ñ \u0010 DLE(DataLink Escape) { 0x00, 0xC3, 0xB1, 0x16} as UTF-8 JS Go
  50. まとめ • • k6/xk6 は簡単に使える ◦ 複雑なシナリオも書きやすい ◦ サブコマンドも充実 プロトコル追加での観点

    ◦ ◦ ◦ 共有リソースの管理 ▪ IDを与えてGo 内部で明示的に管理 ▪ 再利用前提のAPIを準備 セッション共有と呼び出し元の分離 ▪ メッセージ振り分け機構が必要 ▪ 計測には共有リソースと別にハンドラが必要 JavaScript/Go 間の変換 ▪ バイト列として使われる文字列がないか確認する ▪ (sobek の ECMAScript 仕様外にも注意; JSON だけ UTF-8) The Grafana Labs Marks are trademarks of Grafana Labs, and are used with Grafana Labs’ permission. We are not affiliated with, endorsed or sponsored by Grafana Labs or its affiliates.
  51. 余談: k6 便利なオプション、サブコマンド テストツール(k6)のpprof を取得する k6 run --address '0.0.0.0:18000' --profiling-enabled

    example.js テスト実行の依存関係を確認する k6 deps example.js テスト実行に必要なファイルをまとめる k6 archive example.js
  52. 余談: 公式拡張は自動ロードできる • 自動解決されてロードできる ◦ • • JavaScriptテストコードで使っていると勝手に読み込まれる 公式の拡張だけでも色々ある ◦

    xk6-dns, xk6-faker, xk6-icmp, xk6-mqtt, xk6-redis ◦ xk6-sql, xk6-sql-driver-mysql, … ターミナルからも k6 x explore コマンドで一覧をみれる ref. Explore extensions | Grafana k6 documentation