Skip to content

Commit

Permalink
Questdb benchmark support (#157)
Browse files Browse the repository at this point in the history
Co-authored-by: bsmth <[email protected]>
Co-authored-by: Vlad Ilyushchenko <[email protected]>
  • Loading branch information
3 people authored Aug 24, 2021
1 parent 161c39d commit 1eb7705
Show file tree
Hide file tree
Showing 25 changed files with 1,848 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.DS_Store
.idea
.vscode
*~

# High Dynamic Range (HDR) Histogram files
*.hdr
*.hdr
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Current databases supported:
+ CrateDB [(supplemental docs)](docs/cratedb.md)
+ InfluxDB [(supplemental docs)](docs/influx.md)
+ MongoDB [(supplemental docs)](docs/mongo.md)
+ QuestDB [(supplemental docs)](docs/questdb.md)
+ SiriDB [(supplemental docs)](docs/siridb.md)
+ TimescaleDB [(supplemental docs)](docs/timescaledb.md)
+ Timestream [(supplemental docs)](docs/timestream.md)
Expand Down Expand Up @@ -75,6 +76,7 @@ cases are implemented for each database:
|CrateDB|X||
|InfluxDB|X|X|
|MongoDB|X|
|QuestDB|X|X
|SiriDB|X|
|TimescaleDB|X|X|
|Timestream|X||
Expand Down Expand Up @@ -132,7 +134,7 @@ Variables needed:
1. an end time. E.g., `2016-01-04T00:00:00Z`
1. how much time should be between each reading per device, in seconds. E.g., `10s`
1. and which database(s) you want to generate for. E.g., `timescaledb`
(choose from `cassandra`, `clickhouse`, `cratedb`, `influx`, `mongo`, `siridb`,
(choose from `cassandra`, `clickhouse`, `cratedb`, `influx`, `mongo`, `questdb`, `siridb`,
`timescaledb` or `victoriametrics`)

Given the above steps you can now generate a dataset (or multiple
Expand Down
50 changes: 50 additions & 0 deletions cmd/tsbs_generate_queries/databases/questdb/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package questdb

import (
"fmt"
"net/url"
"time"

"github.com/timescale/tsbs/cmd/tsbs_generate_queries/uses/devops"
"github.com/timescale/tsbs/cmd/tsbs_generate_queries/utils"
"github.com/timescale/tsbs/pkg/query"
)

// BaseGenerator contains settings specific for QuestDB
type BaseGenerator struct {
}

// GenerateEmptyQuery returns an empty query.QuestDB.
func (g *BaseGenerator) GenerateEmptyQuery() query.Query {
return query.NewHTTP()
}

// fillInQuery fills the query struct with data.
func (g *BaseGenerator) fillInQuery(qi query.Query, humanLabel, humanDesc, sql string) {
v := url.Values{}
v.Set("count", "false")
v.Set("query", sql)
q := qi.(*query.HTTP)
q.HumanLabel = []byte(humanLabel)
q.RawQuery = []byte(sql)
q.HumanDescription = []byte(humanDesc)
q.Method = []byte("GET")
q.Path = []byte(fmt.Sprintf("/exec?%s", v.Encode()))
q.Body = nil
}

// NewDevops creates a new devops use case query generator.
func (g *BaseGenerator) NewDevops(start, end time.Time, scale int) (utils.QueryGenerator, error) {
core, err := devops.NewCore(start, end, scale)

if err != nil {
return nil, err
}

devops := &Devops{
BaseGenerator: g,
Core: core,
}

return devops, nil
}
210 changes: 210 additions & 0 deletions cmd/tsbs_generate_queries/databases/questdb/devops.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package questdb

import (
"fmt"
"strings"
"time"

"github.com/timescale/tsbs/cmd/tsbs_generate_queries/uses/devops"
"github.com/timescale/tsbs/pkg/query"
)

// TODO: Remove the need for this by continuing to bubble up errors
func panicIfErr(err error) {
if err != nil {
panic(err.Error())
}
}

// Devops produces QuestDB-specific queries for all the devops query types.
type Devops struct {
*BaseGenerator
*devops.Core
}

// getSelectAggClauses builds specified aggregate function clauses for
// a set of column idents.
//
// For instance:
// max(cpu_time) AS max_cpu_time
func (d *Devops) getSelectAggClauses(aggFunc string, idents []string) []string {
selectAggClauses := make([]string, len(idents))
for i, ident := range idents {
selectAggClauses[i] =
fmt.Sprintf("%[1]s(%[2]s) AS %[1]s_%[2]s", aggFunc, ident)
}
return selectAggClauses
}

// MaxAllCPU selects the MAX of all metrics under 'cpu' per hour for N random
// hosts
//
// Queries:
// cpu-max-all-1
// cpu-max-all-8
func (d *Devops) MaxAllCPU(qi query.Query, nHosts int) {
interval := d.Interval.MustRandWindow(devops.MaxAllDuration)
selectClauses := d.getSelectAggClauses("max", devops.GetAllCPUMetrics())
hosts, err := d.GetRandomHosts(nHosts)
panicIfErr(err)

sql := fmt.Sprintf(`
SELECT
hour(timestamp) AS hour,
%s
FROM cpu
WHERE hostname IN ('%s')
AND timestamp >= '%s'
AND timestamp < '%s'
SAMPLE BY 1h`,
strings.Join(selectClauses, ", "),
strings.Join(hosts, "', '"),
interval.StartString(),
interval.EndString())

humanLabel := devops.GetMaxAllLabel("QuestDB", nHosts)
humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}

// GroupByTimeAndPrimaryTag selects the AVG of metrics in the group `cpu` per device
// per hour for a day
//
// Queries:
// double-groupby-1
// double-groupby-5
// double-groupby-all
func (d *Devops) GroupByTimeAndPrimaryTag(qi query.Query, numMetrics int) {
metrics, err := devops.GetCPUMetricsSlice(numMetrics)
panicIfErr(err)
interval := d.Interval.MustRandWindow(devops.DoubleGroupByDuration)
selectClauses := d.getSelectAggClauses("avg", metrics)

sql := fmt.Sprintf(`
SELECT timestamp, hostname,
%s
FROM cpu
WHERE timestamp >= '%s'
AND timestamp < '%s'
SAMPLE BY 1h
GROUP BY timestamp, hostname`,
strings.Join(selectClauses, ", "),
interval.StartString(),
interval.EndString())

humanLabel := devops.GetDoubleGroupByLabel("QuestDB", numMetrics)
humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}

// GroupByOrderByLimit populates a query.Query that has a time WHERE clause,
// that groups by a truncated date, orders by that date, and takes a limit:
//
// Queries:
// groupby-orderby-limit
func (d *Devops) GroupByOrderByLimit(qi query.Query) {
interval := d.Interval.MustRandWindow(time.Hour)
sql := fmt.Sprintf(`
SELECT timestamp AS minute,
max(usage_user)
FROM cpu
WHERE timestamp < '%s'
SAMPLE BY 1m
LIMIT 5`,
interval.EndString())

humanLabel := "QuestDB max cpu over last 5 min-intervals (random end)"
humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.EndString())
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}

// LastPointPerHost finds the last row for every host in the dataset
//
// Queries:
// lastpoint
func (d *Devops) LastPointPerHost(qi query.Query) {
sql := fmt.Sprintf(`SELECT * FROM cpu latest by hostname`)

humanLabel := "QuestDB last row per host"
humanDesc := humanLabel
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}

// HighCPUForHosts populates a query that gets CPU metrics when the CPU has
// high usage between a time period for a number of hosts (if 0, it will
// search all hosts)
//
// Queries:
// high-cpu-1
// high-cpu-all
func (d *Devops) HighCPUForHosts(qi query.Query, nHosts int) {
interval := d.Interval.MustRandWindow(devops.HighCPUDuration)
sql := ""
if nHosts > 0 {
hosts, err := d.GetRandomHosts(nHosts)
panicIfErr(err)

sql = fmt.Sprintf(`
SELECT *
FROM cpu
WHERE usage_user > 90.0
AND hostname IN ('%s')
AND timestamp >= '%s'
AND timestamp < '%s'`,
strings.Join(hosts, "', '"),
interval.StartString(),
interval.EndString())
} else {
sql = fmt.Sprintf(`
SELECT *
FROM cpu
WHERE usage_user > 90.0
AND timestamp >= '%s'
AND timestamp < '%s'`,
interval.StartString(),
interval.EndString())
}

humanLabel, err := devops.GetHighCPULabel("QuestDB", nHosts)
panicIfErr(err)
humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}

// GroupByTime selects the MAX for metrics under 'cpu', per minute for N random
// hosts
//
// Resultsets:
// single-groupby-1-1-12
// single-groupby-1-1-1
// single-groupby-1-8-1
// single-groupby-5-1-12
// single-groupby-5-1-1
// single-groupby-5-8-1
func (d *Devops) GroupByTime(qi query.Query, nHosts, numMetrics int, timeRange time.Duration) {
interval := d.Interval.MustRandWindow(timeRange)
metrics, err := devops.GetCPUMetricsSlice(numMetrics)
panicIfErr(err)
selectClauses := d.getSelectAggClauses("max", metrics)
hosts, err := d.GetRandomHosts(nHosts)
panicIfErr(err)

sql := fmt.Sprintf(`
SELECT timestamp,
%s
FROM cpu
WHERE hostname IN ('%s')
AND timestamp >= '%s'
AND timestamp < '%s'
SAMPLE BY 1m`,
strings.Join(selectClauses, ", "),
strings.Join(hosts, "', '"),
interval.StartString(),
interval.EndString())

humanLabel := fmt.Sprintf(
"QuestDB %d cpu metric(s), random %4d hosts, random %s by 1m",
numMetrics, nHosts, timeRange)
humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString())
d.fillInQuery(qi, humanLabel, humanDesc, sql)
}
Loading

0 comments on commit 1eb7705

Please sign in to comment.