redisLoadTest/redisLoadTest.go
mitch ee8c466b78 Added auth support
Improved pool handling
Improved ticker for rate test
2022-01-04 19:19:31 -05:00

236 lines
6.4 KiB
Go

package main
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <mitch@nerdfortress.dev> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return
* ----------------------------------------------------------------------------
*/
import (
"flag"
"fmt"
"github.com/gomodule/redigo/redis"
"github.com/montanaflynn/stats"
"math"
"math/rand"
"sync"
"time"
)
/**
buildTestData iterates through a goroutine of setRedis to build test data into the redis db
@param redisPtr connection to redis
@param size int number of entries into the database
*/
func buildTestData(rdbPtr *redis.Conn, size int) {
fmt.Println("Initializing Test Data...")
var waitGroup sync.WaitGroup
for i := 1; i < 50000; i++ {
key := i
value := "ThisIsATestStringThatShouldBeReplacedWithSomethingMoreRandomMaybeFromAnInputOrCSV"
setRedis(rdbPtr, key, value, &waitGroup)
}
fmt.Println("Database Initialized...")
}
/**
getRateTest calculates closest value for requests per milliseconds from inputted requests per second
Then starts goroutines for getRedis at that interval
@param rdbPtr connection is a pointer to the redis connection
@param rate int requests per second for the test
@param keyMaxValue int max value to query the database and expect a return
*/
func getRateTest(rdbPtr *redis.Conn, rate int, keyMaxValue int, duration time.Duration) {
durationChannel := make(chan bool, 1)
x := int(math.Abs(60 / float64(rate) * 10000000))
rateLimiter := time.Tick(time.Duration(x))
fmt.Println("Starting Test at", rateLimiter, "milliseconds per request...")
go func() {
for i := 0; i < int(duration.Seconds()); i++ {
<-time.Tick(time.Second)
}
durationChannel <- true
}()
end := false
for {
if end {
break
}
select {
case <-durationChannel:
fmt.Println("Closing Test...")
end = true
case <-rateLimiter:
key := rand.Intn(keyMaxValue)
go getRedis(rdbPtr, key)
}
}
fmt.Println("Rate test concluded...")
}
/**
getBurstTest starts goroutines for getRedis at the burstRateLimit per second
@param rdbPtr connection is a pointer to the redis connection
@param rate int requests per second for the test
@param keyMaxValue int max value to query the database and expect a return
*/
func getBurstTest(rdbPtr *redis.Conn, rate int, keyMaxValue int, duration time.Duration) {
durationChannel := make(chan bool, 1)
burstRateLimiter := time.Tick(time.Second)
go func() {
for i := 0; i < int(duration.Seconds()); i++ {
<-time.Tick(time.Second)
}
durationChannel <- true
}()
fmt.Println("Starting burst test at", rate, "requests per second...")
end := false
for {
if end {
break
}
select {
case <-durationChannel:
end = true
case <-burstRateLimiter:
for i := 0; i < rate; i++ {
key := rand.Intn(keyMaxValue)
go getRedis(rdbPtr, key)
}
}
}
fmt.Println("Burst test concluded...")
}
/**
setRateTest sets key/value into database
@param rdbPtr connection is a pointer to the redis connection
@param key int redis key for the storage
@param value string string for the value to store
*/
func setRedis(rdbPtr *redis.Conn, key int, value string, waitGroup *sync.WaitGroup) {
rdb := *rdbPtr
//defer waitGroup.Done()
_, err := rdb.Do("Set", key, value)
if err != nil {
fmt.Println(err, key, value)
panic(err)
}
}
/**
getRedis queries the redis database for value of key
@param rdbPtr connection is a pointer to the redis connection
@param key int is key value to query in database
*/
func getRedis(rdbPtr *redis.Conn, key int) {
rdb := *rdbPtr
_, err := rdb.Do("Get", key)
if err != nil {
fmt.Println("Unhandled error:", err)
}
}
/**
newPool builds redis database connection
@param host string host:port of redis server
returns redis.Connection of connection to database
returns err from database connection
*/
func newPool(host string, username string, password string) *redis.Pool {
return &redis.Pool{
MaxIdle: 80,
MaxActive: 12000,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", host)
if err != nil {
panic(err.Error())
}
if username != "" && password != "" {
_, err = c.Do("AUTH", password)
if err != nil {
c.Close()
panic(err.Error())
}
}
return c, err
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}
func keepTotal(timesChan chan float64, errorChan chan bool, endChan chan bool) ([]float64, int) {
var timesSlice []float64
var errorRate int
for {
select {
case <-endChan:
break
case timeValue := <-timesChan:
timesSlice = append(timesSlice, timeValue)
case <-errorChan:
errorRate++
}
}
return timesSlice, errorRate
}
/**
getResponseTimes takes a list of ints and returns mean, stddev, 95p, 99p
Uses stats library to calculate mean, stddev, 95p, 99p
*/
func getResponseTimes(times []float64) (float64, float64, float64, float64) {
mean, _ := stats.Mean(times)
stddev, _ := stats.StandardDeviation(times)
perc99, _ := stats.Percentile(times, 99.00)
perc95, _ := stats.Percentile(times, 95.00)
return mean, stddev, perc99, perc95
}
func main() {
hostPtr := flag.String("host", "", "Redis Server FQDN:port")
usernamePtr := flag.String("username", "", "Redis Server username")
passwordPtr := flag.String("password", "", "Redis user password")
//dbPtr := flag.Int("db", 0, "Redis db, default 0")
initializeDBPtr := flag.Bool("initialize", false, "Boolean initialize db, default false")
ratePtr := flag.Int("rate", 5, "Test rate limit, default 50/sec")
burstPtr := flag.Bool("burst", false, "Boolean burst test default true")
//todo this should be off that same csv rather than an input
maxEntriesPtr := flag.Int("dbEntries", 50000, "Test rate limit, default 50/sec")
testDurationPtr := flag.Int("duration", 10, "Duration of each test")
flag.Parse()
host := *hostPtr
username := *usernamePtr
password := *passwordPtr
//db := *dbPtr
initializeDB := *initializeDBPtr
rate := *ratePtr
burst := *burstPtr
keyMaxValue := *maxEntriesPtr
durationInt := *testDurationPtr
duration := time.Second * time.Duration(durationInt)
pool := newPool(host, username, password)
client := pool.Get()
defer client.Close()
if initializeDB {
buildTestData(&client, 50000)
}
getRateTest(&client, rate, keyMaxValue, duration)
if burst {
getBurstTest(&client, rate, keyMaxValue, duration)
}
fmt.Println("Tests completed...")
}