Skip to content

Commit 74fa703

Browse files
committed
perf(otelbench): add promql query sender
Ref: #283
1 parent 2e062a6 commit 74fa703

File tree

5 files changed

+220
-2
lines changed

5 files changed

+220
-2
lines changed

cmd/otelbench/main.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Binary otelbench implements benchmarking suite for oteldb.
2+
package main
3+
4+
import (
5+
"context"
6+
"os"
7+
"os/signal"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func main() {
13+
rootCmd := &cobra.Command{
14+
Use: "otelbench",
15+
Short: "otelbench is a benchmarking suite for querying oteldb",
16+
17+
SilenceUsage: true,
18+
}
19+
rootCmd.AddCommand(
20+
newPromQLCommand(),
21+
)
22+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
23+
defer stop()
24+
if err := rootCmd.ExecuteContext(ctx); err != nil {
25+
_, _ = os.Stderr.WriteString(err.Error() + "\n")
26+
os.Exit(1)
27+
}
28+
}

cmd/otelbench/promql.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strconv"
10+
"time"
11+
12+
"github.com/go-faster/errors"
13+
"github.com/schollz/progressbar/v3"
14+
"github.com/spf13/cobra"
15+
16+
"github.com/go-faster/oteldb/internal/promapi"
17+
"github.com/go-faster/oteldb/internal/promproxy"
18+
)
19+
20+
type PromQL struct {
21+
Addr string
22+
Input string
23+
RequestTimeout time.Duration
24+
25+
client *promapi.Client
26+
}
27+
28+
func (p *PromQL) Setup() error {
29+
var err error
30+
p.client, err = promapi.NewClient(p.Addr)
31+
if err != nil {
32+
return errors.Wrap(err, "create client")
33+
}
34+
return nil
35+
}
36+
37+
func toPrometheusTimestamp(t time.Time) promapi.PrometheusTimestamp {
38+
return promapi.PrometheusTimestamp(strconv.FormatInt(t.Unix(), 10))
39+
}
40+
41+
func (p *PromQL) sendRangeQuery(ctx context.Context, q promproxy.RangeQuery) error {
42+
if _, err := p.client.GetQueryRange(ctx, promapi.GetQueryRangeParams{
43+
Query: q.Query,
44+
Step: strconv.Itoa(q.Step),
45+
Start: toPrometheusTimestamp(q.Start),
46+
End: toPrometheusTimestamp(q.End),
47+
}); err != nil {
48+
return errors.Wrap(err, "get query range")
49+
}
50+
return nil
51+
}
52+
53+
func toOptPrometheusTimestamp(t promproxy.OptDateTime) promapi.OptPrometheusTimestamp {
54+
if !t.IsSet() {
55+
return promapi.OptPrometheusTimestamp{}
56+
}
57+
return promapi.NewOptPrometheusTimestamp(toPrometheusTimestamp(t.Value))
58+
}
59+
60+
func (p *PromQL) sendInstantQuery(ctx context.Context, q promproxy.InstantQuery) error {
61+
if _, err := p.client.GetQuery(ctx, promapi.GetQueryParams{
62+
Query: q.Query,
63+
Time: toOptPrometheusTimestamp(q.Time),
64+
}); err != nil {
65+
return errors.Wrap(err, "get query")
66+
}
67+
return nil
68+
}
69+
70+
func (p *PromQL) sendSeriesQuery(ctx context.Context, query promproxy.SeriesQuery) error {
71+
if _, err := p.client.GetSeries(ctx, promapi.GetSeriesParams{
72+
Start: toOptPrometheusTimestamp(query.Start),
73+
End: toOptPrometheusTimestamp(query.End),
74+
Match: query.Matchers,
75+
}); err != nil {
76+
return errors.Wrap(err, "get series")
77+
}
78+
return nil
79+
}
80+
81+
func (p *PromQL) send(ctx context.Context, q promproxy.Query) error {
82+
ctx, cancel := context.WithTimeout(ctx, p.RequestTimeout)
83+
defer cancel()
84+
switch q.Type {
85+
case promproxy.InstantQueryQuery:
86+
return p.sendInstantQuery(ctx, q.InstantQuery)
87+
case promproxy.RangeQueryQuery:
88+
return p.sendRangeQuery(ctx, q.RangeQuery)
89+
case promproxy.SeriesQueryQuery:
90+
return p.sendSeriesQuery(ctx, q.SeriesQuery)
91+
default:
92+
return errors.Errorf("unknown query type %q", q.Type)
93+
}
94+
}
95+
96+
func (p *PromQL) each(ctx context.Context, fn func(ctx context.Context, q promproxy.Query) error) error {
97+
f, err := os.Open(p.Input)
98+
if err != nil {
99+
return errors.Wrap(err, "read")
100+
}
101+
defer func() {
102+
_ = f.Close()
103+
}()
104+
d := json.NewDecoder(f)
105+
for {
106+
var q promproxy.Query
107+
if err := d.Decode(&q); err != nil {
108+
if errors.Is(err, io.EOF) {
109+
break
110+
}
111+
return errors.Wrap(err, "decode query")
112+
}
113+
if err := fn(ctx, q); err != nil {
114+
return errors.Wrap(err, "callback")
115+
}
116+
}
117+
return nil
118+
}
119+
120+
func (p *PromQL) Run(ctx context.Context) error {
121+
fmt.Println("sending", p.Input, "to", p.Addr)
122+
var total int64
123+
if err := p.each(ctx, func(ctx context.Context, q promproxy.Query) error {
124+
total++
125+
return nil
126+
}); err != nil {
127+
return errors.Wrap(err, "count total")
128+
}
129+
pb := progressbar.Default(total)
130+
start := time.Now()
131+
if err := p.each(ctx, func(ctx context.Context, q promproxy.Query) error {
132+
if err := p.send(ctx, q); err != nil {
133+
return errors.Wrap(err, "send")
134+
}
135+
if err := pb.Add(1); err != nil {
136+
return errors.Wrap(err, "update progress bar")
137+
}
138+
return nil
139+
}); err != nil {
140+
_ = pb.Exit()
141+
return errors.Wrap(err, "send")
142+
}
143+
if err := pb.Finish(); err != nil {
144+
return errors.Wrap(err, "finish progress bar")
145+
}
146+
fmt.Println("done in", time.Since(start).Round(time.Millisecond))
147+
return nil
148+
}
149+
150+
func newPromQLCommand() *cobra.Command {
151+
p := &PromQL{}
152+
cmd := &cobra.Command{
153+
Use: "promql",
154+
Short: "Run promql queries",
155+
RunE: func(cmd *cobra.Command, args []string) error {
156+
if err := p.Setup(); err != nil {
157+
return errors.Wrap(err, "setup")
158+
}
159+
return p.Run(cmd.Context())
160+
},
161+
}
162+
f := cmd.Flags()
163+
f.StringVar(&p.Addr, "addr", "http://localhost:9090", "Prometheus address")
164+
f.StringVarP(&p.Input, "input", "i", "queries.jsonl", "Input file")
165+
f.DurationVar(&p.RequestTimeout, "request-timeout", time.Second*10, "Request timeout")
166+
return cmd
167+
}

