Vegetaで負荷試験シナリオを作る(初歩)

f:id:pigggg:20210907205647p:plain

パクリ元

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にすると異常に短いのもあるが…