mirror of
https://github.com/americanexpress/baton.git
synced 2025-07-06 20:32:31 +00:00
191 lines
5.6 KiB
Go
191 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"flag"
|
|
"github.com/valyala/fasthttp"
|
|
"io/ioutil"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
body = flag.String("b", "", "Body (use instead of -f)")
|
|
concurrency = flag.Int("c", 1, "Number of concurrent requests")
|
|
dataFilePath = flag.String("f", "", "File path to file to be used as the body (use instead of -b)")
|
|
duration = flag.Int("t", 0, "Duration of testing in seconds (use instead of -r)")
|
|
ignoreTLS = flag.Bool("i", false, "Ignore TLS/SSL certificate validation ")
|
|
method = flag.String("m", "GET", "HTTP Method (GET,POST,PUT,DELETE)")
|
|
numberOfRequests = flag.Int("r", 1, "Number of requests (use instead of -t)")
|
|
requestsFromFile = flag.String("z", "", "Read requests from a file")
|
|
suppressOutput = flag.Bool("o", false, "Suppress output, no results will be printed to stdout")
|
|
url = flag.String("u", "", "URL to run against")
|
|
wait = flag.Int("w", 0, "Number of seconds to wait before running test")
|
|
)
|
|
|
|
// Configuration represents the Baton configuration
|
|
type Configuration struct {
|
|
body string
|
|
concurrency int
|
|
dataFilePath string
|
|
duration int
|
|
ignoreTLS bool
|
|
method string
|
|
numberOfRequests int
|
|
requestsFromFile string
|
|
suppressOutput bool
|
|
url string
|
|
wait int
|
|
}
|
|
|
|
// Baton implements the load tester
|
|
type Baton struct {
|
|
configuration Configuration
|
|
result Result
|
|
}
|
|
|
|
type preloadedRequest struct {
|
|
method string // The HTTP method used to send the request
|
|
url string // The URL to send the request at
|
|
body string // The body of the request (if appropriate method is selected)
|
|
headers [][]string // Array of two-element key/value pairs of header and value
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
configuration := Configuration{
|
|
*body,
|
|
*concurrency,
|
|
*dataFilePath,
|
|
*duration,
|
|
*ignoreTLS,
|
|
*method,
|
|
*numberOfRequests,
|
|
*requestsFromFile,
|
|
*suppressOutput,
|
|
*url,
|
|
*wait,
|
|
}
|
|
|
|
baton := &Baton{configuration: configuration, result: Result{}}
|
|
|
|
baton.run()
|
|
baton.result.printResults()
|
|
}
|
|
|
|
func (baton *Baton) run() {
|
|
|
|
logWriter := &logWriter{true}
|
|
|
|
if baton.configuration.suppressOutput {
|
|
logWriter.Disable()
|
|
}
|
|
|
|
log.SetFlags(0)
|
|
log.SetOutput(logWriter)
|
|
|
|
preloadedRequestsMode := false
|
|
timedMode := false
|
|
var preloadedRequests []preloadedRequest
|
|
var err error
|
|
|
|
if baton.configuration.requestsFromFile != "" {
|
|
preloadedRequests, err = preloadRequestsFromFile(baton.configuration.requestsFromFile)
|
|
preloadedRequestsMode = true
|
|
if err != nil {
|
|
log.Fatal("Failed to parse requests from file: " + baton.configuration.requestsFromFile)
|
|
}
|
|
}
|
|
|
|
if baton.configuration.duration != 0 {
|
|
timedMode = true
|
|
}
|
|
|
|
if baton.configuration.concurrency < 1 || baton.configuration.numberOfRequests == 0 {
|
|
log.Fatal("Invalid concurrency level or number of requests")
|
|
}
|
|
|
|
client := &fasthttp.Client{}
|
|
if baton.configuration.ignoreTLS {
|
|
tlsConfig := &tls.Config{InsecureSkipVerify: true}
|
|
client = &fasthttp.Client{TLSConfig: tlsConfig}
|
|
}
|
|
|
|
switch baton.configuration.method {
|
|
case "GET", "POST", "PUT", "DELETE":
|
|
if baton.configuration.dataFilePath != "" {
|
|
data, err := ioutil.ReadFile(baton.configuration.dataFilePath)
|
|
if err != nil {
|
|
log.Fatal(err.Error())
|
|
}
|
|
baton.configuration.body = string(data)
|
|
}
|
|
default:
|
|
log.Fatal("Invalid method specified")
|
|
}
|
|
|
|
if preloadedRequestsMode {
|
|
log.Printf("Configuring to send requests from file. (Read %d requests)\n", len(preloadedRequests))
|
|
} else {
|
|
log.Printf("Configuring to send %s requests to: %s\n", baton.configuration.method, baton.configuration.url)
|
|
}
|
|
|
|
if baton.configuration.wait > 0 {
|
|
time.Sleep(time.Duration(baton.configuration.wait) * time.Second)
|
|
}
|
|
|
|
requests := make(chan bool, baton.configuration.numberOfRequests)
|
|
results := make(chan HTTPResult, baton.configuration.concurrency)
|
|
done := make(chan bool, baton.configuration.concurrency)
|
|
|
|
log.Println("Generating the requests...")
|
|
for r := 1; r <= baton.configuration.numberOfRequests; r++ {
|
|
requests <- true
|
|
}
|
|
close(requests)
|
|
log.Println("Finished generating the requests")
|
|
log.Println("Sending the requests to the server...")
|
|
|
|
// Start the timer and kick off the workers
|
|
start := time.Now()
|
|
for w := 1; w <= baton.configuration.concurrency; w++ {
|
|
var worker workable
|
|
if timedMode {
|
|
worker = newTimedWorker(requests, results, done, float64(baton.configuration.duration))
|
|
} else {
|
|
worker = newCountWorker(requests, results, done)
|
|
}
|
|
worker.setCustomClient(client)
|
|
if preloadedRequestsMode {
|
|
go worker.sendRequests(preloadedRequests)
|
|
} else {
|
|
request := preloadedRequest{baton.configuration.method, baton.configuration.url, baton.configuration.body, [][]string{}}
|
|
go worker.sendRequest(request)
|
|
}
|
|
}
|
|
|
|
// Wait for all the workers to finish and then stop the timer
|
|
for a := 1; a <= baton.configuration.concurrency; a++ {
|
|
<-done
|
|
}
|
|
baton.result.timeTaken = time.Since(start)
|
|
|
|
log.Println("Finished sending the requests")
|
|
log.Println("Processing the results...")
|
|
|
|
for a := 1; a <= baton.configuration.concurrency; a++ {
|
|
result := <-results
|
|
baton.result.httpResult.connectionErrorCount += result.connectionErrorCount
|
|
baton.result.httpResult.status1xxCount += result.status1xxCount
|
|
baton.result.httpResult.status2xxCount += result.status2xxCount
|
|
baton.result.httpResult.status3xxCount += result.status3xxCount
|
|
baton.result.httpResult.status4xxCount += result.status4xxCount
|
|
baton.result.httpResult.status5xxCount += result.status5xxCount
|
|
}
|
|
|
|
baton.result.totalRequests = baton.result.httpResult.total()
|
|
|
|
baton.result.requestsPerSecond = int(float64(baton.result.totalRequests)/baton.result.timeTaken.Seconds() + 0.5)
|
|
}
|