1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-18 14:47:03 +00:00
Files
go-chasquid-smtp/test/util/loadgen.go
Alberto Bertogli 4357379737 test: Implement retries on the load generator
If the load generator is sending emails too fast, chasquid queue might
hit the maximum size and fail the test.

This patch makes it sleep and retry, to give the server some time to
catch up.

Thanks to Max Mazurov (fox.cpp@disroot.org) for reporting this problem.
2020-03-28 16:42:04 +00:00

208 lines
3.7 KiB
Go

// +build ignore
// SMTP load generator, for testing purposes.
package main
import (
"flag"
"net"
"net/http"
"net/textproto"
"runtime"
"sync"
"time"
_ "net/http/pprof"
"golang.org/x/net/trace"
"blitiri.com.ar/go/chasquid/internal/smtp"
"blitiri.com.ar/go/log"
)
var (
addr = flag.String("addr", "",
"server address")
httpAddr = flag.String("http_addr", "localhost:8011",
"monitoring HTTP server listening address")
parallel = flag.Int("parallel", 0,
"how many sending loops to run in parallel")
runFor = flag.Duration("run_for", 0,
"how long to run for (0 = forever)")
wait = flag.Bool("wait", false,
"don't exit after --run_for has lapsed")
noop = flag.Bool("noop", false,
"don't send an email, just connect and run a NOOP")
)
var (
host string
exit bool
globalCount int64 = 0
globalRuntime time.Duration
globalMu = &sync.Mutex{}
)
func main() {
var err error
flag.Parse()
log.Init()
host, _, err = net.SplitHostPort(*addr)
if err != nil {
log.Fatalf("failed to split --addr=%q: %v", *addr, err)
}
if *wait {
go http.ListenAndServe(*httpAddr, nil)
log.Infof("monitoring address: http://%v/debug/requests?fam=one&b=11",
*httpAddr)
}
if *parallel == 0 {
*parallel = runtime.GOMAXPROCS(0)
}
lt := "full"
if *noop {
lt = "noop"
}
log.Infof("launching %d %s sending loops in parallel", *parallel, lt)
for i := 0; i < *parallel; i++ {
go serial(i)
}
var totalCount int64
var totalRuntime time.Duration
start := time.Now()
for range time.Tick(1 * time.Second) {
globalMu.Lock()
totalCount += globalCount
totalRuntime += globalRuntime
count := globalCount
runtime := globalRuntime
globalCount = 0
globalRuntime = 0
globalMu.Unlock()
if count == 0 {
log.Infof("0 ops")
} else {
log.Infof("%d ops, %v /op", count,
time.Duration(runtime.Nanoseconds()/count).Truncate(time.Microsecond))
}
if *runFor > 0 && time.Since(start) > *runFor {
exit = true
break
}
}
end := time.Now()
window := end.Sub(start)
log.Infof("total: %d ops, %v wall, %v run",
totalCount,
window.Truncate(time.Millisecond),
totalRuntime.Truncate(time.Millisecond))
avgLat := time.Duration(totalRuntime.Nanoseconds() / totalCount)
log.Infof("avg: %v /op, %.0f ops/s",
avgLat.Truncate(time.Microsecond),
float64(totalCount)/window.Seconds(),
)
if *wait {
for {
time.Sleep(24 * time.Hour)
}
}
}
func serial(id int) {
var count int64
start := time.Now()
for {
count += 1
err := one()
if err != nil {
log.Fatalf("%v", err)
}
if count == 5 {
globalMu.Lock()
globalCount += count
globalRuntime += time.Since(start)
globalMu.Unlock()
count = 0
start = time.Now()
if exit {
return
}
}
}
}
func one() error {
tr := trace.New("one", *addr)
defer tr.Finish()
conn, err := net.Dial("tcp", *addr)
if err != nil {
return err
}
defer conn.Close()
client, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
defer client.Close()
if *noop {
err = client.Noop()
if err != nil {
return err
}
} else {
err = client.MailAndRcpt("test@test", "null@testserver")
if err != nil {
return err
}
retry:
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write(body)
if err != nil {
return err
}
err = w.Close()
if err != nil {
// If we are sending too fast we might hit chasquid's queue size
// limit. In that case, wait and try again.
// We detect it with error code 451 which is used for this
// situation.
if terr, ok := err.(*textproto.Error); ok {
if terr.Code == 451 {
time.Sleep(10 * time.Millisecond)
goto retry
}
}
return err
}
}
return nil
}
var body = []byte(`Subject: Load test
This is the body of the load test email.
`)