Vegetaで負荷試験シナリオを作る(初歩)
パクリ元
rarejob-tech-dept.hatenablog.com 基本的にここのパクリ
実装
main.go
package main import ( "context" "encoding/json" "flag" "os" "sync" "time" log "github.com/sirupsen/logrus" vegeta "github.com/tsenart/vegeta/lib" "github.com/hogehoge/hoge" ) func main() { var ( rate = flag.Int("rate", 10, "Number of requests per time unit [0 = infinity] (default 10/1s)") duration = flag.Int("duration", 10, "Duration of the test [0 = forever]") ) flag.Parse() rt := vegeta.Rate{Freq: *rate, Per: time.Second} dur := time.Duration(*duration) * time.Second count := int64(*rate) targeter, err := hoge.NewUserTargets(count) if err != nil { log.Fatal(err) } var metrics MetricsMutex var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for res := range vegeta.NewAttacker().Attack(targeter, rt, dur, "load_test") { // 全アカウント分終了すると空の結果が返るのでそれはMetricsに含めないようにする if res == nil || res.Error == vegeta.ErrNoTargets.Error() { continue } metrics.Lock() metrics.Add(res) metrics.Unlock() } }() wg.Wait() metrics.Close() b, err := json.Marshal(metrics) if err != nil { log.Fatal(err) } os.Stdout.Write(b) } type MetricsMutex struct { sync.Mutex vegeta.Metrics }
user.go
package hoge import ( "encoding/json" vegeta "github.com/tsenart/vegeta/lib" "net/http" "strconv" "sync" ) func CreateUserTargets(targetCount int64) ([]*UserTarget, error) { header := map[string][]string{ "Content-Type": {"application/json"}, } var userTargets []*UserTarget index := int64(0) for { if index == targetCount { break } // user_id を投げつけるだけのPOSTリクエスト user := User{ UserId: strconv.FormatInt(index+1, 10), } var err error var body []byte if body, err = json.Marshal(user); err != nil { return nil, err } bean := &UserTarget{ Method: http.MethodPost, URL: "http://localhost:8080/api/hogehoge", Body: body, Header: header, } userTargets = append(userTargets, bean) index++ } return userTargets, nil } func NewUserTargets(targetCount int64) (vegeta.Targeter, error) { userTargets, err := CreateUserTargets(targetCount) if err != nil { panic(err) } var ( index int mu sync.Mutex ) return func(tgt *vegeta.Target) error { mu.Lock() defer mu.Unlock() defer func() { index++ }() if index >= len(userTargets) { return vegeta.ErrNoTargets } target := userTargets[index] tgt.Method = target.Method tgt.URL = target.URL tgt.Header = target.Header tgt.Body = target.Body return nil }, nil } type User struct { UserId string `json:"user_id"` } type UserTarget struct { Method string URL string Header http.Header Body []byte }
Result
{ "latencies": { // Total は、攻撃におけるすべてのリクエストのレイテンシーの合計です。 "total": 210145759, // Meanは、リクエストの平均レイテンシーです。 "mean": 21014575, // P50は、50パーセンタイルのリクエストレイテンシーです。 "50th": 19912375, // P95は、95パーセンタイルのリクエストレイテンシーです。 "95th": 30986404, // P99は、99パーセンタイルのリクエストレイテンシーです。 "99th": 30986404, // Maxは、観測されたリクエストの最大レイテンシーです。 "max": 30986404 }, // BytesInには、計算された受信バイトメトリクスが格納されます。 "bytes_in": { // Totalは、攻撃におけるフローイングバイトの総数です。 "total": 1771, // Meanは、1ヒットあたりのフローイングバイト数の平均値。 "mean": 177.1 }, // BytesOutには、計算された送信バイトメトリクスが格納されます。 "bytes_out": { // Totalは、攻撃におけるフローイングバイトの総数です。 "total": 151, // Meanは、1ヒットあたりのフローイングバイト数の平均値 "mean": 15.1 }, // Earliest は、結果セットの中で最も古いタイムスタンプです。 "earliest": "2021-09-07T17:49:43.85985934+09:00", // Latest は、Result set の最新のタイムスタンプです。 "latest": "2021-09-07T17:49:44.75820043+09:00", // Endは、Result setの最新のタイムスタンプにレイテンシーを加えたものです。 "end": "2021-09-07T17:49:44.778052431+09:00", // Durationは、攻撃の継続時間です。 "duration": 898341090, // 待ち時間は、ターゲットからの回答を待つ余分な時間です。 "wait": 19852001, // Requestsは、実行されたリクエストの総数です。 "requests": 10, // Rateは、1秒間に送信されるリクエストの割合です。 "rate": 11.131629301293566, // スループットとは、1秒間に成功したリクエストの割合です。 "throughput": 10.89095539709305, // 成功とは、エラーにならなかった回答の割合です。 "success": 1, // StatusCodesは、回答のステータスコードのヒストグラムです。 "status_codes": { "200": 10 }, // Errorsは、攻撃時にターゲットから返されるユニークなエラーのセットです。 "errors": [] }
Latencyの単位がいまいちわからない。秒数的にns??
nsにすると異常に短いのもあるが…