mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-22 19:43:51 -05:00
400 lines
10 KiB
Go
400 lines
10 KiB
Go
// Copyright 2019-2022 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package sysbench_runner
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
stampFormat = time.RFC3339
|
|
SqlStatsPrefix = "SQL statistics:"
|
|
read = "read"
|
|
write = "write"
|
|
other = "other"
|
|
totalQueries = "total"
|
|
totalEvents = "total number of events"
|
|
min = "min"
|
|
avg = "avg"
|
|
max = "max"
|
|
percentile = "percentile"
|
|
sum = "sum"
|
|
transactions = "transactions"
|
|
queriesPerSec = "queries"
|
|
ignoredErrors = "ignored errors"
|
|
reconnects = "reconnects"
|
|
totalTimeSecs = "total time"
|
|
)
|
|
|
|
var (
|
|
ResultFileTemplate = "%s_%s_%s_sysbench_performance%s"
|
|
|
|
ErrUnableToParseOutput = errors.New("unable to parse output")
|
|
ErrUnsupportedHeaderField = errors.New("unsupported header field")
|
|
)
|
|
|
|
// Result stores the output from a sysbench test run
|
|
type Result struct {
|
|
// Id is the uuid of this result row
|
|
Id string `json:"id"`
|
|
|
|
// SuiteId is the test suite id this test result is associated with
|
|
SuiteId string `json:"suite_id"`
|
|
|
|
// TestId is the unique id for this test
|
|
TestId string `json:"test_id"`
|
|
|
|
// RuntimeOS is the runtime platform
|
|
RuntimeOS string `json:"runtime_os"`
|
|
|
|
// RuntimeGoArch is the runtime architecture
|
|
RuntimeGoArch string `json:"runtime_goarch"`
|
|
|
|
// ServerName is the name of the server used for the benchmark
|
|
ServerName string `json:"server_name"`
|
|
|
|
// ServerVersion is the server's version
|
|
ServerVersion string `json:"server_version"`
|
|
|
|
// ServerParams are the params used to run the server
|
|
ServerParams string `json:"server_params"`
|
|
|
|
// TestName is the name of the test
|
|
TestName string `json:"test_name"`
|
|
|
|
// TestParams are the params used to run the test
|
|
TestParams string `json:"test_params"`
|
|
|
|
// CreatedAt is the time the result was created UTC
|
|
CreatedAt string `json:"created_at"`
|
|
|
|
// SqlReadQueries is the number of read queries performed
|
|
SqlReadQueries int64 `json:"sql_read_queries"`
|
|
|
|
// SqlWriteQueries is the number of write queries performed
|
|
SqlWriteQueries int64 `json:"sql_write_queries"`
|
|
|
|
// SqlOtherQueries is the number of other queries performed
|
|
SqlOtherQueries int64 `json:"sql_other_queries"`
|
|
|
|
// SqlTotalQueries is the number of total queries performed
|
|
SqlTotalQueries int64 `json:"sql_total_queries"`
|
|
|
|
// SqlTotalQueriesPerSecond is the number of queries per second
|
|
SqlTotalQueriesPerSecond float64 `json:"sql_total_queries_per_second"`
|
|
|
|
// TransactionsTotal is the number of transactions performed
|
|
TransactionsTotal int64 `json:"sql_transactions_total"`
|
|
|
|
// TransactionsPerSecond is the number of transactions per second
|
|
TransactionsPerSecond float64 `json:"sql_transactions_per_second"`
|
|
|
|
// IgnoredErrorsTotal is the number of errors ignored
|
|
IgnoredErrorsTotal int64 `json:"sql_ignored_errors_total"`
|
|
|
|
// IgnoredErrorsPerSecond is the number of errors ignored per second
|
|
IgnoredErrorsPerSecond float64 `json:"sql_ignored_errors_per_second"`
|
|
|
|
// ReconnectsTotal is the number of reconnects performed
|
|
ReconnectsTotal int64 `json:"sql_reconnects_total"`
|
|
|
|
// ReconnectsPerSecond is the number of reconnects per second
|
|
ReconnectsPerSecond float64 `json:"sql_reconnects_per_second"`
|
|
|
|
// TotalTimeSeconds is the total time elapsed for this test
|
|
TotalTimeSeconds float64 `json:"total_time_seconds"`
|
|
|
|
// TotalNumberOfEvents is the total number of events
|
|
TotalNumberOfEvents int64 `json:"total_number_of_events"`
|
|
|
|
// LatencyMinMS is the minimum latency in milliseconds
|
|
LatencyMinMS float64 `json:"latency_minimum_ms"`
|
|
|
|
// LatencyAvgMS is the average latency in milliseconds
|
|
LatencyAvgMS float64 `json:"latency_average_ms"`
|
|
|
|
// LatencyMaxMS is the maximum latency in milliseconds
|
|
LatencyMaxMS float64 `json:"latency_maximum_ms"`
|
|
|
|
// LatencyPercentile is the latency of the 95th percentile
|
|
LatencyPercentile float64 `json:"latency_percentile"`
|
|
|
|
// LatencySumMS is the latency sum in milliseconds
|
|
LatencySumMS float64 `json:"latency_sum_ms"`
|
|
}
|
|
|
|
// Results is a slice of Result
|
|
type Results []*Result
|
|
|
|
// Stamp timestamps the result using the provided stamp function
|
|
func (r *Result) Stamp(stampFunc func() string) {
|
|
if r.CreatedAt != "" || stampFunc == nil {
|
|
return
|
|
}
|
|
r.CreatedAt = stampFunc()
|
|
}
|
|
|
|
// FromConfigsNewResult returns a new result with some fields set based on the provided configs
|
|
func FromConfigsNewResult(config *Config, serverConfig *ServerConfig, t *Test, suiteId string, idFunc func() string) (*Result, error) {
|
|
serverParams := serverConfig.GetServerArgs()
|
|
|
|
var getId func() string
|
|
if idFunc == nil {
|
|
getId = func() string {
|
|
return uuid.New().String()
|
|
}
|
|
} else {
|
|
getId = idFunc
|
|
}
|
|
|
|
var name string
|
|
if t.FromScript {
|
|
base := filepath.Base(t.Name)
|
|
ext := filepath.Ext(base)
|
|
name = strings.TrimSuffix(base, ext)
|
|
} else {
|
|
name = t.Name
|
|
}
|
|
|
|
return &Result{
|
|
Id: getId(),
|
|
SuiteId: suiteId,
|
|
TestId: t.id,
|
|
RuntimeOS: config.RuntimeOS,
|
|
RuntimeGoArch: config.RuntimeGoArch,
|
|
ServerName: string(serverConfig.Server),
|
|
ServerVersion: serverConfig.Version,
|
|
ServerParams: strings.Join(serverParams, " "),
|
|
TestName: name,
|
|
TestParams: strings.Join(t.Params, " "),
|
|
}, nil
|
|
}
|
|
|
|
// FromOutputResult accepts raw sysbench run output and returns the Result
|
|
func FromOutputResult(output []byte, config *Config, serverConfig *ServerConfig, test *Test, suiteId string, idFunc func() string) (*Result, error) {
|
|
result, err := FromConfigsNewResult(config, serverConfig, test, suiteId, idFunc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lines := strings.Split(string(output), "\n")
|
|
var process bool
|
|
for _, l := range lines {
|
|
trimmed := strings.TrimSpace(l)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(trimmed, SqlStatsPrefix) {
|
|
process = true
|
|
continue
|
|
}
|
|
if process {
|
|
err := UpdateResult(result, trimmed)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// UpdateResult extracts the key and value from the given line and updates the given Result
|
|
func UpdateResult(result *Result, line string) error {
|
|
lineParts := strings.Split(line, ":")
|
|
key := strings.TrimSpace(lineParts[0])
|
|
if len(lineParts) > 1 {
|
|
rawVal := strings.TrimSpace(lineParts[1])
|
|
err := updateResult(result, key, rawVal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// updateResult updates a field in the given Result with the given value
|
|
func updateResult(result *Result, key, val string) error {
|
|
var k string
|
|
if strings.Contains(key, percentile) {
|
|
k = percentile
|
|
} else {
|
|
k = key
|
|
}
|
|
switch k {
|
|
case read:
|
|
i, err := fromStringInt64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.SqlReadQueries = i
|
|
case write:
|
|
i, err := fromStringInt64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.SqlWriteQueries = i
|
|
case other:
|
|
i, err := fromStringInt64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.SqlOtherQueries = i
|
|
case totalQueries:
|
|
i, err := fromStringInt64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.SqlTotalQueries = i
|
|
case transactions:
|
|
total, perSecond, err := FromValWithParens(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t, err := fromStringInt64(total)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p, err := fromStringFloat64(fromPerSecondVal(perSecond))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.TransactionsTotal = t
|
|
result.TransactionsPerSecond = p
|
|
case queriesPerSec:
|
|
_, perSecond, err := FromValWithParens(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p, err := fromStringFloat64(fromPerSecondVal(perSecond))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.SqlTotalQueriesPerSecond = p
|
|
case ignoredErrors:
|
|
total, perSecond, err := FromValWithParens(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t, err := fromStringInt64(total)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p, err := fromStringFloat64(fromPerSecondVal(perSecond))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.IgnoredErrorsTotal = t
|
|
result.IgnoredErrorsPerSecond = p
|
|
case reconnects:
|
|
total, perSecond, err := FromValWithParens(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t, err := fromStringInt64(total)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p, err := fromStringFloat64(fromPerSecondVal(perSecond))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.ReconnectsTotal = t
|
|
result.ReconnectsPerSecond = p
|
|
case totalTimeSecs:
|
|
t, err := fromStringFloat64(fromSecondsVal(val))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.TotalTimeSeconds = t
|
|
case totalEvents:
|
|
i, err := fromStringInt64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.TotalNumberOfEvents = i
|
|
case min:
|
|
f, err := fromStringFloat64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.LatencyMinMS = f
|
|
case avg:
|
|
f, err := fromStringFloat64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.LatencyAvgMS = f
|
|
case max:
|
|
f, err := fromStringFloat64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.LatencyMaxMS = f
|
|
case percentile:
|
|
f, err := fromStringFloat64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.LatencyPercentile = f
|
|
case sum:
|
|
f, err := fromStringFloat64(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.LatencySumMS = f
|
|
default:
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FromValWithParens takes a string containing parens and
|
|
// returns the value outside the parens first, and the value
|
|
// inside the parens second
|
|
func FromValWithParens(val string) (string, string, error) {
|
|
if val == "" {
|
|
return "", "", nil
|
|
}
|
|
parts := strings.Split(val, "(")
|
|
if len(parts) <= 1 {
|
|
return strings.TrimSpace(parts[0]), "", nil
|
|
}
|
|
if len(parts) > 2 {
|
|
return "", "", ErrUnableToParseOutput
|
|
}
|
|
return strings.TrimSpace(parts[0]), strings.Trim(parts[1], ")"), nil
|
|
}
|
|
|
|
func fromPerSecondVal(val string) string {
|
|
return strings.TrimSuffix(val, " per sec.")
|
|
}
|
|
|
|
func fromSecondsVal(val string) string {
|
|
return strings.TrimRight(val, "s")
|
|
}
|
|
|
|
func fromStringInt64(val string) (int64, error) {
|
|
return strconv.ParseInt(val, 10, 64)
|
|
}
|
|
|
|
func fromStringFloat64(val string) (float64, error) {
|
|
return strconv.ParseFloat(val, 64)
|
|
}
|