cmd/oteldb/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func loadConfig(name string) (cfg Config, _ error) {
3535
return cfg, nil
3636
}
3737

38-
// Config is a oteldb config.
38+
// Config is the oteldb config.
3939
type Config struct {
4040
DSN string `json:"dsn" yaml:"dsn"`
4141
TTL time.Duration `json:"ttl" yaml:"ttl"`

dev/local/ch-bench-read/docker-compose.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ services:
2121
build:
2222
context: ../../../
2323
dockerfile: Dockerfile
24+
command:
25+
- --config=/etc/otel/cfg.yml
26+
volumes:
27+
- ./oteldb.yml:/etc/otel/cfg.yml:ro
2428
environment:
2529
- OTELDB_STORAGE=ch
26-
- CH_DSN=clickhouse://clickhouse:9000
2730
- OTEL_LOG_LEVEL=info
2831
- OTEL_METRICS_EXPORTER=none
2932
- OTEL_LOGS_EXPORTER=none

dev/local/ch-bench-read/oteldb.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
prometheus:
2+
bind: 0.0.0.0:9090
3+
max_samples: 1_000_000
4+
timeout: 1m
5+
enable_negative_offset: true
6+
health_check:
7+
bind: 0.0.0.0:13133
8+
otelcol:
9+
receivers:
10+
prometheusremotewrite:
11+
endpoint: 0.0.0.0:19291
12+
time_threshold: 100_000 # hours
13+
exporters:
14+
oteldbexporter:
15+
dsn: clickhouse://clickhouse:9000
16+
service:
17+
pipelines:
18+
metrics:
19+
receivers: [prometheusremotewrite]
20+
exporters: [oteldbexporter]

0 commit comments

Comments
 (0)