спеціалізована СУБД для зберігання та обробки показань датчиків та лічильників
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

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
}