diff --git a/README.md b/README.md index bb920328..8d442bd6 100644 --- a/README.md +++ b/README.md @@ -728,6 +728,88 @@ The main reason for running the benchmark on several clients would be to help el It is important to note that only data that strictly overlaps in absolute time will be considered for analysis. + +## InfluxDB Output + +Warp allows realtime statistics to be pushed to InfluxDB v2 or later. + +This can be combined with the `--stress` parameter, which will allow to have long-running tests without consuming memory and still get access to performance numbers. + +Warp does not provide any analysis on the data sent to InfluxDB. + +### Configuring + +InfluxDB is enabled via a the `--influxdb` parameter. Alternatively the parameter can be set in the `WARP_INFLUXDB_CONNECT` environment variable. + +The value must be formatted like a URL: `://@://?` + +| Part | | +|---------------|-------------------------------------------------------------------------------| +| `` | Connection type. Replace with `http` or `https` | +| `` | Replace with the token needed to access the server | +| `` | Replace with the host name or IP address of your server | +| `` | Replace with the port of your server | +| `` | Replace with the bucket in which to place the data | +| `` | Replace with the organization to which the data should be associated (if any) | +| `` | One or more tags to add to each data point | + +Each parameter can be URL encoded. + +Example: + +`--influxdb "http://shmRUvVjk0Ig2J9qU0_g349PF6l-GB1dmwXUXDh5qd19n1Nda_K7yvSIi9tGpax9jyOsmP2dUd-md8yPOoDNHg==@127.0.0.1:8086/mybucket/myorg?mytag=myvalue"` + +This will connect to port 8086 on 127.0.0.1 using the provided token `shmRU...`. + +Data will be placed in `mybucket` and associated with `myorg`. An additional tag `mytag` will be set to `myvalue` on all data points. + +For distributed benchmarking all clients will be sending data, so hosts like localhost and 127.0.0.1 should not be used. + +### Data + +All in-run measurements are of type `warp`. + +| Tag | Value | +|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `warp_id` | Contains a random string value, unique per client.
This can be used to identify individual runs or single warp clients when using distributed benchmarks. | +| `op` | Contains the operation type, for example GET, PUT, DELETE, etc. | +| `endpoint` | Endpoint is the endpoint to which the operation was sent.
Measurements without this value is total for the warp client. | + + +Fields are sent as accumulated totals per run per operation type. + +New metrics are sent as each operation (request) completes. There is no inter-operation progress logged. +This means that bigger objects (meaning less requests) will create bigger fluctuations. That is important to note when analyzing. + +| Field | Value | +|---------------------------|--------------------------------------------------------------------------------| +| `requests` | Total number of requests performed | +| `objects` | Total number of objects affected | +| `bytes_total` | Total number of bytes affected | +| `errors` | Total errors encountered | +| `request_total_secs` | Total request time in seconds | +| `request_ttfb_total_secs` | Total time to first byte in seconds for relevant operations | + +The statistics provided means that to get "rates over time" the numbers must be calculated as differences (increase/positive derivatives). + +### Summary + +When a run has finished a summary will be sent. This will be a `warp_run_summary` measurement type. +In addition to the fields above it will contain: + +| Field | Value | +|-------------------------|-----------------------------------| +| `request_avg_secs` | Average Request Time | +| `request_max_secs` | Longest Request Time | +| `request_min_secs` | Shortest Request Time | +| `request_ttfb_avg_secs` | Average Time To First Byte (TTFB) | +| `request_ttfb_max_secs` | Longest TTFB | +| `request_ttfb_min_secs` | Shortest TTFB | + +All times are in float point seconds. + +The summary will be sent for each host and operation type. + # Server Profiling When running against a MinIO server it is possible to enable profiling while the benchmark is running. @@ -735,13 +817,13 @@ When running against a MinIO server it is possible to enable profiling while the This is done by adding `--serverprof=type` parameter with the type of profile you would like. This requires that the credentials allows admin access for the first host. -| Type | Description | -|-------|--------------------------------------------------------------------------------------------------------------------------------------------| -| cpu | CPU profile determines where a program spends its time while actively consuming CPU cycles (as opposed while sleeping or waiting for I/O). | -| mem | Heap profile reports the currently live allocations; used to monitor current memory usage or check for memory leaks. | -| block | Block profile show where goroutines block waiting on synchronization primitives (including timer channels). | -| mutex | Mutex profile reports the lock contentions. When you think your CPU is not fully utilized due to a mutex contention, use this profile. | -| trace | A detailed trace of execution of the current program. This will include information about goroutine scheduling and garbage collection. | +| Type | Description | +|---------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `cpu` | CPU profile determines where a program spends its time while actively consuming CPU cycles (as opposed while sleeping or waiting for I/O). | +| `mem` | Heap profile reports the currently live allocations; used to monitor current memory usage or check for memory leaks. | +| `block` | Block profile show where goroutines block waiting on synchronization primitives (including timer channels). | +| `mutex` | Mutex profile reports the lock contentions. When you think your CPU is not fully utilized due to a mutex contention, use this profile. | +| `trace` | A detailed trace of execution of the current program. This will include information about goroutine scheduling and garbage collection. | Profiles for all cluster members will be downloaded as a zip file. diff --git a/cli/analyze.go b/cli/analyze.go index 984c3234..e438465d 100644 --- a/cli/analyze.go +++ b/cli/analyze.go @@ -463,7 +463,6 @@ func printRequestAnalysis(_ *cli.Context, ops aggregate.Operation, details bool) console.SetColor("Print", color.New(color.FgWhite)) if reqs.Skipped { - fmt.Println(reqs) console.Println("Not enough requests") return } diff --git a/cli/benchmark.go b/cli/benchmark.go index 7701b2ad..9b9375a5 100644 --- a/cli/benchmark.go +++ b/cli/benchmark.go @@ -93,6 +93,7 @@ var benchFlags = []cli.Flag{ // runBench will run the supplied benchmark and save/print the analysis. func runBench(ctx *cli.Context, b bench.Benchmark) error { + defer globalWG.Wait() activeBenchmarkMu.Lock() ab := activeBenchmark activeBenchmarkMu.Unlock() @@ -246,21 +247,23 @@ func runBench(ctx *cli.Context, b bench.Benchmark) error { ops.SetClientID(cID) prof.stop(ctx2, ctx, fileName+".profiles.zip") - f, err := os.Create(fileName + ".csv.zst") - if err != nil { - monitor.Errorln("Unable to write benchmark data:", err) - } else { - func() { - defer f.Close() - enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) - fatalIf(probe.NewError(err), "Unable to compress benchmark output") + if len(ops) > 0 { + f, err := os.Create(fileName + ".csv.zst") + if err != nil { + monitor.Errorln("Unable to write benchmark data:", err) + } else { + func() { + defer f.Close() + enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) + fatalIf(probe.NewError(err), "Unable to compress benchmark output") - defer enc.Close() - err = ops.CSV(enc, commandLine(ctx)) - fatalIf(probe.NewError(err), "Unable to write benchmark output") + defer enc.Close() + err = ops.CSV(enc, commandLine(ctx)) + fatalIf(probe.NewError(err), "Unable to write benchmark output") - monitor.InfoLn(fmt.Sprintf("Benchmark data written to %q\n", fileName+".csv.zst")) - }() + monitor.InfoLn(fmt.Sprintf("Benchmark data written to %q\n", fileName+".csv.zst")) + }() + } } monitor.OperationsReady(ops, fileName, commandLine(ctx)) printAnalysis(ctx, ops) @@ -425,21 +428,23 @@ func runClientBenchmark(ctx *cli.Context, b bench.Benchmark, cb *clientBenchmark ops.SetClientID(cID) ops.SortByStartTime() - f, err := os.Create(fileName + ".csv.zst") - if err != nil { - console.Error("Unable to write benchmark data:", err) - } else { - func() { - defer f.Close() - enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) - fatalIf(probe.NewError(err), "Unable to compress benchmark output") + if len(ops) > 0 { + f, err := os.Create(fileName + ".csv.zst") + if err != nil { + console.Error("Unable to write benchmark data:", err) + } else { + func() { + defer f.Close() + enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) + fatalIf(probe.NewError(err), "Unable to compress benchmark output") - defer enc.Close() - err = ops.CSV(enc, commandLine(ctx)) - fatalIf(probe.NewError(err), "Unable to write benchmark output") + defer enc.Close() + err = ops.CSV(enc, commandLine(ctx)) + fatalIf(probe.NewError(err), "Unable to write benchmark output") - console.Infof("Benchmark data written to %q\n", fileName+".csv.zst") - }() + console.Infof("Benchmark data written to %q\n", fileName+".csv.zst") + }() + } } err = cb.waitForStage(stageCleanup) @@ -512,6 +517,9 @@ func checkBenchmark(ctx *cli.Context) { madmin.ProfilerTrace, } + _, err := parseInfluxURL(ctx) + fatalIf(probe.NewError(err), "invalid influx config") + profs := strings.Split(ctx.String("serverprof"), ",") for _, profilerType := range profs { if len(profilerType) == 0 { diff --git a/cli/benchserver.go b/cli/benchserver.go index 86936e4c..066fe67c 100644 --- a/cli/benchserver.go +++ b/cli/benchserver.go @@ -201,22 +201,24 @@ func runServerBenchmark(ctx *cli.Context, b bench.Benchmark) (bool, error) { } } - allOps.SortByStartTime() - f, err := os.Create(fileName + ".csv.zst") - if err != nil { - errorLn("Unable to write benchmark data:", err) - } else { - func() { - defer f.Close() - enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) - fatalIf(probe.NewError(err), "Unable to compress benchmark output") - - defer enc.Close() - err = allOps.CSV(enc, commandLine(ctx)) - fatalIf(probe.NewError(err), "Unable to write benchmark output") - - infoLn(fmt.Sprintf("Benchmark data written to %q\n", fileName+".csv.zst")) - }() + if len(allOps) > 0 { + allOps.SortByStartTime() + f, err := os.Create(fileName + ".csv.zst") + if err != nil { + errorLn("Unable to write benchmark data:", err) + } else { + func() { + defer f.Close() + enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) + fatalIf(probe.NewError(err), "Unable to compress benchmark output") + + defer enc.Close() + err = allOps.CSV(enc, commandLine(ctx)) + fatalIf(probe.NewError(err), "Unable to write benchmark output") + + infoLn(fmt.Sprintf("Benchmark data written to %q\n", fileName+".csv.zst")) + }() + } } monitor.OperationsReady(allOps, fileName, commandLine(ctx)) printAnalysis(ctx, allOps) diff --git a/cli/flags.go b/cli/flags.go index 790d43a8..be4b695a 100644 --- a/cli/flags.go +++ b/cli/flags.go @@ -20,8 +20,10 @@ package cli import ( "fmt" "os" + "sync" "github.com/minio/cli" + "github.com/minio/mc/pkg/probe" "github.com/minio/pkg/console" "github.com/minio/warp/pkg/bench" "github.com/minio/warp/pkg/generator" @@ -97,6 +99,8 @@ var profileFlags = []cli.Flag{ }, } +var globalWG sync.WaitGroup + // Set global states. NOTE: It is deliberately kept monolithic to ensure we dont miss out any flags. func setGlobalsFromContext(ctx *cli.Context) error { quiet := ctx.IsSet("quiet") @@ -130,7 +134,7 @@ func commandLine(ctx *cli.Context) string { } name := flag.GetName() switch name { - case "access-key", "secret-key": + case "access-key", "secret-key", "influxdb": val = "*REDACTED*" } s += " --" + flag.GetName() + "=" + val @@ -233,9 +237,24 @@ var ioFlags = []cli.Flag{ Name: "stress", Usage: "stress test only and discard output", }, + cli.StringFlag{ + Name: "influxdb", + EnvVar: appNameUC + "_INFLUXDB_CONNECT", + Usage: "Send operations to InfluxDB. Specify as 'http://@://'", + }, } func getCommon(ctx *cli.Context, src func() generator.Source) bench.Common { + var extra []chan<- bench.Operation + u, err := parseInfluxURL(ctx) + if err != nil { + fatalIf(probe.NewError(err), "invalid influx config") + } + if u != nil { + if in := newInfluxDB(ctx, &globalWG); in != nil { + extra = append(extra, in) + } + } return bench.Common{ Client: newClient(ctx), Concurrency: ctx.Int("concurrent"), @@ -244,5 +263,6 @@ func getCommon(ctx *cli.Context, src func() generator.Source) bench.Common { Location: ctx.String("region"), PutOpts: putOpts(ctx), DiscardOutput: ctx.Bool("stress"), + ExtraOut: extra, } } diff --git a/cli/influx.go b/cli/influx.go new file mode 100644 index 00000000..59eaa4b0 --- /dev/null +++ b/cli/influx.go @@ -0,0 +1,259 @@ +/* + * Warp (C) 2019-2023 MinIO, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package cli + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + "sync" + "time" + + influxdb2 "github.com/influxdata/influxdb-client-go/v2" + "github.com/influxdata/influxdb-client-go/v2/api/http" + "github.com/influxdata/influxdb-client-go/v2/api/write" + "github.com/minio/cli" + "github.com/minio/mc/pkg/probe" + "github.com/minio/warp/pkg/bench" +) + +func newInfluxDB(ctx *cli.Context, wg *sync.WaitGroup) chan<- bench.Operation { + u, err := parseInfluxURL(ctx) + if err != nil { + fatalIf(probe.NewError(err), "unable to parse influxdb parameter") + } + token := "" + if u.User != nil { + token = u.User.Username() + } + var tagValues url.Values + if len(u.RawQuery) > 0 { + tagValues, err = url.ParseQuery(u.RawQuery) + errorIf(probe.NewError(err), "unable to parse tags") + } + tags := make(map[string]string, len(tagValues)+1) + for key, tag := range tagValues { + if len(tag) > 0 && len(key) > 0 { + tags[key] = tag[0] + } + } + tags["warp_id"] = pRandASCII(8) + + // Create a new client using an InfluxDB server base URL and an authentication token + serverURL := u.Scheme + "://" + u.Host + client := influxdb2.NewClientWithOptions(serverURL, token, influxdb2.DefaultOptions().SetMaxRetryTime(1000).SetMaxRetries(2)) + { + to, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + ok, err := client.Ping(to) + if !ok { + errorIf(probe.NewError(err), "unable to reach influxdb") + } + } + // Use blocking write client for writes to desired bucket + path := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + writeAPI := client.WriteAPI(path[1], path[0]) + writeAPI.SetWriteFailedCallback(func(batch string, error http.Error, retryAttempts uint) bool { + errorIf(probe.NewError(err), "unable to write to influxdb") + return false + }) + ch := make(chan bench.Operation, 10000) + wg.Add(1) + go func() { + defer func() { + writeAPI.Flush() + wg.Done() + }() + hosts := make(map[string]map[string]aggregatedStats, 100) + totalOp := make(map[string]aggregatedStats, 5) + for op := range ch { + host := hosts[op.Endpoint] + var hostStats aggregatedStats + if host == nil { + host = make(map[string]aggregatedStats, 5) + hosts[op.Endpoint] = host + } else { + hostStats = host[op.OpType] + } + total := totalOp[op.OpType] + hostStats.add(op) + total.add(op) + + // Store + totalOp[op.OpType] = total + host[op.OpType] = hostStats + + // Send + pTot := total.point(op) + pHost := hostStats.point(op) + + for key, tag := range tags { + pTot.AddTag(key, tag) + pHost.AddTag(key, tag) + } + pHost.AddTag("endpoint", op.Endpoint) + writeAPI.WritePoint(pHost) + writeAPI.WritePoint(pTot) + } + // Send summaries + for host, ops := range hosts { + for op, stats := range ops { + p := stats.summary(op) + + for key, tag := range tags { + p.AddTag(key, tag) + } + p.AddTag("endpoint", host) + writeAPI.WritePoint(p) + } + } + for op, stats := range totalOp { + p := stats.summary(op) + + for key, tag := range tags { + p.AddTag(key, tag) + } + p.AddTag("endpoint", "") + writeAPI.WritePoint(p) + } + }() + return ch +} + +func parseInfluxURL(ctx *cli.Context) (*url.URL, error) { + s := ctx.String("influxdb") + if s == "" { + return nil, nil + } + u, err := url.Parse(s) + if err != nil { + return nil, err + } + switch u.Scheme { + case "": + return nil, errors.New("influxdb: no scheme specified (http/https)") + case "http", "https": + default: + return nil, fmt.Errorf("influxdb: unknown scheme %s - must be http/https", u.Scheme) + } + path := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + if len(path) != 2 { + return nil, fmt.Errorf("influxdb: unexpected path. Want 'bucket/org', got '%s'", strings.TrimPrefix(u.Path, "/")) + } + if len(path[0]) == 0 { + return nil, errors.New("influxdb: empty bucket specified") + } + if len(u.RawQuery) > 0 { + _, err = url.ParseQuery(u.RawQuery) + if err != nil { + return nil, err + } + } + + // org can be empty + // token can be empty + return u, nil +} + +type aggregatedStats struct { + bytes int64 + objects int + ops int + errors int + + // requests + reqDur time.Duration + reqMin time.Duration + reqMax time.Duration + + // time to first byte + ttfb time.Duration + ttfbMin time.Duration + ttfbMax time.Duration +} + +func (a *aggregatedStats) add(o bench.Operation) { + a.ops++ + if o.Err != "" { + // Do not add more + a.errors++ + return + } + + a.bytes += o.Size + a.objects += o.ObjPerOp + + dur := o.End.Sub(o.Start) + a.reqDur += dur + if dur > a.reqMax { + a.reqMax = dur + } + if a.reqMin == 0 || dur < a.reqMin { + a.reqMin = dur + } + if o.FirstByte != nil { + ttfb := o.FirstByte.Sub(o.Start) + a.ttfb += ttfb + if ttfb > a.ttfbMax { + a.ttfbMax = ttfb + } + if a.ttfbMin == 0 || dur < a.ttfbMin { + a.ttfbMin = dur + } + } +} + +func (a aggregatedStats) point(op bench.Operation) *write.Point { + p := influxdb2.NewPointWithMeasurement("warp") + p.AddTag("op", op.OpType) + p.AddField("requests", a.ops) + p.AddField("objects", a.objects) + p.AddField("bytes_total", a.bytes) + p.AddField("errors", a.errors) + p.AddField("request_total_secs", float64(a.reqDur)/float64(time.Second)) + if a.ttfb > 0 { + p.AddField("request_ttfb_total_secs", float64(a.ttfb)/float64(time.Second)) + } + return p +} + +func (a aggregatedStats) summary(opType string) *write.Point { + p := influxdb2.NewPointWithMeasurement("warp_run_summary") + p.AddTag("op", opType) + p.AddField("requests", a.ops) + p.AddField("objects", a.objects) + p.AddField("bytes_total", a.bytes) + p.AddField("errors", a.errors) + p.AddField("request_total_secs", float64(a.reqDur)/float64(time.Second)) + if a.ops-a.errors > 0 { + p.AddField("request_avg_secs", float64(a.reqDur)/float64(time.Second)/float64(a.ops-a.errors)) + } + p.AddField("request_min_secs", float64(a.reqMin)/float64(time.Second)) + p.AddField("request_max_secs", float64(a.reqMax)/float64(time.Second)) + if a.ttfb > 0 { + p.AddField("request_ttfb_total_secs", float64(a.ttfb)/float64(time.Second)) + if a.ops-a.errors > 0 { + p.AddField("request_ttfb_avg_secs", float64(a.ttfb)/float64(time.Second)/float64(a.ops-a.errors)) + } + p.AddField("request_ttfb_min_secs", float64(a.ttfbMin)/float64(time.Second)) + p.AddField("request_ttfb_max_secs", float64(a.ttfbMax)/float64(time.Second)) + } + return p +} diff --git a/cli/merge.go b/cli/merge.go index f6130080..658f3165 100644 --- a/cli/merge.go +++ b/cli/merge.go @@ -90,22 +90,24 @@ func mainMerge(ctx *cli.Context) error { if fileName == "" { fileName = fmt.Sprintf("%s-%s-%s", appName, ctx.Command.Name, time.Now().Format("2006-01-02[150405]")) } - allOps.SortByStartTime() - f, err := os.Create(fileName + ".csv.zst") - if err != nil { - console.Error("Unable to write benchmark data:", err) - } else { - func() { - defer f.Close() - enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) - fatalIf(probe.NewError(err), "Unable to compress benchmark output") + if len(allOps) > 0 { + allOps.SortByStartTime() + f, err := os.Create(fileName + ".csv.zst") + if err != nil { + console.Error("Unable to write benchmark data:", err) + } else { + func() { + defer f.Close() + enc, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBetterCompression)) + fatalIf(probe.NewError(err), "Unable to compress benchmark output") - defer enc.Close() - err = allOps.CSV(enc, commandLine(ctx)) - fatalIf(probe.NewError(err), "Unable to write benchmark output") + defer enc.Close() + err = allOps.CSV(enc, commandLine(ctx)) + fatalIf(probe.NewError(err), "Unable to write benchmark output") - console.Infof("Benchmark data written to %q\n", fileName+".csv.zst") - }() + console.Infof("Benchmark data written to %q\n", fileName+".csv.zst") + }() + } } for typ, ops := range allOps.ByOp() { start, end := ops.ActiveTimeRange(true) diff --git a/go.mod b/go.mod index d3c597a7..f772f655 100644 --- a/go.mod +++ b/go.mod @@ -22,11 +22,14 @@ require ( require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/influxdata/influxdb-client-go/v2 v2.12.3 // indirect + github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect diff --git a/go.sum b/go.sum index aa242ef9..21a05bbe 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ github.com/bygui86/multi-profile/v2 v2.1.0 h1:x/jPqeL/6hJqLXoDI/H5zLPsSFbDR6IEbr github.com/bygui86/multi-profile/v2 v2.1.0/go.mod h1:f4qCZiQo1nnJdwbPoADUtdDXg3hhnpfgZ9iq3/kW4BA= github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,28 +11,42 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0IpXeMSkY/uJa/O/vC4= +github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= @@ -40,8 +55,13 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= @@ -60,11 +80,19 @@ github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE= github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -95,6 +123,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -137,11 +166,17 @@ github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+Kd github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= @@ -150,6 +185,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -164,11 +201,17 @@ golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -181,28 +224,35 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/bench/benchmark.go b/pkg/bench/benchmark.go index c7e79841..a53eb79f 100644 --- a/pkg/bench/benchmark.go +++ b/pkg/bench/benchmark.go @@ -58,6 +58,7 @@ type Common struct { // ExtraFlags contains extra flags to add to remote clients. ExtraFlags map[string]string Source func() generator.Source + ExtraOut []chan<- Operation // Error should log an error similar to fmt.Print(data...) Error func(data ...interface{}) @@ -241,4 +242,5 @@ func (c *Common) addCollector() { } else { c.Collector = NewCollector() } + c.Collector.extra = c.ExtraOut } diff --git a/pkg/bench/collector.go b/pkg/bench/collector.go index e0dcc09a..f4a16039 100644 --- a/pkg/bench/collector.go +++ b/pkg/bench/collector.go @@ -30,6 +30,7 @@ type Collector struct { rcv chan Operation ops Operations rcvWg sync.WaitGroup + extra []chan<- Operation // The mutex protects the ops above. // Once ops have been added, they should no longer be modified. opsMu sync.Mutex @@ -44,6 +45,9 @@ func NewCollector() *Collector { go func() { defer r.rcvWg.Done() for op := range r.rcv { + for _, ch := range r.extra { + ch <- op + } r.opsMu.Lock() r.ops = append(r.ops, op) r.opsMu.Unlock() @@ -61,10 +65,10 @@ func NewNullCollector() *Collector { r.rcvWg.Add(1) go func() { defer r.rcvWg.Done() - // https://github.com/mgechev/revive/issues/386 - //nolint:revive - for range r.rcv { - // Just dump them + for op := range r.rcv { + for _, ch := range r.extra { + ch <- op + } } }() return r @@ -153,5 +157,8 @@ func (c *Collector) Receiver() chan<- Operation { func (c *Collector) Close() Operations { close(c.rcv) c.rcvWg.Wait() + for _, ch := range c.extra { + close(ch) + } return c.ops }