Load testing with random data per request in Vegeta
2021-03-02
負荷試験ツール Vegeta で毎回ランダムな値を使って HTTP POST する方法のメモ
Vegeta 自体はコマンドラインからも使えます。 よくある jq
で生成した JSON を食わせるワンライナーがベースだと、ランダムな値を生成しつつ変数展開する方法がわからなかったので今回はライブラリとして使ってなんとかします。
README にあるサンプルでは vegeta.NewStaticTargeter()
を利用して targeter
を生成しています。
// https://github.com/tsenart/vegeta#usage-library
func main() {
rate := vegeta.Rate{Freq: 100, Per: time.Second}
duration := 4 * time.Second
targeter := vegeta.NewStaticTargeter(vegeta.Target{
Method: "GET",
URL: "http://localhost:9100/",
})
attacker := vegeta.NewAttacker()
var metrics vegeta.Metrics
for res := range attacker.Attack(targeter, rate, duration, "Big Bang!") {
metrics.Add(res)
}
metrics.Close()
fmt.Printf("99th percentile: %s\n", metrics.Latencies.P99)
}
この NewStaticTargeter
は、引数として受け取った []vegeta.Target
を呼び出しごとに先頭から順にループしながら、別のポインタに代入する Targeter
を返します。 名前の通り Static なので、リクエストの中身はあらかじめ渡した vegeta.Target
から変化しません。
仕組み上ここを置き換える何かだけで動きそうということがわかりました。
// commit ref: d73edf2bc2663d83848da2a97a8401a7ed1440bc
// https://github.com/tsenart/vegeta/blob/master/lib/targets.go#L106
type Targeter func(*Target) error
// https://github.com/tsenart/vegeta/blob/master/lib/targets.go#L212
func NewStaticTargeter(tgts ...Target) Targeter {
i := int64(-1)
return func(tgt *Target) error {
if tgt == nil {
return ErrNilTarget
}
*tgt = tgts[atomic.AddInt64(&i, 1)%int64(len(tgts))]
return nil
}
}
以下のように毎回 payload を作る genTargeter()
を書き、これを NewStaticTargeter()
の代わりに使うことで実行ごとに内容を変化させつつビッグバンアタックできます。 Target
の生成コストがかかるのでもしかするとテスト自体のパフォーマンスに多少影響があるかもしれませんが特に考慮していません。
package main
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
vegeta "github.com/tsenart/vegeta/v12/lib"
)
type requestBody struct {
UUID string `json:"uuid"`
Value string `json:"value"`
}
func genTarget() vegeta.Target {
u, _ := uuid.NewRandom()
uuid := u.String()
header := make(http.Header)
header.Set("Content-Type", "application/json")
encoded, _ := json.Marshal(requestBody{
UUID: uuid,
Value: "some value",
})
return vegeta.Target{
Method: "POST",
URL: "http://localhost:9100/",
Body: encoded,
Header: header,
}
}
func genTargeter() vegeta.Targeter {
return func(tgt *vegeta.Target) error {
*tgt = genTarget()
return nil
}
}
※負荷試験を行う際のリクエスト先やリクエスト数等には十分ご注意ください。このプログラムを使用して生じたいかなる損害・問題等について、筆者は一切の責任を負いかねます。