You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
5.8 KiB
261 lines
5.8 KiB
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"gopkg.in/ini.v1"
|
|
"gordenko.dev/dima/diploma/client"
|
|
)
|
|
|
|
const (
|
|
listCumulativeMeasures = 0
|
|
listCumulativePeriods = 1
|
|
listInstantMeasures = 2
|
|
listInstantPeriods = 3
|
|
listCurrentValues = 4
|
|
|
|
methodsQty = 5
|
|
timeRangesQty = 4
|
|
)
|
|
|
|
var (
|
|
methodCodeToName = []string{
|
|
"listCumulativeMeasures",
|
|
"listCumulativePeriods",
|
|
"listInstantMeasures",
|
|
"listInstantPeriods",
|
|
"listCurrentValues",
|
|
}
|
|
|
|
rangeCodeToName = []string{
|
|
"last day",
|
|
"last week",
|
|
"last month",
|
|
"random time range",
|
|
}
|
|
)
|
|
|
|
type WorkerStat struct {
|
|
Queries int
|
|
ElapsedTime time.Duration
|
|
MethodCalls []int
|
|
ElapsedTimeByMethods []time.Duration
|
|
TimeRangeCalls []int
|
|
ElapsedTimeByTimeRanges []time.Duration
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
iniFileName string
|
|
)
|
|
|
|
flag.Usage = func() {
|
|
fmt.Fprint(flag.CommandLine.Output(), helpMessage)
|
|
fmt.Fprint(flag.CommandLine.Output(), configExample)
|
|
fmt.Fprintf(flag.CommandLine.Output(), mainUsage, os.Args[0])
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.StringVar(&iniFileName, "c", "loadtest.ini", "path to *.ini config file")
|
|
flag.Parse()
|
|
|
|
config, err := loadConfig(iniFileName)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
metrics, err := readMetricInfo(config.MetricsInfo)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
var (
|
|
wg = new(sync.WaitGroup)
|
|
stats = make([]*WorkerStat, config.Connections)
|
|
queryGenerator = NewRandomQueryGenerator(RandomQueryGeneratorOptions{
|
|
Metrics: metrics,
|
|
// call method probabilitites
|
|
ListCurrentValuesProbability: 50, // current values / others
|
|
ListPeriodsProbability: 80, // periods / measures
|
|
// time range probabilities
|
|
TimeRangeProbabilities: []int{
|
|
82, // last day
|
|
12, // last week
|
|
3, // last month
|
|
3, // any range
|
|
},
|
|
})
|
|
)
|
|
|
|
for i := range stats {
|
|
stats[i] = &WorkerStat{
|
|
MethodCalls: make([]int, methodsQty),
|
|
ElapsedTimeByMethods: make([]time.Duration, methodsQty),
|
|
TimeRangeCalls: make([]int, timeRangesQty),
|
|
ElapsedTimeByTimeRanges: make([]time.Duration, timeRangesQty),
|
|
}
|
|
}
|
|
|
|
t1 := time.Now()
|
|
|
|
for i := range config.Connections {
|
|
wg.Add(1)
|
|
go func(stat *WorkerStat) {
|
|
defer wg.Done()
|
|
|
|
conn, err := client.Connect(config.DatabaseAddr)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
for range config.RequestsPerConn {
|
|
err := execQuery(conn, queryGenerator, stat)
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
}(stats[i])
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
testingTime := time.Since(t1)
|
|
|
|
var (
|
|
methodCalls = make([]int, methodsQty)
|
|
elapsedTimeByMethods = make([]time.Duration, methodsQty)
|
|
timeRangeCalls = make([]int, timeRangesQty)
|
|
elapsedTimeByTimeRanges = make([]time.Duration, timeRangesQty)
|
|
totalElapsedTime time.Duration
|
|
totalQueries int
|
|
avgTimePerQuery time.Duration
|
|
rps float64
|
|
)
|
|
|
|
for _, stat := range stats {
|
|
totalElapsedTime += stat.ElapsedTime
|
|
totalQueries += stat.Queries
|
|
|
|
for i, elapsedTime := range stat.ElapsedTimeByMethods {
|
|
elapsedTimeByMethods[i] += elapsedTime
|
|
}
|
|
for i, qty := range stat.MethodCalls {
|
|
methodCalls[i] += qty
|
|
}
|
|
for i, elapsedTime := range stat.ElapsedTimeByTimeRanges {
|
|
elapsedTimeByTimeRanges[i] += elapsedTime
|
|
}
|
|
for i, qty := range stat.TimeRangeCalls {
|
|
timeRangeCalls[i] += qty
|
|
}
|
|
}
|
|
|
|
avgTimePerQuery = totalElapsedTime / time.Duration(totalQueries)
|
|
rps = float64(config.Connections*config.RequestsPerConn) / testingTime.Seconds()
|
|
|
|
fmt.Printf(`TEST RESULTS:
|
|
Time: %.0f seconds
|
|
Connections: %d
|
|
Requests per conn: %d
|
|
Total requests: %d
|
|
AVG request time: %v
|
|
RPS: %d
|
|
|
|
`,
|
|
testingTime.Seconds(), config.Connections, config.RequestsPerConn,
|
|
totalQueries, avgTimePerQuery, int(rps))
|
|
|
|
for i, calls := range methodCalls {
|
|
totalElapsedTimeByMethod := elapsedTimeByMethods[i]
|
|
|
|
methodPercent := float64(calls*100) / float64(totalQueries)
|
|
|
|
fmt.Printf("%s: %d (%.1f%%), AVG request time: %v\n",
|
|
methodCodeToName[i], calls, methodPercent,
|
|
totalElapsedTimeByMethod/time.Duration(calls))
|
|
}
|
|
|
|
fmt.Println()
|
|
|
|
for i, calls := range timeRangeCalls {
|
|
totalElapsedTimeByTimeRange := elapsedTimeByTimeRanges[i]
|
|
|
|
timeRangePercent := float64(calls*100) / float64(totalQueries-methodCalls[listCurrentValues])
|
|
|
|
fmt.Printf("%s: %d (%.1f%%), AVG request time: %v\n",
|
|
rangeCodeToName[i], calls, timeRangePercent,
|
|
totalElapsedTimeByTimeRange/time.Duration(calls))
|
|
}
|
|
}
|
|
|
|
// CONFIG FILE
|
|
|
|
const mainUsage = `Usage:
|
|
%s -c path/to/config.ini
|
|
|
|
`
|
|
|
|
const helpMessage = `Diploma project. Load test. Version: 1.0
|
|
created by Dmytro Gordenko, 1.e4.kc6@gmail.com
|
|
`
|
|
|
|
const configExample = `
|
|
loadtest.ini example:
|
|
|
|
databaseAddr = :12345
|
|
metricsInfo = ../../datadir/metrics.info
|
|
connections = 1000
|
|
requestsPerConn = 500
|
|
|
|
`
|
|
|
|
type Config struct {
|
|
DatabaseAddr string
|
|
MetricsInfo string
|
|
Connections int
|
|
RequestsPerConn int
|
|
}
|
|
|
|
func (s Config) String() string {
|
|
return fmt.Sprintf(`starting options:
|
|
databaseAddr = %s
|
|
metricsInfo = %s
|
|
connections = %d
|
|
requestsPerConn = %d
|
|
`,
|
|
s.DatabaseAddr, s.MetricsInfo, s.Connections, s.RequestsPerConn)
|
|
}
|
|
|
|
func loadConfig(iniFileName string) (_ Config, err error) {
|
|
file, err := ini.Load(iniFileName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
conf := Config{}
|
|
|
|
top := file.Section("")
|
|
|
|
conf.DatabaseAddr = top.Key("databaseAddr").String()
|
|
conf.MetricsInfo = top.Key("metricsInfo").String()
|
|
|
|
conf.Connections, err = top.Key("connections").Int()
|
|
if err != nil {
|
|
err = fmt.Errorf("'connections' option is required in config file")
|
|
return
|
|
}
|
|
conf.RequestsPerConn, err = top.Key("requestsPerConn").Int()
|
|
if err != nil {
|
|
err = fmt.Errorf("'requestsPerConn' option is required in config file")
|
|
return
|
|
}
|
|
return conf, nil
|
|
}
|
|
|