CMSgov/dpc-app

View on GitHub
dpc-testing/performance/pkg/dpc/targeter/targeter.go

Summary

Maintainability
A
0 mins
Test Coverage
// Package targeter package is used to create a generic Vegeta Targeter
package targeter

import (
    "fmt"
    "os"
    "time"

    vegeta "github.com/tsenart/vegeta/lib"
)

type Config struct {
    Method      string // required
    BaseURL     string // required
    Endpoint    string // required
    AccessToken string // required

    ID        string   // optional id to use for GET/PUT/DELETE requests; Mutually exclusive with IDs
    IDs       []string // optional ids to use for GET/PUT/DELETE requests; Mutually exclusive with ID
    Generator func() []byte

    Headers *Headers
}

type Targeter struct {
    nextBody    func() []byte
    nextID      func() string
    nextHeaders func() map[string][]string
    Config
}

func New(config Config) *Targeter {
    // Check for exclusivity for ids and bodies
    if config.ID != "" && config.IDs != nil {
        panic("Cannot set both `ID` and `IDs` for Targeter")
    }

    // If no Headers are passed, default to fhir json types
    if config.Headers == nil {
        config.Headers = &Headers{
            ContentType: "application/fhir+json",
            Accept:      "application/fhir+json",
        }
    }

    return &Targeter{
        nextBody:    genBodies(config),
        nextID:      genIDs(config),
        nextHeaders: genHeaders(config),
        Config:      config,
    }
}

func (dt *Targeter) Run(duration, frequency int) [][]byte {
    fmt.Printf("\nRunning performance test on %s...\n", dt.name())

    d := time.Second * time.Duration(duration)
    r := vegeta.Rate{Freq: frequency, Per: time.Second}

    attacker := vegeta.NewAttacker(vegeta.Timeout(60 * time.Second))
    var metrics vegeta.Metrics
    var respBodies [][]byte
    for results := range attacker.Attack(dt.buildTarget, r, d, fmt.Sprintf("%dps:", r.Freq)) {
        metrics.Add(results)
        respBodies = append(respBodies, results.Body)
    }
    metrics.Close()

    reporter := vegeta.NewTextReporter(&metrics)
    reporter.Report(os.Stdout)

    return respBodies
}

func (dt *Targeter) buildTarget(t *vegeta.Target) error {
    t.URL = dt.nextURL()
    t.Body = dt.nextBody()
    t.Method = dt.Method

    t.Header = dt.nextHeaders()
    t.Header.Add("Authorization", fmt.Sprintf("Bearer %s", dt.AccessToken))

    return nil
}

func (dt *Targeter) nextURL() string {
    url := fmt.Sprintf("%s/%s", dt.BaseURL, dt.Endpoint)
    if dt.IDs != nil || dt.ID != "" {
        url = url + "/" + dt.nextID()
    }
    return url
}

// Name generates a human-readable name for a given targeter configuration
func (dt *Targeter) name() string {
    var id string
    if dt.IDs != nil {
        id = "{id}"
    }
    return fmt.Sprintf("%s %s/%s/%s", dt.Method, dt.BaseURL, dt.Endpoint, id)
}

// GenStrs general generator function that returns strings
func GenStrs(strs []string) func() string {
    i := 0
    n := len(strs)
    return func() string {
        nextVal := strs[i%n]
        i++
        return nextVal
    }
}

// genIDs produces a closure that returns successive ids from the supplied list
func genIDs(config Config) func() string {
    // If `ID` is present it has precedence over `IDs`
    if config.ID != "" {
        return func() string { return config.ID }
    }

    return GenStrs(config.IDs)
}

// genBodies produces a closure that returns successive request bodies from all files matching the pattern
func genBodies(config Config) func() []byte {
    if config.Generator == nil {
        return func() []byte { return []byte{} }
    }

    return config.Generator
}