diff --git a/README.md b/README.md index 51e645e..fc9e366 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,18 @@ Ramp up and down are handled only by increasing or decreasing the number of goro To mix different kinds of traces, or send traces to multiple datasets, use multiple loadgen processes. +## Configuration File + +A YAML configuration file can be used by specifying `--config=filename`. +The format of the YAML file reflects the configuration parameters. +See the file [sample_config.yaml](https://github.com/honeycombio/loadgen/blob/main/sample_config.yaml) for an example. + +For an easy way to convert an existing command line to a YAML file, use `--writecfg=outputfile`. +This will write the YAML equivalent of the complete configuration (except for the API key) to the specified location. + +Fields, which are specified on the command line as `key=value` (without any `-` characters) can be specified in YAML +by adding key-value pairs under the `fields` key. + ## Generators After the list of options, loadgen permits a list of fields in the form of name=constant or name=/gen. diff --git a/fielder.go b/fielder.go index 88a7cbe..f745830 100644 --- a/fielder.go +++ b/fielder.go @@ -51,9 +51,14 @@ var nouns = []string{ "watch", "wheel", "whip", "whistle", "window", "wing", "wire", "worm", } -var constpat = regexp.MustCompile(`^([a-zA-Z0-9_.]+)=([^/].*)$`) -var genpat = regexp.MustCompile(`^((?:[0-9]+\.)?[a-zA-Z0-9_]+)=/([ibfsu][awxrgqt]?)([0-9.-]+)?(,[0-9.-]+)?$`) -var keypat = regexp.MustCompile(`^([0-9]+)\.(.*$)`) +// constfield is a field that *doesn't* start with slash +var constfield = regexp.MustCompile(`^([^/].*)$`) + +// genfield is used to parse generator fields by matching valid commands and numeric arguments +var genfield = regexp.MustCompile(`^/([ibfsu][awxrgqt]?)([0-9.-]+)?(,[0-9.-]+)?$`) + +// keysplitter separates fields that look like number.name (ex: 1.myfield) +var keysplitter = regexp.MustCompile(`^([0-9]+)\.(.*$)`) type Rng struct { rng *rand.Rand @@ -166,39 +171,35 @@ func getWordList(rng Rng, cardinality int, source []string) []string { // parseUserFields expects a list of fields in the form of name=constant or name=/gen. // See README.md for more information. -func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error) { +func parseUserFields(rng Rng, userfields map[string]string) (map[string]func() any, error) { // groups 1 2 3 4 fields := make(map[string]func() any) - for _, field := range userfields { + for name, value := range userfields { // see if it's a constant - matches := constpat.FindStringSubmatch(field) - if matches != nil { - name := matches[1] - value := matches[2] + if constfield.MatchString(value) { fields[name] = getConst(value) continue } // see if it's a generator - matches = genpat.FindStringSubmatch(field) + matches := genfield.FindStringSubmatch(value) if matches == nil { - return nil, fmt.Errorf("unparseable user field %s", field) + return nil, fmt.Errorf("unparseable user field %s=%s", name, value) } var err error - name := matches[1] - gentype := matches[2] - p1 := matches[3] - p2 := matches[4] + gentype := matches[1] + p1 := matches[2] + p2 := matches[3] switch gentype { case "i", "ir", "ig": fields[name], err = getIntGen(rng, gentype, p1, p2) if err != nil { - return nil, fmt.Errorf("invalid int in user field %s: %w", field, err) + return nil, fmt.Errorf("invalid int in user field %s=%s: %w", name, value, err) } case "f", "fr", "fg": fields[name], err = getFloatGen(rng, gentype, p1, p2) if err != nil { - return nil, fmt.Errorf("invalid float in user field %s: %w", field, err) + return nil, fmt.Errorf("invalid float in user field %s=%s: %w", name, value, err) } case "b": n := 50.0 @@ -206,7 +207,7 @@ func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error if p1 != "" { n, err = strconv.ParseFloat(p1, 64) if err != nil || n < 0 || n > 100 { - return nil, fmt.Errorf("invalid bool option in %s", field) + return nil, fmt.Errorf("invalid bool option in %s=%s", name, value) } } fields[name] = func() any { return rng.BoolWithProb(n) } @@ -215,7 +216,7 @@ func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error if p1 != "" { n, err = strconv.Atoi(p1) if err != nil { - return nil, fmt.Errorf("invalid string option in %s", field) + return nil, fmt.Errorf("invalid string option in %s=%s", name, value) } } switch gentype { @@ -236,7 +237,7 @@ func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error // Generate a URL-like string with a random path and possibly a query string fields[name], err = getURLGen(rng, gentype, p1, p2) if err != nil { - return nil, fmt.Errorf("invalid float in user field %s: %w", field, err) + return nil, fmt.Errorf("invalid float in user field %s=%s: %w", name, value, err) } case "st": // Generate a semi-plausible mix of status codes; percentage of 400s and 500s can be controlled by the extra args @@ -246,13 +247,13 @@ func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error if p1 != "" { fours, err = strconv.ParseFloat(p1, 64) if err != nil { - return nil, fmt.Errorf("invalid float in user field %s: %w", field, err) + return nil, fmt.Errorf("invalid float in user field %s=%s: %w", name, value, err) } } if p2 != "" { fives, err = strconv.ParseFloat(p2[1:], 64) if err != nil { - return nil, fmt.Errorf("invalid float in user field %s: %w", field, err) + return nil, fmt.Errorf("invalid float in user field %s=%s: %w", name, value, err) } } twos = 100 - fours - fives @@ -268,7 +269,7 @@ func parseUserFields(rng Rng, userfields []string) (map[string]func() any, error } default: - return nil, fmt.Errorf("invalid generator type %s in field %s", gentype, field) + return nil, fmt.Errorf("invalid generator type %s in field %s=%s", gentype, name, value) } } return fields, nil @@ -411,7 +412,7 @@ type Fielder struct { // combining an adjective and a noun and are consistent for a given fielder. // The field values are randomly generated. // Fielder also includes the process_id. -func NewFielder(seed string, userFields []string, nextras, nservices int) (*Fielder, error) { +func NewFielder(seed string, userFields map[string]string, nextras, nservices int) (*Fielder, error) { rng := NewRng(seed) gens := rng.getValueGenerators() fields, err := parseUserFields(rng, userFields) @@ -440,7 +441,7 @@ func (f *Fielder) GetServiceName(n int) string { // indicate that the field should be included at a specific // level in the trace, where 0 is the root. func (f *Fielder) atLevel(name string, level int) (string, bool) { - matches := keypat.FindStringSubmatch(name) + matches := keysplitter.FindStringSubmatch(name) if len(matches) == 0 { return name, true } diff --git a/generator.go b/generator.go index a7951a3..52d602e 100644 --- a/generator.go +++ b/generator.go @@ -13,7 +13,7 @@ import ( // taking opts.Duration to do so. Its TPS method returns the number of traces // per second it is currently generating. type Generator interface { - Generate(opts Options, wg *sync.WaitGroup, stop chan struct{}, counter chan int64) + Generate(opts *Options, wg *sync.WaitGroup, stop chan struct{}, counter chan int64) TPS() float64 } @@ -39,7 +39,7 @@ type TraceGenerator struct { // make sure it implements Generator var _ Generator = (*TraceGenerator)(nil) -func NewTraceGenerator(tsender Sender, getFielder func() *Fielder, log Logger, opts Options) *TraceGenerator { +func NewTraceGenerator(tsender Sender, getFielder func() *Fielder, log Logger, opts *Options) *TraceGenerator { chans := make([]chan struct{}, 0) return &TraceGenerator{ depth: opts.Format.Depth, @@ -146,7 +146,7 @@ func (s *TraceGenerator) generator(wg *sync.WaitGroup, counter chan int64) { } } -func (s *TraceGenerator) Generate(opts Options, wg *sync.WaitGroup, stop chan struct{}, counter chan int64) { +func (s *TraceGenerator) Generate(opts *Options, wg *sync.WaitGroup, stop chan struct{}, counter chan int64) { defer wg.Done() ngenerators := float64(opts.Quantity.TPS) / s.TPS() uSgeneratorInterval := float64(opts.Quantity.RampTime.Microseconds()) / ngenerators diff --git a/go.mod b/go.mod index ac00a82..3993915 100644 --- a/go.mod +++ b/go.mod @@ -7,57 +7,58 @@ toolchain go1.21.1 require ( github.com/dgryski/go-wyhash v0.0.0-20191203203029-c4841ae36371 github.com/goware/urlx v0.3.2 - github.com/honeycombio/beeline-go v1.15.0 - github.com/honeycombio/otel-config-go v1.14.0 + github.com/honeycombio/beeline-go v1.16.0 + github.com/honeycombio/otel-config-go v1.15.0 github.com/jessevdk/go-flags v1.5.0 - go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 + github.com/jonboulle/clockwork v0.4.0 + go.opentelemetry.io/otel v1.27.0 + go.opentelemetry.io/otel/trace v1.27.0 + gopkg.in/yaml.v3 v3.0.1 pgregory.net/rand v1.0.2 ) require ( github.com/PuerkitoBio/purell v1.2.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 // indirect github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/honeycombio/libhoney-go v1.22.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect - github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect - github.com/sethvargo/go-envconfig v1.0.0 // indirect - github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/honeycombio/libhoney-go v1.23.0 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/sethvargo/go-envconfig v1.0.3 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/tklauser/go-sysconf v0.3.13 // indirect - github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - go.opentelemetry.io/contrib/instrumentation/host v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.21.1 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.21.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.22.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.52.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.52.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.27.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/grpc v1.61.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/alexcesaro/statsd.v2 v2.0.0 // indirect ) diff --git a/go.sum b/go.sum index 9c80594..3886312 100644 --- a/go.sum +++ b/go.sum @@ -4,9 +4,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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= github.com/dgryski/go-wyhash v0.0.0-20191203203029-c4841ae36371 h1:bz5ApY1kzFBvw3yckuyRBCtqGvprWrKswYK468nm+Gs= @@ -24,107 +23,97 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/goware/urlx v0.3.2 h1:gdoo4kBHlkqZNaf6XlQ12LGtQOmpKJrR04Rc3RnpJEo= github.com/goware/urlx v0.3.2/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= -github.com/honeycombio/beeline-go v1.15.0 h1:+pDnESPLYVnzymtiJ6BHH3LgcxK3NyoQoj3HteSlN0s= -github.com/honeycombio/beeline-go v1.15.0/go.mod h1:NITHv4VMG+Mi7NWVmIMG9zVnlO+L8Dy79ntBLWKVeEA= -github.com/honeycombio/libhoney-go v1.22.0 h1:JLDVH6IWoFYHhZqjTqrTLC/lX5kiCCjs+pGqlI9SYPk= -github.com/honeycombio/libhoney-go v1.22.0/go.mod h1:RIaurCpfg5NDWSEV8t3QLcda9dUAiVNyWeHRAaSpN90= -github.com/honeycombio/otel-config-go v1.14.0 h1:Na1ERKYXKrnviORrFF3csdcIJhMVHu1kKUZwhhWMLjs= -github.com/honeycombio/otel-config-go v1.14.0/go.mod h1:Wm3qyqj1Rc9mkSdRxVxhsWdlqOIMNi/46l5/bZEojb4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/honeycombio/beeline-go v1.16.0 h1:hJjxbsq6szrv5bGrrRuIg2ayHwJhEKPX/h6Phf/D4Ss= +github.com/honeycombio/beeline-go v1.16.0/go.mod h1:zYW9IRowl76PocAe0A8rU35EChJlT1AafAwCQ1pPEkI= +github.com/honeycombio/libhoney-go v1.23.0 h1:Cw0uV90w0+GuelJYUzkEbMWVJi7bstkfWWEAvOcakDQ= +github.com/honeycombio/libhoney-go v1.23.0/go.mod h1:mbaBmUkuGwrVa9NdsskMaOzvkYMRbknsfIvavWq+5kA= +github.com/honeycombio/otel-config-go v1.15.0 h1:wUiYG2gGaQEmyZeMMLCb14bx1DR/rxOylkZ05YWN1Jg= +github.com/honeycombio/otel-config-go v1.15.0/go.mod h1:9s9M2qozR0OfVCvTMEQBLMglo1SzuRTxulsU0i/NIkA= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0= -github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= -github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU= -github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc= -github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= -github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sethvargo/go-envconfig v1.0.3 h1:ZDxFGT1M7RPX0wgDOCdZMidrEB+NrayYr6fL0/+pk4I= +github.com/sethvargo/go-envconfig v1.0.3/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/contrib/detectors/aws/lambda v0.46.1 h1:MCtkJ76UiXEphmYGj+C2MB20viwYjEHVn+I+2qq7/os= -go.opentelemetry.io/contrib/detectors/aws/lambda v0.46.1/go.mod h1:mb4RYmSIT8466N536PZ1z3FLiBwiwvX81AisUfgqgyE= -go.opentelemetry.io/contrib/instrumentation/host v0.46.1 h1:jLPv7OPP2CROWQ8PaUx3zONn5S4HjCJnH1syT3fnEEc= -go.opentelemetry.io/contrib/instrumentation/host v0.46.1/go.mod h1:7PhaLiZ6K9zbeZNxOdr+DB8tzxWsrjVa9BcCMGuMPeA= -go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc= -go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= -go.opentelemetry.io/contrib/propagators/b3 v1.21.1 h1:WPYiUgmw3+b7b3sQ1bFBFAf0q+Di9dvNc3AtYfnT4RQ= -go.opentelemetry.io/contrib/propagators/b3 v1.21.1/go.mod h1:EmzokPoSqsYMBVK4nRnhsfm5mbn8J1eDuz/U1UaQaWg= -go.opentelemetry.io/contrib/propagators/ot v1.21.1 h1:3TN5vkXjKYWp0YdMcnUEC/A+pBPvqz9V3nCS2xmcurk= -go.opentelemetry.io/contrib/propagators/ot v1.21.1/go.mod h1:oy0MYCbS/b3cqUDW37wBWtlwBIsutngS++Lklpgh+fc= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 h1:+RbSCde0ERway5FwKvXR3aRJIFeDu9rtwC6E7BC6uoM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0/go.mod h1:zcI8u2EJxbLPyoZ3SkVAAcQPgYb1TDRzW93xLFnsggU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= -go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/detectors/aws/lambda v0.50.0 h1:oQ6efIH7uVEFjMD8dc3Nvf9iVgRiDeKNuBOXwpFkWFI= +go.opentelemetry.io/contrib/detectors/aws/lambda v0.50.0/go.mod h1:STy//KhfG8W+us6VG0itPeQWVUR6ODaGOwvfBdY1WVM= +go.opentelemetry.io/contrib/instrumentation/host v0.52.0 h1:cluW2+yXY/RbUDcMMPFoAFrIHrb+Guu6Hjc3nGG8QLA= +go.opentelemetry.io/contrib/instrumentation/host v0.52.0/go.mod h1:QgaWu0LRrkD9KTV3Nf1wdItC4FA1x5OV2dCmiMwOgTE= +go.opentelemetry.io/contrib/instrumentation/runtime v0.52.0 h1:UaQVCH34fQsyDjlgS0L070Kjs9uCrLKoQfzn2Nl7XTY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.52.0/go.mod h1:Ks4aHdMgu1vAfEY0cIBHcGx2l1S0+PwFm2BE/HRzqSk= +go.opentelemetry.io/contrib/propagators/b3 v1.27.0 h1:IjgxbomVrV9za6bRi8fWCNXENs0co37SZedQilP2hm0= +go.opentelemetry.io/contrib/propagators/b3 v1.27.0/go.mod h1:Dv9obQz25lCisDvvs4dy28UPh974CxkahRDUPsY7y9E= +go.opentelemetry.io/contrib/propagators/ot v1.27.0 h1:xFPqk7ntRR87dqvl6RfeHiq9UlE8mPSuL6Dtr/zysL8= +go.opentelemetry.io/contrib/propagators/ot v1.27.0/go.mod h1:nVLTPrDlSZPoVdeWRmpWBwxA73TYL6XLkC4bj72jvmg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -132,37 +121,30 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA= golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= +google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pgregory.net/rand v1.0.2 h1:ASEbkvwOmY/UPF2evJPBJ8XZg71xdKWYdByqKapI7Vw= diff --git a/main.go b/main.go index 2cea9f7..06c07f0 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,20 @@ package main import ( "fmt" + "log" "net/http" _ "net/http/pprof" "net/url" "os" "os/signal" + "strings" "sync" "syscall" "time" "github.com/goware/urlx" "github.com/jessevdk/go-flags" + "gopkg.in/yaml.v3" ) var ResourceLibrary = "loadgen" @@ -21,34 +24,50 @@ var ResourceVersion = "dev" type Options struct { Telemetry struct { Host string `long:"host" description:"the url of the host to receive the telemetry (or honeycomb, dogfood, local)" default:"honeycomb"` - Insecure bool `long:"insecure" description:"use this for insecure http (not https) connections"` + Insecure bool `long:"insecure" description:"use this for insecure http (not https) connections" yaml:",omitempty"` Dataset string `long:"dataset" description:"sends all traces to the given dataset" env:"HONEYCOMB_DATASET" default:"loadgen"` - APIKey string `long:"apikey" description:"the honeycomb API key" env:"HONEYCOMB_API_KEY"` + APIKey string `long:"apikey" description:"the honeycomb API key(*)" env:"HONEYCOMB_API_KEY" yaml:"-"` } `group:"Telemetry Options"` Format struct { Depth int `long:"depth" description:"the nesting depth of each trace" default:"3"` NSpans int `long:"nspans" description:"the total number of spans in a trace" default:"3"` - Extra int `long:"extra" description:"the number of random fields in a span beyond the standard ones" default:"0"` + Extra int `long:"extra" description:"the number of random fields in a span beyond the standard ones" default:"0" yaml:",omitempty"` TraceTime time.Duration `long:"tracetime" description:"the duration of a trace" default:"1s"` } `group:"Trace Format Options"` Quantity struct { TPS int `long:"tps" description:"the maximum number of traces to generate per second" default:"1"` - TraceCount int64 `long:"tracecount" description:"the maximum number of traces to generate (0 means no limit, but if runtime is not specified defaults to 1)" default:"0"` - RunTime time.Duration `long:"runtime" description:"the maximum time to spend generating traces at max TPS (0 means no limit)" default:"0s"` + TraceCount int64 `long:"tracecount" description:"the maximum number of traces to generate (0 means no limit, but if runtime is not specified defaults to 1)" default:"0" yaml:",omitempty"` + RunTime time.Duration `long:"runtime" description:"the maximum time to spend generating traces at max TPS (0 means no limit)" default:"0s" yaml:",omitempty"` RampTime time.Duration `long:"ramptime" description:"duration to spend ramping up or down to the desired TPS" default:"1s"` } `group:"Quantity Options"` Output struct { Sender string `long:"sender" description:"type of sender" choice:"honeycomb" choice:"otel" choice:"print" choice:"dummy" default:"honeycomb"` Protocol string `long:"protocol" description:"for otel only, protocol to use" choice:"grpc" choice:"protobuf" choice:"json" default:"grpc"` } `group:"Output Options"` - LogLevel string `long:"loglevel" description:"level of logging" choice:"debug" choice:"info" choice:"warn" choice:"error" default:"warn"` - DebugPort int `long:"debugport" description:"port to listen on for pprof" default:"-1"` - Seed string `long:"seed" description:"string seed for random number generator (defaults to dataset name)"` - apihost *url.URL + Global struct { + LogLevel string `long:"loglevel" description:"level of logging" choice:"debug" choice:"info" choice:"warn" choice:"error" default:"warn"` + DebugPort int `long:"debugport" description:"port to listen on for pprof(*)" default:"-1" yaml:"-"` + Seed string `long:"seed" description:"string seed for random number generator (defaults to dataset name)" yaml:",omitempty"` + Config string `long:"config" description:"name of config file to load(*)" default:"" yaml:"-"` + WriteCfg string `long:"writecfg" description:"write effective YAML config to the specified output file and quit(*)" default:"" yaml:"-"` + } `group:"Global Options"` + Fields map[string]string `yaml:"fields,omitempty"` + apihost *url.URL +} + +func newOptions() *Options { + return &Options{Fields: make(map[string]string)} +} + +func (o *Options) CopyStarredFieldsFrom(other *Options) { + o.Telemetry.APIKey = other.Telemetry.APIKey + o.Global.DebugPort = other.Global.DebugPort + o.Global.Config = other.Global.Config + o.Global.WriteCfg = other.Global.WriteCfg } func (o *Options) DebugLevel() int { - switch o.LogLevel { + switch o.Global.LogLevel { case "debug": return 3 case "info": @@ -92,10 +111,38 @@ func parseHost(log Logger, host string, insecure bool) *url.URL { return u } +func ReadConfig(opts *Options, filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + dec := yaml.NewDecoder(f) + err = dec.Decode(opts) + if err != nil { + return err + } + log.Printf("read config from %s\n", filename) + return nil +} + +func WriteConfig(opts *Options, filename string) error { + f, err := os.Create(filename) + if err != nil { + return err + } + enc := yaml.NewEncoder(f) + err = enc.Encode(opts) + if err != nil { + return err + } + log.Printf("wrote config to %s\n", filename) + return nil +} + func main() { - var opts Options + cmdopts := newOptions() - parser := flags.NewParser(&opts, flags.Default) + parser := flags.NewParser(cmdopts, flags.Default) parser.Usage = `[OPTIONS] [FIELD=VALUE]... loadgen generates telemetry loads for performance testing, load testing, and @@ -109,9 +156,9 @@ func main() { It can generate OTLP or Honeycomb-formatted traces, and send them to Honeycomb or (for OTLP) to any OTel agent. - You can specify fields to be added to each span by specifying them on the command - line. Each field should be specified as FIELD=VALUE. The value can be a constant - (and will be sent as the appropriate type), or a generator function starting with /. + You can specify fields to be added to each span. Each field should be specified as + FIELD=VALUE. The value can be a constant (and will be sent as the appropriate type), + or a generator function starting with /. Allowed generators are /i, /ir, /ig, /f, /fr, /fg, /s, /sx, /sw, /b, optionally followed by a single number or a comma-separated pair of numbers. Example generators: @@ -130,9 +177,19 @@ func main() { a number and a dot (e.g. 1.foo=bar) the field will only be injected into spans at that level of nesting (where 0 is the root span). - For full details, see https://github.com/honeycombio/loadgen/ + Fields can also be specified in the config file as key/value pairs under the "fields" key. + + Options can be set in a config file, or on the command line; to specify them in the + config file, specify it on the command line with "--config=FILENAME". The config file + format is YAML; see "example.yml" for an example. + + Note: If a config file is used, it MUST be used for all options, except for the ones + marked in the help text with (*) -- these fields CANNOT be set in the config file. + + For more detail, see https://github.com/honeycombio/loadgen/ ` + // read the command line and envvars into cmdargs args, err := parser.Parse() if err != nil { switch flagsErr := err.(type) { @@ -140,15 +197,40 @@ func main() { if flagsErr.Type == flags.ErrHelp { os.Exit(0) } - os.Exit(1) - default: - os.Exit(1) } + log.Fatalf("error reading command line: %v", err) + } + + opts := newOptions() + if cmdopts.Global.Config != "" { + if err := ReadConfig(opts, cmdopts.Global.Config); err != nil { + log.Fatalf("err %v -- unable to read config file %s", err, cmdopts.Global.Config) + } + opts.CopyStarredFieldsFrom(cmdopts) + } else { + opts = cmdopts // we don't have to read from a file + } + + // split the args into opts.Fields, potentially overwriting + for _, arg := range args { + s := strings.SplitN(arg, "=", 2) + if len(s) < 2 { + log.Fatalf("field `%s` missing required '='", s) + } + opts.Fields[s[0]] = s[1] + } + + if opts.Global.WriteCfg != "" { + err := WriteConfig(opts, opts.Global.WriteCfg) + if err != nil { + log.Fatalf("unable to write config: %s\n", err) + } + os.Exit(0) } - if opts.DebugPort > 0 { + if opts.Global.DebugPort > 0 { go func() { - http.ListenAndServe(fmt.Sprintf("localhost:%d", opts.DebugPort), nil) + http.ListenAndServe(fmt.Sprintf("localhost:%d", opts.Global.DebugPort), nil) }() } @@ -160,7 +242,7 @@ func main() { log := NewLogger(opts.DebugLevel()) getFielderFn := func() *Fielder { - getFielder, err := NewFielder(opts.Seed, args, opts.Format.Extra, opts.Format.Depth) + getFielder, err := NewFielder(opts.Global.Seed, opts.Fields, opts.Format.Extra, opts.Format.Depth) if err != nil { log.Fatal("unable to create fields as specified: %s\n", err) } diff --git a/sample_config.yaml b/sample_config.yaml new file mode 100644 index 0000000..5b284ca --- /dev/null +++ b/sample_config.yaml @@ -0,0 +1,24 @@ +# This is a sample YAML configuration for sending data to +# a production Honeycomb dataset using OTel. +# Note that you must specify HONEYCOMB_API_KEY in your environment +# or --apikey on the command line to authenticate with Honeycomb. +telemetry: + dataset: my-dataset +format: + depth: 5 + nspans: 100 + tracetime: 10s +quantity: + tps: 1 + tracecount: 2 + ramptime: 1s +output: + sender: honeycomb + protocol: grpc +global: + loglevel: warn +fields: + # simulate URLs for 10 services, each of which has 10 endpoints + http.url: /u10,10 + # generate status codes where 10% are 400s and .1% are 500s + http.status: /st10,0.1 diff --git a/sender_dummy.go b/sender_dummy.go index 026867b..29fc1de 100644 --- a/sender_dummy.go +++ b/sender_dummy.go @@ -52,7 +52,7 @@ type SenderDummy struct { // make sure it implements Sender var _ Sender = (*SenderDummy)(nil) -func NewSenderDummy(log Logger, opts Options) Sender { +func NewSenderDummy(log Logger, opts *Options) Sender { return &SenderDummy{log: log} } diff --git a/sender_honeycomb.go b/sender_honeycomb.go index a7c04d6..ea2462b 100644 --- a/sender_honeycomb.go +++ b/sender_honeycomb.go @@ -11,7 +11,7 @@ type SenderHoneycomb struct{} // make sure it implements Sender var _ Sender = (*SenderHoneycomb)(nil) -func NewSenderHoneycomb(opts Options) *SenderHoneycomb { +func NewSenderHoneycomb(opts *Options) *SenderHoneycomb { beeline.Init(beeline.Config{ WriteKey: opts.Telemetry.APIKey, APIHost: opts.apihost.String(), diff --git a/sender_otel_honey.go b/sender_otel_honey.go index 12bde94..62d73b8 100644 --- a/sender_otel_honey.go +++ b/sender_otel_honey.go @@ -45,7 +45,7 @@ func (l OtelLogger) Fatalf(format string, args ...interface{}) { l.Logger.Fatal(format, args...) } -func NewSenderOTel(log Logger, opts Options) *SenderOTel { +func NewSenderOTel(log Logger, opts *Options) *SenderOTel { var protocol otelconfig.Protocol switch opts.Output.Protocol { case "grpc": @@ -64,7 +64,7 @@ func NewSenderOTel(log Logger, opts Options) *SenderOTel { otelconfig.WithTracesExporterEndpoint(otelTracesFromURL(opts.apihost)), otelconfig.WithTracesExporterInsecure(opts.Telemetry.Insecure), otelconfig.WithMetricsEnabled(false), - otelconfig.WithLogLevel(opts.LogLevel), + otelconfig.WithLogLevel(opts.Global.LogLevel), otelconfig.WithLogger(OtelLogger{log}), otelconfig.WithHeaders(map[string]string{ "x-honeycomb-team": opts.Telemetry.APIKey, diff --git a/sender_print.go b/sender_print.go index 5f82308..bb33eaf 100644 --- a/sender_print.go +++ b/sender_print.go @@ -56,7 +56,7 @@ type SenderPrint struct { log Logger } -func NewSenderPrint(log Logger, opts Options) Sender { +func NewSenderPrint(log Logger, opts *Options) Sender { return &SenderPrint{ log: log, }