Initial Commit
This commit is contained in:
commit
444bd9144b
105
.gitignore
vendored
Normal file
105
.gitignore
vendored
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
|
||||||
|
# Edit at https://www.gitignore.io/?templates=macos,visualstudiocode,terraform,windows
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### Terraform ###
|
||||||
|
# Local .terraform directories
|
||||||
|
**/.terraform/*
|
||||||
|
|
||||||
|
# .tfstate files
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.*
|
||||||
|
*.plan
|
||||||
|
|
||||||
|
# Crash log files
|
||||||
|
crash.log
|
||||||
|
|
||||||
|
# Ignore any .tfvars files that are generated automatically for each Terraform run. Most
|
||||||
|
# .tfvars files are managed as part of configuration and so should be included in
|
||||||
|
# version control.
|
||||||
|
#
|
||||||
|
# example.tfvars
|
||||||
|
|
||||||
|
# Ignore override files as they are usually used to override resources locally and so
|
||||||
|
# are not checked in
|
||||||
|
override.tf
|
||||||
|
override.tf.json
|
||||||
|
*_override.tf
|
||||||
|
*_override.tf.json
|
||||||
|
secrets.tfvars
|
||||||
|
|
||||||
|
# Include override files you do wish to add to version control using negated pattern
|
||||||
|
#
|
||||||
|
# !example_override.tf
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode/*
|
||||||
|
#!.vscode/settings.json
|
||||||
|
#!.vscode/tasks.json
|
||||||
|
#!.vscode/launch.json
|
||||||
|
#!.vscode/extensions.json
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
|
||||||
|
|
||||||
|
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
|
.idea
|
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
FROM golang:1.17.5-bullseye AS builder
|
||||||
|
COPY go.mod .
|
||||||
|
COPY redisLoadTest.go .
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& go build -o redisLoadTest redisLoadTest.go
|
||||||
|
|
||||||
|
FROM golang:1.17.5-bullseye AS redisloadtest
|
||||||
|
|
||||||
|
ENV USER=redisloadtest
|
||||||
|
ENV FILEPATH=/usr/local/bin/
|
||||||
|
ENV FILENAME=redisLoadTest
|
||||||
|
ENV HOME=/home/${USER}
|
||||||
|
|
||||||
|
RUN useradd ${USER} --home-dir ${HOME}
|
||||||
|
|
||||||
|
WORKDIR /tmp
|
||||||
|
|
||||||
|
# Copy the binary from the builder stage
|
||||||
|
COPY --from=builder ${FILENAME} ./
|
||||||
|
|
||||||
|
RUN mv /tmp/${FILENAME} ${FILEPATH} \
|
||||||
|
&& chown ${USER}:${USER} ${FILEPATH}${FILENAME} \
|
||||||
|
&& chmod 4555 ${FILENAME}${FILEPATH} \
|
||||||
|
&& rm -rf -- /tmp/* \
|
||||||
|
|
||||||
|
WORKDIR ${HOME}
|
||||||
|
|
||||||
|
CMD ["${FILEPATH}${FILENAME}"]
|
32
Makefile
Normal file
32
Makefile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
####################################################
|
||||||
|
# Build Container
|
||||||
|
####################################################
|
||||||
|
build:
|
||||||
|
docker build -t redisloadtest .
|
||||||
|
####################################################
|
||||||
|
# Local Build
|
||||||
|
####################################################
|
||||||
|
build-local:
|
||||||
|
go build -o redisLoadTest redisLoadTest.go
|
||||||
|
####################################################
|
||||||
|
# Prune docker environment of stale resources
|
||||||
|
####################################################
|
||||||
|
clean-docker:
|
||||||
|
docker system prune -a -f
|
||||||
|
####################################################
|
||||||
|
# Perform nothing
|
||||||
|
####################################################
|
||||||
|
all:
|
||||||
|
@echo "Nothing to do."
|
||||||
|
####################################################
|
||||||
|
# help feature
|
||||||
|
####################################################
|
||||||
|
help:
|
||||||
|
@echo ''
|
||||||
|
@echo 'Usage: make [TARGET]'
|
||||||
|
@echo 'Targets:'
|
||||||
|
@echo ' build build docker --image--'
|
||||||
|
@echo ' build-local go build -o redisLoadTest redisLoadTest.go'
|
||||||
|
@echo ' clean-docker shortcut for down and docker system prune -af. Cleanup inactive containers and cache.'
|
||||||
|
@echo ' all Nothing to do.'
|
||||||
|
@echo ''
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#Redis Load Test
|
||||||
|
A simple program to perform load testing against a redis instance
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- go compiler
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module redis
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/gomodule/redigo v1.8.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
14
go.sum
Normal file
14
go.sum
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gomodule/redigo v1.8.6 h1:h7kHSqUl2kxeaQtVslsfUCPJ1oz2pxcyzLy4zezIzPw=
|
||||||
|
github.com/gomodule/redigo v1.8.6/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
157
redisLoadTest.go
Normal file
157
redisLoadTest.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
|
/**
|
||||||
|
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 := 0; i < size; i++ {
|
||||||
|
key := i
|
||||||
|
value := "ThisIsATestStringThatShouldBeReplacedWithSomethingMoreRandomMaybeFromAnInputOrCSV"
|
||||||
|
go setRedis(rdbPtr, key, value, &waitGroup)
|
||||||
|
waitGroup.Add(1)
|
||||||
|
}
|
||||||
|
waitGroup.Wait()
|
||||||
|
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) {
|
||||||
|
x := int(math.Abs(60 / float64(rate) * 1000))
|
||||||
|
rateLimiter := time.Tick(time.Millisecond * time.Duration(x))
|
||||||
|
|
||||||
|
fmt.Println("Starting Test at", rateLimiter, "milliseconds per request...")
|
||||||
|
//todo add end to test
|
||||||
|
for {
|
||||||
|
<-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) {
|
||||||
|
burstRateLimiter := time.Tick(time.Second)
|
||||||
|
//todo add end to test
|
||||||
|
fmt.Println("Starting burst test at", rate, "requests per second...")
|
||||||
|
for {
|
||||||
|
<-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
|
||||||
|
_, err := rdb.Do("Set", key, value)
|
||||||
|
if err != nil {
|
||||||
|
defer waitGroup.Done()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer waitGroup.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
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) *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())
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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", 50, "Test rate limit, default 50/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")
|
||||||
|
|
||||||
|
host := *hostPtr
|
||||||
|
//todo add username/password support
|
||||||
|
//username := *usernamePtr
|
||||||
|
//password := *passwordPtr
|
||||||
|
//db := *dbPtr
|
||||||
|
initializeDB := *initializeDBPtr
|
||||||
|
rate := *ratePtr
|
||||||
|
burst := *burstPtr
|
||||||
|
keyMaxValue := *maxEntriesPtr
|
||||||
|
|
||||||
|
pool := newPool(host)
|
||||||
|
client := pool.Get()
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if initializeDB {
|
||||||
|
buildTestData(&client, 50000)
|
||||||
|
}
|
||||||
|
getRateTest(&client, rate, keyMaxValue)
|
||||||
|
if burst {
|
||||||
|
getBurstTest(&client, rate, keyMaxValue)
|
||||||
|
}
|
||||||
|
fmt.Println("Tests completed...")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user