diff --git a/burstTest.go b/burstTest.go index ef2d311..061c12d 100644 --- a/burstTest.go +++ b/burstTest.go @@ -3,6 +3,7 @@ package main import ( "fmt" "github.com/gomodule/redigo/redis" + "math" "math/rand" "time" ) @@ -15,14 +16,22 @@ getBurstTest starts goroutines for getRedis at the burstRateLimit per second */ func getBurstTest(rdbPtr *redis.Conn, rate int, keyMaxValue int, duration time.Duration) { durationChannel := make(chan bool, 1) - burstRateLimiter := time.Tick(time.Second) + burstRateLimiter := time.Tick(time.Millisecond * 250) + timesChan := make(chan time.Duration) + errorChan := make(chan bool) + endChan := make(chan bool) + totalErrorChan := make(chan int) + totalTimeChan := make(chan []int64) + quaterRate := math.Abs(float64(rate) / 4) 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...") + + go keepTotal(timesChan, errorChan, endChan, totalTimeChan, totalErrorChan) + fmt.Println("Starting burst test at", quaterRate, "requests per 250 milliseconds...") end := false for { if end { @@ -32,11 +41,26 @@ func getBurstTest(rdbPtr *redis.Conn, rate int, keyMaxValue int, duration time.D case <-durationChannel: end = true case <-burstRateLimiter: - for i := 0; i < rate; i++ { + for i := float64(0); i < quaterRate; i++ { + //this might need toggled + time.Sleep(time.Millisecond * 5) key := rand.Intn(keyMaxValue) - go getRedis(rdbPtr, key) + go getRedis(rdbPtr, key, timesChan, errorChan) } } } + + endChan <- true + totalTimesFloat := <-totalTimeChan + totalErrors := <-totalErrorChan + errorRate := totalErrors / len(totalTimesFloat) + totalTimes := intToFloat(totalTimesFloat) + mean, stddev, perc99, perc95 := getResponseTimes(totalTimes) fmt.Println("Burst test concluded...") + fmt.Println("Total Requests:", len(totalTimesFloat)) + fmt.Println("Error Rate:", errorRate, "ms") + fmt.Println("Mean:", mean, "ms") + fmt.Println("Standard Deviation:", stddev, "ms") + fmt.Println("95 Percentile:", perc95, "ms") + fmt.Println("99 Percentile:", perc99, "ms") } diff --git a/rateTest.go b/rateTest.go index 2cd2af3..ab50560 100644 --- a/rateTest.go +++ b/rateTest.go @@ -17,10 +17,17 @@ Then starts goroutines for getRedis at that interval */ 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)) + x := int(math.Abs(100 / float64(rate) * 10000000)) rateLimiter := time.Tick(time.Duration(x)) - fmt.Println("Starting Test at", rateLimiter, "milliseconds per request...") + timesChan := make(chan time.Duration) + errorChan := make(chan bool) + endChan := make(chan bool) + totalErrorChan := make(chan int) + totalTimeChan := make(chan []int64) + + go keepTotal(timesChan, errorChan, endChan, totalTimeChan, totalErrorChan) + fmt.Println("Starting Test at", time.Duration(x), "milliseconds per request...") go func() { for i := 0; i < int(duration.Seconds()); i++ { <-time.Tick(time.Second) @@ -39,8 +46,20 @@ func getRateTest(rdbPtr *redis.Conn, rate int, keyMaxValue int, duration time.Du end = true case <-rateLimiter: key := rand.Intn(keyMaxValue) - go getRedis(rdbPtr, key) + go getRedis(rdbPtr, key, timesChan, errorChan) } } + endChan <- true + totalTimesFloat := <-totalTimeChan + totalErrors := <-totalErrorChan + errorRate := totalErrors / len(totalTimesFloat) + totalTimes := intToFloat(totalTimesFloat) + mean, stddev, perc99, perc95 := getResponseTimes(totalTimes) fmt.Println("Rate test concluded...") + fmt.Println("Total Requests:", len(totalTimesFloat)) + fmt.Println("Error Rate:", errorRate, "ms") + fmt.Println("Mean:", mean, "ms") + fmt.Println("Standard Deviation:", stddev, "ms") + fmt.Println("95 Percentile:", perc95, "ms") + fmt.Println("99 Percentile:", perc99, "ms") } diff --git a/redisHandler.go b/redisHandler.go index 6317898..6870a62 100644 --- a/redisHandler.go +++ b/redisHandler.go @@ -3,7 +3,6 @@ package main import ( "fmt" "github.com/gomodule/redigo/redis" - "sync" "time" ) @@ -15,7 +14,7 @@ returns err from database connection */ func newPool(host string, username string, password string) *redis.Pool { return &redis.Pool{ - MaxIdle: 80, + MaxIdle: 12000, MaxActive: 12000, Dial: func() (redis.Conn, error) { c, err := redis.Dial("tcp", host) @@ -44,7 +43,7 @@ setRateTest sets key/value into database @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) { +func setRedis(rdbPtr *redis.Conn, key int, value string) { rdb := *rdbPtr //defer waitGroup.Done() _, err := rdb.Do("Set", key, value) @@ -61,11 +60,10 @@ buildTestData iterates through a goroutine of setRedis to build test data into t */ func buildTestData(rdbPtr *redis.Conn, size int) { fmt.Println("Initializing Test Data...") - var waitGroup sync.WaitGroup - for i := 1; i < 50000; i++ { + for i := 1; i < size; i++ { key := i value := "ThisIsATestStringThatShouldBeReplacedWithSomethingMoreRandomMaybeFromAnInputOrCSV" - setRedis(rdbPtr, key, value, &waitGroup) + setRedis(rdbPtr, key, value) } fmt.Println("Database Initialized...") } @@ -75,10 +73,14 @@ 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) { +func getRedis(rdbPtr *redis.Conn, key int, timesChan chan time.Duration, errorChan chan bool) { rdb := *rdbPtr + startTime := time.Now() _, err := rdb.Do("Get", key) + responseTime := time.Now().Sub(startTime) if err != nil { + errorChan <- true fmt.Println("Unhandled error:", err) } + timesChan <- responseTime } diff --git a/redisLoadTest.go b/redisLoadTest.go index 19ca492..d547e13 100644 --- a/redisLoadTest.go +++ b/redisLoadTest.go @@ -21,11 +21,11 @@ func main() { 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") + ratePtr := flag.Int("rate", 50, "Test rate limit, default 30/sec") + burstPtr := flag.Bool("burst", true, "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") + testDurationPtr := flag.Int("duration", 50, "Duration of each test") flag.Parse() host := *hostPtr @@ -46,7 +46,17 @@ func main() { if initializeDB { buildTestData(&client, 50000) } + client.Close() + //refreshing state for next test + pool = newPool(host, username, password) + client = pool.Get() + defer client.Close() getRateTest(&client, rate, keyMaxValue, duration) + client.Close() + //refreshing state for next test + pool = newPool(host, username, password) + client = pool.Get() + defer client.Close() if burst { getBurstTest(&client, rate, keyMaxValue, duration) } diff --git a/testHandler.go b/testHandler.go index bf17e61..18e1912 100644 --- a/testHandler.go +++ b/testHandler.go @@ -1,6 +1,20 @@ package main -import "github.com/montanaflynn/stats" +import ( + "github.com/montanaflynn/stats" + "time" +) + +/** +intToFloat takes []int64 and converts it to []float64 for processing +*/ +func intToFloat(times []int64) []float64 { + var floatArray []float64 + for timeRange := range times { + floatArray = append(floatArray, float64(timeRange)) + } + return floatArray +} /** getResponseTimes takes a list of ints and returns mean, stddev, 95p, 99p @@ -14,8 +28,16 @@ func getResponseTimes(times []float64) (float64, float64, float64, float64) { return mean, stddev, perc99, perc95 } -func keepTotal(timesChan chan float64, errorChan chan bool, endChan chan bool) ([]float64, int) { - var timesSlice []float64 +/** +keepTotal takes several channels and keeps a running total of +duration and errors +*/ +func keepTotal(timesChan chan time.Duration, + errorChan chan bool, + endChan chan bool, + totalTimeChan chan []int64, + totalErrorChan chan int) { + var timesSlice []int64 var errorRate int end := false for { @@ -24,14 +46,16 @@ func keepTotal(timesChan chan float64, errorChan chan bool, endChan chan bool) ( } select { case timeValue := <-timesChan: - timesSlice = append(timesSlice, timeValue) + typedTimeValue := int64(1) * timeValue.Microseconds() + timesSlice = append(timesSlice, typedTimeValue) case <-errorChan: - errorRate++ + errorRate += 1 case <-endChan: if len(errorChan) == 0 && len(timesChan) == 0 { end = true } } } - return timesSlice, errorRate + totalTimeChan <- timesSlice + totalErrorChan <- errorRate }