diff --git a/cmd/outputs.go b/cmd/outputs.go index 4875adff589..5c644c68884 100644 --- a/cmd/outputs.go +++ b/cmd/outputs.go @@ -14,7 +14,6 @@ import ( "go.k6.io/k6/output/csv" "go.k6.io/k6/output/influxdb" "go.k6.io/k6/output/json" - "go.k6.io/k6/output/statsd" "github.com/grafana/xk6-dashboard/dashboard" "github.com/grafana/xk6-output-opentelemetry/pkg/opentelemetry" @@ -52,12 +51,11 @@ func getAllOutputConstructors() (map[string]output.Constructor, error) { return nil, errors.New("the kafka output was deprecated in k6 v0.32.0 and removed in k6 v0.34.0, " + "please use the new xk6 kafka output extension instead - https://github.com/k6io/xk6-output-kafka") }, - builtinOutputStatsd.String(): func(params output.Params) (output.Output, error) { - params.Logger.Warn("The statsd output is deprecated, and will be removed in k6 v0.55.0 " + - "Please use the new xk6 statsd output extension instead. " + + builtinOutputStatsd.String(): func(_ output.Params) (output.Output, error) { + return nil, errors.New("the statsd output was deprecated in k6 v0.47.0 and removed in in k6 v0.55.0, " + + "please use the new xk6 statsd output extension instead. " + "It can be found at https://github.com/LeonAdato/xk6-output-statsd and " + - "more info at https://github.com/grafana/k6/issues/2982.") - return statsd.New(params) + "more info at https://github.com/grafana/k6/issues/2982") }, builtinOutputDatadog.String(): func(_ output.Params) (output.Output, error) { return nil, errors.New("the datadog output was deprecated in k6 v0.32.0 and removed in k6 v0.34.0, " + diff --git a/go.mod b/go.mod index efd5cc7a8cd..afd2456f0e0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.20 require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a github.com/PuerkitoBio/goquery v1.9.2 github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 github.com/andybalholm/brotli v1.1.0 diff --git a/go.sum b/go.sum index 2ed5455f162..3ba10257456 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a h1:zpQSzEApXM0qkXcpdjeJ4OpnBWhD/X8zT/iT1wYLiVU= -github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 h1:k+1+doEm31k0rRjCjLnGG3YRkuO9ljaEyS2ajZd6GK8= diff --git a/output/statsd/config.go b/output/statsd/config.go deleted file mode 100644 index 544e9649e68..00000000000 --- a/output/statsd/config.go +++ /dev/null @@ -1,94 +0,0 @@ -// Package statsd is implements an output to send metric samples to a statsd server. -package statsd - -import ( - "encoding/json" - "time" - - "github.com/mstoykov/envconfig" - "gopkg.in/guregu/null.v3" - - "go.k6.io/k6/lib/types" - "go.k6.io/k6/metrics" -) - -// config defines the StatsD configuration. -type config struct { - Addr null.String `json:"addr,omitempty" envconfig:"K6_STATSD_ADDR"` - BufferSize null.Int `json:"bufferSize,omitempty" envconfig:"K6_STATSD_BUFFER_SIZE"` - Namespace null.String `json:"namespace,omitempty" envconfig:"K6_STATSD_NAMESPACE"` - PushInterval types.NullDuration `json:"pushInterval,omitempty" envconfig:"K6_STATSD_PUSH_INTERVAL"` - TagBlocklist metrics.EnabledTags `json:"tagBlocklist,omitempty" envconfig:"K6_STATSD_TAG_BLOCKLIST"` - EnableTags null.Bool `json:"enableTags,omitempty" envconfig:"K6_STATSD_ENABLE_TAGS"` -} - -func processTags(t metrics.EnabledTags, tags map[string]string) []string { - var res []string - for key, value := range tags { - if value != "" && !t[key] { - res = append(res, key+":"+value) - } - } - return res -} - -// Apply saves config non-zero config values from the passed config in the receiver. -func (c config) Apply(cfg config) config { - if cfg.Addr.Valid { - c.Addr = cfg.Addr - } - if cfg.BufferSize.Valid { - c.BufferSize = cfg.BufferSize - } - if cfg.Namespace.Valid { - c.Namespace = cfg.Namespace - } - if cfg.PushInterval.Valid { - c.PushInterval = cfg.PushInterval - } - if cfg.TagBlocklist != nil { - c.TagBlocklist = cfg.TagBlocklist - } - if cfg.EnableTags.Valid { - c.EnableTags = cfg.EnableTags - } - - return c -} - -// newConfig creates a new Config instance with default values for some fields. -func newConfig() config { - return config{ - Addr: null.NewString("localhost:8125", false), - BufferSize: null.NewInt(20, false), - Namespace: null.NewString("k6.", false), - PushInterval: types.NewNullDuration(1*time.Second, false), - TagBlocklist: metrics.SystemTagSet(metrics.TagVU | metrics.TagIter | metrics.TagURL).Map(), - EnableTags: null.NewBool(false, false), - } -} - -// getConsolidatedConfig combines {default config values + JSON config + -// environment vars}, and returns the final result. -func getConsolidatedConfig(jsonRawConf json.RawMessage, env map[string]string, _ string) (config, error) { - result := newConfig() - if jsonRawConf != nil { - jsonConf := config{} - if err := json.Unmarshal(jsonRawConf, &jsonConf); err != nil { - return result, err - } - result = result.Apply(jsonConf) - } - - envConfig := config{} - _ = env // TODO: get rid of envconfig and actually use the env parameter... - if err := envconfig.Process("", &envConfig, func(key string) (string, bool) { - v, ok := env[key] - return v, ok - }); err != nil { - return result, err - } - result = result.Apply(envConfig) - - return result, nil -} diff --git a/output/statsd/helper_test.go b/output/statsd/helper_test.go deleted file mode 100644 index 7f48cb6df6a..00000000000 --- a/output/statsd/helper_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package statsd - -import ( - "net" - "testing" - "time" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" - - "go.k6.io/k6/lib/testutils" - "go.k6.io/k6/lib/types" - "go.k6.io/k6/metrics" -) - -type getOutputFn func( - logger logrus.FieldLogger, - addr, namespace null.String, - bufferSize null.Int, - pushInterval types.NullDuration, -) (*Output, error) - -func baseTest(t *testing.T, - getOutput getOutputFn, - checkResult func(t *testing.T, samples []metrics.SampleContainer, expectedOutput, output string), -) { - t.Helper() - testNamespace := "testing.things." // to be dynamic - - addr, err := net.ResolveUDPAddr("udp", "localhost:0") - require.NoError(t, err) - listener, err := net.ListenUDP("udp", addr) // we want to listen on a random port - require.NoError(t, err) - ch := make(chan string, 20) - end := make(chan struct{}) - defer close(end) - - go func() { - defer close(ch) - var buf [4096]byte - for { - select { - case <-end: - return - default: - n, _, err := listener.ReadFromUDP(buf[:]) - require.NoError(t, err) - ch <- string(buf[:n]) - } - } - }() - - pushInterval := types.NullDurationFrom(time.Millisecond * 10) - collector, err := getOutput( - testutils.NewLogger(t), - null.StringFrom(listener.LocalAddr().String()), - null.StringFrom(testNamespace), - null.IntFrom(5), - pushInterval, - ) - require.NoError(t, err) - require.NoError(t, collector.Start()) - defer func() { - require.NoError(t, collector.Stop()) - }() - - registry := metrics.NewRegistry() - newSample := func(m *metrics.Metric, value float64, tags map[string]string) metrics.Sample { - return metrics.Sample{ - TimeSeries: metrics.TimeSeries{ - Metric: m, - Tags: registry.RootTagSet().WithTagsFromMap(tags), - }, - Time: time.Now(), - Value: value, - } - } - - myCounter, err := registry.NewMetric("my_counter", metrics.Counter) - require.NoError(t, err) - myGauge, err := registry.NewMetric("my_gauge", metrics.Gauge) - require.NoError(t, err) - myTrend, err := registry.NewMetric("my_trend", metrics.Trend) - require.NoError(t, err) - myRate, err := registry.NewMetric("my_rate", metrics.Rate) - require.NoError(t, err) - myCheck, err := registry.NewMetric("my_check", metrics.Rate) - require.NoError(t, err) - - testMatrix := []struct { - input []metrics.SampleContainer - output string - }{ - { - input: []metrics.SampleContainer{ - newSample(myCounter, 12, map[string]string{ - "tag1": "value1", - "tag3": "value3", - }), - }, - output: "testing.things.my_counter:12|c", - }, - { - input: []metrics.SampleContainer{ - newSample(myGauge, 13, map[string]string{ - "tag1": "value1", - "tag3": "value3", - }), - }, - output: "testing.things.my_gauge:13.000000|g", - }, - { - input: []metrics.SampleContainer{ - newSample(myTrend, 14, map[string]string{ - "tag1": "value1", - "tag3": "value3", - }), - }, - output: "testing.things.my_trend:14.000000|ms", - }, - { - input: []metrics.SampleContainer{ - newSample(myRate, 15, map[string]string{ - "tag1": "value1", - "tag3": "value3", - }), - }, - output: "testing.things.my_rate:15|c", - }, - { - input: []metrics.SampleContainer{ - newSample(myCheck, 16, map[string]string{ - "tag1": "value1", - "tag3": "value3", - "check": "max<100", - }), - newSample(myCheck, 0, map[string]string{ - "tag1": "value1", - "tag3": "value3", - "check": "max>100", - }), - }, - output: "testing.things.check.max<100.pass:1|c\ntesting.things.check.max>100.fail:1|c", - }, - } - for _, test := range testMatrix { - collector.AddMetricSamples(test.input) - time.Sleep((time.Duration)(pushInterval.Duration)) - output := <-ch - checkResult(t, test.input, test.output, output) - } -} diff --git a/output/statsd/output.go b/output/statsd/output.go deleted file mode 100644 index 51b3bc27032..00000000000 --- a/output/statsd/output.go +++ /dev/null @@ -1,164 +0,0 @@ -package statsd - -import ( - "fmt" - "time" - - "github.com/DataDog/datadog-go/statsd" - "github.com/sirupsen/logrus" - - "go.k6.io/k6/metrics" - "go.k6.io/k6/output" -) - -// New creates a new statsd connector client -func New(params output.Params) (output.Output, error) { - return newOutput(params) -} - -func newOutput(params output.Params) (*Output, error) { - conf, err := getConsolidatedConfig(params.JSONConfig, params.Environment, params.ConfigArgument) - if err != nil { - return nil, err - } - logger := params.Logger.WithFields(logrus.Fields{"output": "statsd"}) - - return &Output{ - config: conf, - logger: logger, - }, nil -} - -var _ output.Output = &Output{} - -// Output sends result data to statsd daemons with the ability to send to datadog as well -type Output struct { - output.SampleBuffer - - periodicFlusher *output.PeriodicFlusher - - config config - - logger logrus.FieldLogger - client *statsd.Client -} - -func (o *Output) dispatch(entry metrics.Sample) error { - var tagList []string - if o.config.EnableTags.Bool { - tagList = processTags(o.config.TagBlocklist, entry.Tags.Map()) - } - - switch entry.Metric.Type { - case metrics.Counter: - return o.client.Count(entry.Metric.Name, int64(entry.Value), tagList, 1) - case metrics.Trend: - return o.client.TimeInMilliseconds(entry.Metric.Name, entry.Value, tagList, 1) - case metrics.Gauge: - return o.client.Gauge(entry.Metric.Name, entry.Value, tagList, 1) - case metrics.Rate: - if check, ok := entry.Tags.Get("check"); ok { - return o.client.Count( - checkToString(check, entry.Value), - 1, - tagList, - 1, - ) - } - return o.client.Count(entry.Metric.Name, int64(entry.Value), tagList, 1) - default: - return fmt.Errorf("unsupported metric type %s", entry.Metric.Type) - } -} - -func checkToString(check string, value float64) string { - label := "pass" - if value == 0 { - label = "fail" - } - return "check." + check + "." + label -} - -// Description returns a human-readable description of the output. -func (o *Output) Description() string { - return fmt.Sprintf("statsd (%s)", o.config.Addr.String) -} - -// Start tries to open a connection to specified statsd service and starts the goroutine for -// metric flushing. -func (o *Output) Start() error { - o.logger.Debug("Starting...") - - var err error - if address := o.config.Addr.String; address == "" { - err = fmt.Errorf( - "connection string is invalid. Received: \"%+s\"", - address, - ) - o.logger.Error(err) - - return err - } - - o.client, err = statsd.NewBuffered(o.config.Addr.String, int(o.config.BufferSize.Int64)) - if err != nil { - o.logger.Errorf("Couldn't make buffered client, %s", err) - return err - } - - if namespace := o.config.Namespace.String; namespace != "" { - o.client.Namespace = namespace - } - - pf, err := output.NewPeriodicFlusher(o.config.PushInterval.TimeDuration(), o.flushMetrics) - if err != nil { - return err - } - o.logger.Debug("Started!") - o.periodicFlusher = pf - - return nil -} - -// Stop flushes any remaining metrics and stops the goroutine. -func (o *Output) Stop() error { - o.logger.Debug("Stopping...") - defer o.logger.Debug("Stopped!") - o.periodicFlusher.Stop() - return o.client.Close() -} - -func (o *Output) flushMetrics() { - samples := o.GetBufferedSamples() - start := time.Now() - var count int - var errorCount int - for _, sc := range samples { - samples := sc.GetSamples() - count += len(samples) - o.logger. - WithField("samples", len(samples)). - Debug("Pushing metrics to server") - - for _, entry := range samples { - if err := o.dispatch(entry); err != nil { - // No need to return error if just one metric didn't go through - o.logger.WithError(err).Debugf("Error while sending metric %s", entry.Metric.Name) - errorCount++ - } - } - } - - if count > 0 { - if errorCount != 0 { - o.logger.Warnf("Couldn't send %d out of %d metrics. Enable verbose logging with --verbose to see individual errors", - errorCount, count) - } - if err := o.client.Flush(); err != nil { - o.logger. - WithError(err). - Error("Couldn't flush a batch") - } - o.logger.WithField("t", time.Since(start)).WithField("count", count).Debug("Wrote metrics to statsd") - } -} diff --git a/output/statsd/output_test.go b/output/statsd/output_test.go deleted file mode 100644 index c93c1fcd8e5..00000000000 --- a/output/statsd/output_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package statsd - -import ( - "encoding/json" - "fmt" - "strings" - "testing" - - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" - - "go.k6.io/k6/lib/testutils" - "go.k6.io/k6/lib/types" - "go.k6.io/k6/metrics" - "go.k6.io/k6/output" -) - -func getOutput( - logger logrus.FieldLogger, addr, namespace null.String, bufferSize null.Int, pushInterval types.NullDuration, -) (*Output, error) { - return newOutput( - output.Params{ - Logger: logger, - JSONConfig: json.RawMessage(fmt.Sprintf(`{ - "addr": "%s", - "namespace": "%s", - "bufferSize": %d, - "pushInterval": "%s" - }`, addr.String, namespace.String, bufferSize.Int64, pushInterval.Duration.String())), - }) -} - -func TestStatsdOutput(t *testing.T) { - t.Parallel() - baseTest(t, getOutput, - func(t *testing.T, _ []metrics.SampleContainer, expectedOutput, output string) { - assert.Equal(t, expectedOutput, output) - }) -} - -func TestStatsdEnabledTags(t *testing.T) { - t.Parallel() - tagMap := metrics.EnabledTags{"tag1": true, "tag2": true} - - baseTest(t, func( - logger logrus.FieldLogger, addr, namespace null.String, bufferSize null.Int, pushInterval types.NullDuration, - ) (*Output, error) { - return newOutput( - output.Params{ - Logger: logger, - JSONConfig: json.RawMessage(fmt.Sprintf(`{ - "addr": "%s", - "namespace": "%s", - "bufferSize": %d, - "pushInterval": "%s", - "tagBlocklist": ["tag1", "tag2"], - "enableTags": true - }`, addr.String, namespace.String, bufferSize.Int64, pushInterval.Duration.String())), - }) - }, func(t *testing.T, containers []metrics.SampleContainer, expectedOutput, output string) { - outputLines := strings.Split(output, "\n") - expectedOutputLines := strings.Split(expectedOutput, "\n") - var lines int - - for i, container := range containers { - for j, sample := range container.GetSamples() { - lines++ - var ( - expectedTagList = processTags(tagMap, sample.GetTags().Map()) - expectedOutputLine = expectedOutputLines[i*j+i] - outputLine = outputLines[i*j+i] - outputWithoutTags = outputLine - outputTagList = []string{} - tagSplit = strings.LastIndex(outputLine, "|#") - ) - if tagSplit != -1 { - outputWithoutTags = outputLine[:tagSplit] - outputTagList = strings.Split(outputLine[tagSplit+len("|#"):], ",") - } - require.Equal(t, expectedOutputLine, outputWithoutTags) - require.ElementsMatch(t, expectedTagList, outputTagList) - } - } - require.Equal(t, lines, len(outputLines)) - }) -} - -func TestInitWithoutAddressErrors(t *testing.T) { - t.Parallel() - c := &Output{ - config: config{}, - logger: testutils.NewLogger(t), - } - err := c.Start() - require.Error(t, err) -} - -func TestInitWithBogusAddressErrors(t *testing.T) { - t.Parallel() - c := &Output{ - config: config{ - Addr: null.StringFrom("localhost:90000"), - }, - logger: testutils.NewLogger(t), - } - err := c.Start() - require.Error(t, err) -} - -func TestLinkReturnAddress(t *testing.T) { - t.Parallel() - bogusValue := "bogus value" - c := &Output{ - config: config{ - Addr: null.StringFrom(bogusValue), - }, - } - require.Equal(t, fmt.Sprintf("statsd (%s)", bogusValue), c.Description()) -} diff --git a/vendor/github.com/DataDog/datadog-go/LICENSE.txt b/vendor/github.com/DataDog/datadog-go/LICENSE.txt deleted file mode 100644 index 97cd06d7fb1..00000000000 --- a/vendor/github.com/DataDog/datadog-go/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015 Datadog, Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/DataDog/datadog-go/statsd/README.md b/vendor/github.com/DataDog/datadog-go/statsd/README.md deleted file mode 100644 index f68df54be9f..00000000000 --- a/vendor/github.com/DataDog/datadog-go/statsd/README.md +++ /dev/null @@ -1,64 +0,0 @@ -## Overview - -Package `statsd` provides a Go [dogstatsd](http://docs.datadoghq.com/guides/dogstatsd/) client. Dogstatsd extends Statsd, adding tags -and histograms. - -## Get the code - - $ go get github.com/DataDog/datadog-go/statsd - -## Usage - -```go -// Create the client -c, err := statsd.New("127.0.0.1:8125") -if err != nil { - log.Fatal(err) -} -// Prefix every metric with the app name -c.Namespace = "flubber." -// Send the EC2 availability zone as a tag with every metric -c.Tags = append(c.Tags, "us-east-1a") - -// Do some metrics! -err = c.Gauge("request.queue_depth", 12, nil, 1) -err = c.Timing("request.duration", duration, nil, 1) // Uses a time.Duration! -err = c.TimeInMilliseconds("request", 12, nil, 1) -err = c.Incr("request.count_total", nil, 1) -err = c.Decr("request.count_total", nil, 1) -err = c.Count("request.count_total", 2, nil, 1) -``` - -## Buffering Client - -DogStatsD accepts packets with multiple statsd payloads in them. Using the BufferingClient via `NewBufferingClient` will buffer up commands and send them when the buffer is reached or after 100msec. - -## Unix Domain Sockets Client - -DogStatsD version 6 accepts packets through a Unix Socket datagram connection. You can use this protocol by giving a -`unix:///path/to/dsd.socket` addr argument to the `New` or `NewBufferingClient`. - -With this protocol, writes can become blocking if the server's receiving buffer is full. Our default behaviour is to -timeout and drop the packet after 1 ms. You can set a custom timeout duration via the `SetWriteTimeout` method. - -The default mode is to pass write errors from the socket to the caller. This includes write errors the library will -automatically recover from (DogStatsD server not ready yet or is restarting). You can drop these errors and emulate -the UDP behaviour by setting the `SkipErrors` property to `true`. Please note that packets will be dropped in both modes. - -## Development - -Run the tests with: - - $ go test - -## Documentation - -Please see: http://godoc.org/github.com/DataDog/datadog-go/statsd - -## License - -go-dogstatsd is released under the [MIT license](http://www.opensource.org/licenses/mit-license.php). - -## Credits - -Original code by [ooyala](https://github.com/ooyala/go-dogstatsd). diff --git a/vendor/github.com/DataDog/datadog-go/statsd/statsd.go b/vendor/github.com/DataDog/datadog-go/statsd/statsd.go deleted file mode 100644 index 0ae4dac03cb..00000000000 --- a/vendor/github.com/DataDog/datadog-go/statsd/statsd.go +++ /dev/null @@ -1,680 +0,0 @@ -// Copyright 2013 Ooyala, Inc. - -/* -Package statsd provides a Go dogstatsd client. Dogstatsd extends the popular statsd, -adding tags and histograms and pushing upstream to Datadog. - -Refer to http://docs.datadoghq.com/guides/dogstatsd/ for information about DogStatsD. - -Example Usage: - - // Create the client - c, err := statsd.New("127.0.0.1:8125") - if err != nil { - log.Fatal(err) - } - // Prefix every metric with the app name - c.Namespace = "flubber." - // Send the EC2 availability zone as a tag with every metric - c.Tags = append(c.Tags, "us-east-1a") - err = c.Gauge("request.duration", 1.2, nil, 1) - -statsd is based on go-statsd-client. -*/ -package statsd - -import ( - "bytes" - "errors" - "fmt" - "io" - "math/rand" - "strconv" - "strings" - "sync" - "time" -) - -/* -OptimalPayloadSize defines the optimal payload size for a UDP datagram, 1432 bytes -is optimal for regular networks with an MTU of 1500 so datagrams don't get -fragmented. It's generally recommended not to fragment UDP datagrams as losing -a single fragment will cause the entire datagram to be lost. - -This can be increased if your network has a greater MTU or you don't mind UDP -datagrams getting fragmented. The practical limit is MaxUDPPayloadSize -*/ -const OptimalPayloadSize = 1432 - -/* -MaxUDPPayloadSize defines the maximum payload size for a UDP datagram. -Its value comes from the calculation: 65535 bytes Max UDP datagram size - -8byte UDP header - 60byte max IP headers -any number greater than that will see frames being cut out. -*/ -const MaxUDPPayloadSize = 65467 - -/* -UnixAddressPrefix holds the prefix to use to enable Unix Domain Socket -traffic instead of UDP. -*/ -const UnixAddressPrefix = "unix://" - -/* -Stat suffixes -*/ -var ( - gaugeSuffix = []byte("|g") - countSuffix = []byte("|c") - histogramSuffix = []byte("|h") - distributionSuffix = []byte("|d") - decrSuffix = []byte("-1|c") - incrSuffix = []byte("1|c") - setSuffix = []byte("|s") - timingSuffix = []byte("|ms") -) - -// A statsdWriter offers a standard interface regardless of the underlying -// protocol. For now UDS and UPD writers are available. -type statsdWriter interface { - Write(data []byte) (n int, err error) - SetWriteTimeout(time.Duration) error - Close() error -} - -// A Client is a handle for sending messages to dogstatsd. It is safe to -// use one Client from multiple goroutines simultaneously. -type Client struct { - // Writer handles the underlying networking protocol - writer statsdWriter - // Namespace to prepend to all statsd calls - Namespace string - // Tags are global tags to be added to every statsd call - Tags []string - // skipErrors turns off error passing and allows UDS to emulate UDP behaviour - SkipErrors bool - // BufferLength is the length of the buffer in commands. - bufferLength int - flushTime time.Duration - commands []string - buffer bytes.Buffer - stop chan struct{} - sync.Mutex -} - -// New returns a pointer to a new Client given an addr in the format "hostname:port" or -// "unix:///path/to/socket". -func New(addr string) (*Client, error) { - if strings.HasPrefix(addr, UnixAddressPrefix) { - w, err := newUdsWriter(addr[len(UnixAddressPrefix)-1:]) - if err != nil { - return nil, err - } - return NewWithWriter(w) - } - w, err := newUDPWriter(addr) - if err != nil { - return nil, err - } - return NewWithWriter(w) -} - -// NewWithWriter creates a new Client with given writer. Writer is a -// io.WriteCloser + SetWriteTimeout(time.Duration) error -func NewWithWriter(w statsdWriter) (*Client, error) { - client := &Client{writer: w, SkipErrors: false} - return client, nil -} - -// NewBuffered returns a Client that buffers its output and sends it in chunks. -// Buflen is the length of the buffer in number of commands. -func NewBuffered(addr string, buflen int) (*Client, error) { - client, err := New(addr) - if err != nil { - return nil, err - } - client.bufferLength = buflen - client.commands = make([]string, 0, buflen) - client.flushTime = time.Millisecond * 100 - client.stop = make(chan struct{}, 1) - go client.watch() - return client, nil -} - -// format a message from its name, value, tags and rate. Also adds global -// namespace and tags. -func (c *Client) format(name string, value interface{}, suffix []byte, tags []string, rate float64) string { - var buf bytes.Buffer - if c.Namespace != "" { - buf.WriteString(c.Namespace) - } - buf.WriteString(name) - buf.WriteString(":") - - switch val := value.(type) { - case float64: - buf.Write(strconv.AppendFloat([]byte{}, val, 'f', 6, 64)) - - case int64: - buf.Write(strconv.AppendInt([]byte{}, val, 10)) - - case string: - buf.WriteString(val) - - default: - // do nothing - } - buf.Write(suffix) - - if rate < 1 { - buf.WriteString(`|@`) - buf.WriteString(strconv.FormatFloat(rate, 'f', -1, 64)) - } - - writeTagString(&buf, c.Tags, tags) - - return buf.String() -} - -// SetWriteTimeout allows the user to set a custom UDS write timeout. Not supported for UDP. -func (c *Client) SetWriteTimeout(d time.Duration) error { - if c == nil { - return nil - } - return c.writer.SetWriteTimeout(d) -} - -func (c *Client) watch() { - ticker := time.NewTicker(c.flushTime) - - for { - select { - case <-ticker.C: - c.Lock() - if len(c.commands) > 0 { - // FIXME: eating error here - c.flushLocked() - } - c.Unlock() - case <-c.stop: - ticker.Stop() - return - } - } -} - -func (c *Client) append(cmd string) error { - c.Lock() - defer c.Unlock() - c.commands = append(c.commands, cmd) - // if we should flush, lets do it - if len(c.commands) == c.bufferLength { - if err := c.flushLocked(); err != nil { - return err - } - } - return nil -} - -func (c *Client) joinMaxSize(cmds []string, sep string, maxSize int) ([][]byte, []int) { - c.buffer.Reset() //clear buffer - - var frames [][]byte - var ncmds []int - sepBytes := []byte(sep) - sepLen := len(sep) - - elem := 0 - for _, cmd := range cmds { - needed := len(cmd) - - if elem != 0 { - needed = needed + sepLen - } - - if c.buffer.Len()+needed <= maxSize { - if elem != 0 { - c.buffer.Write(sepBytes) - } - c.buffer.WriteString(cmd) - elem++ - } else { - frames = append(frames, copyAndResetBuffer(&c.buffer)) - ncmds = append(ncmds, elem) - // if cmd is bigger than maxSize it will get flushed on next loop - c.buffer.WriteString(cmd) - elem = 1 - } - } - - //add whatever is left! if there's actually something - if c.buffer.Len() > 0 { - frames = append(frames, copyAndResetBuffer(&c.buffer)) - ncmds = append(ncmds, elem) - } - - return frames, ncmds -} - -func copyAndResetBuffer(buf *bytes.Buffer) []byte { - tmpBuf := make([]byte, buf.Len()) - copy(tmpBuf, buf.Bytes()) - buf.Reset() - return tmpBuf -} - -// Flush forces a flush of the pending commands in the buffer -func (c *Client) Flush() error { - if c == nil { - return nil - } - c.Lock() - defer c.Unlock() - return c.flushLocked() -} - -// flush the commands in the buffer. Lock must be held by caller. -func (c *Client) flushLocked() error { - frames, flushable := c.joinMaxSize(c.commands, "\n", OptimalPayloadSize) - var err error - cmdsFlushed := 0 - for i, data := range frames { - _, e := c.writer.Write(data) - if e != nil { - err = e - break - } - cmdsFlushed += flushable[i] - } - - // clear the slice with a slice op, doesn't realloc - if cmdsFlushed == len(c.commands) { - c.commands = c.commands[:0] - } else { - //this case will cause a future realloc... - // drop problematic command though (sorry). - c.commands = c.commands[cmdsFlushed+1:] - } - return err -} - -func (c *Client) sendMsg(msg string) error { - // return an error if message is bigger than MaxUDPPayloadSize - if len(msg) > MaxUDPPayloadSize { - return errors.New("message size exceeds MaxUDPPayloadSize") - } - - // if this client is buffered, then we'll just append this - if c.bufferLength > 0 { - return c.append(msg) - } - - _, err := c.writer.Write([]byte(msg)) - - if c.SkipErrors { - return nil - } - return err -} - -// send handles sampling and sends the message over UDP. It also adds global namespace prefixes and tags. -func (c *Client) send(name string, value interface{}, suffix []byte, tags []string, rate float64) error { - if c == nil { - return nil - } - if rate < 1 && rand.Float64() > rate { - return nil - } - data := c.format(name, value, suffix, tags, rate) - return c.sendMsg(data) -} - -// Gauge measures the value of a metric at a particular time. -func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error { - return c.send(name, value, gaugeSuffix, tags, rate) -} - -// Count tracks how many times something happened per second. -func (c *Client) Count(name string, value int64, tags []string, rate float64) error { - return c.send(name, value, countSuffix, tags, rate) -} - -// Histogram tracks the statistical distribution of a set of values on each host. -func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error { - return c.send(name, value, histogramSuffix, tags, rate) -} - -// Distribution tracks the statistical distribution of a set of values across your infrastructure. -func (c *Client) Distribution(name string, value float64, tags []string, rate float64) error { - return c.send(name, value, distributionSuffix, tags, rate) -} - -// Decr is just Count of -1 -func (c *Client) Decr(name string, tags []string, rate float64) error { - return c.send(name, nil, decrSuffix, tags, rate) -} - -// Incr is just Count of 1 -func (c *Client) Incr(name string, tags []string, rate float64) error { - return c.send(name, nil, incrSuffix, tags, rate) -} - -// Set counts the number of unique elements in a group. -func (c *Client) Set(name string, value string, tags []string, rate float64) error { - return c.send(name, value, setSuffix, tags, rate) -} - -// Timing sends timing information, it is an alias for TimeInMilliseconds -func (c *Client) Timing(name string, value time.Duration, tags []string, rate float64) error { - return c.TimeInMilliseconds(name, value.Seconds()*1000, tags, rate) -} - -// TimeInMilliseconds sends timing information in milliseconds. -// It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing) -func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error { - return c.send(name, value, timingSuffix, tags, rate) -} - -// Event sends the provided Event. -func (c *Client) Event(e *Event) error { - if c == nil { - return nil - } - stat, err := e.Encode(c.Tags...) - if err != nil { - return err - } - return c.sendMsg(stat) -} - -// SimpleEvent sends an event with the provided title and text. -func (c *Client) SimpleEvent(title, text string) error { - e := NewEvent(title, text) - return c.Event(e) -} - -// ServiceCheck sends the provided ServiceCheck. -func (c *Client) ServiceCheck(sc *ServiceCheck) error { - if c == nil { - return nil - } - stat, err := sc.Encode(c.Tags...) - if err != nil { - return err - } - return c.sendMsg(stat) -} - -// SimpleServiceCheck sends an serviceCheck with the provided name and status. -func (c *Client) SimpleServiceCheck(name string, status ServiceCheckStatus) error { - sc := NewServiceCheck(name, status) - return c.ServiceCheck(sc) -} - -// Close the client connection. -func (c *Client) Close() error { - if c == nil { - return nil - } - select { - case c.stop <- struct{}{}: - default: - } - - // if this client is buffered, flush before closing the writer - if c.bufferLength > 0 { - if err := c.Flush(); err != nil { - return err - } - } - - return c.writer.Close() -} - -// Events support -// EventAlertType and EventAlertPriority became exported types after this issue was submitted: https://github.com/DataDog/datadog-go/issues/41 -// The reason why they got exported is so that client code can directly use the types. - -// EventAlertType is the alert type for events -type EventAlertType string - -const ( - // Info is the "info" AlertType for events - Info EventAlertType = "info" - // Error is the "error" AlertType for events - Error EventAlertType = "error" - // Warning is the "warning" AlertType for events - Warning EventAlertType = "warning" - // Success is the "success" AlertType for events - Success EventAlertType = "success" -) - -// EventPriority is the event priority for events -type EventPriority string - -const ( - // Normal is the "normal" Priority for events - Normal EventPriority = "normal" - // Low is the "low" Priority for events - Low EventPriority = "low" -) - -// An Event is an object that can be posted to your DataDog event stream. -type Event struct { - // Title of the event. Required. - Title string - // Text is the description of the event. Required. - Text string - // Timestamp is a timestamp for the event. If not provided, the dogstatsd - // server will set this to the current time. - Timestamp time.Time - // Hostname for the event. - Hostname string - // AggregationKey groups this event with others of the same key. - AggregationKey string - // Priority of the event. Can be statsd.Low or statsd.Normal. - Priority EventPriority - // SourceTypeName is a source type for the event. - SourceTypeName string - // AlertType can be statsd.Info, statsd.Error, statsd.Warning, or statsd.Success. - // If absent, the default value applied by the dogstatsd server is Info. - AlertType EventAlertType - // Tags for the event. - Tags []string -} - -// NewEvent creates a new event with the given title and text. Error checking -// against these values is done at send-time, or upon running e.Check. -func NewEvent(title, text string) *Event { - return &Event{ - Title: title, - Text: text, - } -} - -// Check verifies that an event is valid. -func (e Event) Check() error { - if len(e.Title) == 0 { - return fmt.Errorf("statsd.Event title is required") - } - if len(e.Text) == 0 { - return fmt.Errorf("statsd.Event text is required") - } - return nil -} - -// Encode returns the dogstatsd wire protocol representation for an event. -// Tags may be passed which will be added to the encoded output but not to -// the Event's list of tags, eg. for default tags. -func (e Event) Encode(tags ...string) (string, error) { - err := e.Check() - if err != nil { - return "", err - } - text := e.escapedText() - - var buffer bytes.Buffer - buffer.WriteString("_e{") - buffer.WriteString(strconv.FormatInt(int64(len(e.Title)), 10)) - buffer.WriteRune(',') - buffer.WriteString(strconv.FormatInt(int64(len(text)), 10)) - buffer.WriteString("}:") - buffer.WriteString(e.Title) - buffer.WriteRune('|') - buffer.WriteString(text) - - if !e.Timestamp.IsZero() { - buffer.WriteString("|d:") - buffer.WriteString(strconv.FormatInt(int64(e.Timestamp.Unix()), 10)) - } - - if len(e.Hostname) != 0 { - buffer.WriteString("|h:") - buffer.WriteString(e.Hostname) - } - - if len(e.AggregationKey) != 0 { - buffer.WriteString("|k:") - buffer.WriteString(e.AggregationKey) - - } - - if len(e.Priority) != 0 { - buffer.WriteString("|p:") - buffer.WriteString(string(e.Priority)) - } - - if len(e.SourceTypeName) != 0 { - buffer.WriteString("|s:") - buffer.WriteString(e.SourceTypeName) - } - - if len(e.AlertType) != 0 { - buffer.WriteString("|t:") - buffer.WriteString(string(e.AlertType)) - } - - writeTagString(&buffer, tags, e.Tags) - - return buffer.String(), nil -} - -// ServiceCheckStatus support -type ServiceCheckStatus byte - -const ( - // Ok is the "ok" ServiceCheck status - Ok ServiceCheckStatus = 0 - // Warn is the "warning" ServiceCheck status - Warn ServiceCheckStatus = 1 - // Critical is the "critical" ServiceCheck status - Critical ServiceCheckStatus = 2 - // Unknown is the "unknown" ServiceCheck status - Unknown ServiceCheckStatus = 3 -) - -// An ServiceCheck is an object that contains status of DataDog service check. -type ServiceCheck struct { - // Name of the service check. Required. - Name string - // Status of service check. Required. - Status ServiceCheckStatus - // Timestamp is a timestamp for the serviceCheck. If not provided, the dogstatsd - // server will set this to the current time. - Timestamp time.Time - // Hostname for the serviceCheck. - Hostname string - // A message describing the current state of the serviceCheck. - Message string - // Tags for the serviceCheck. - Tags []string -} - -// NewServiceCheck creates a new serviceCheck with the given name and status. Error checking -// against these values is done at send-time, or upon running sc.Check. -func NewServiceCheck(name string, status ServiceCheckStatus) *ServiceCheck { - return &ServiceCheck{ - Name: name, - Status: status, - } -} - -// Check verifies that an event is valid. -func (sc ServiceCheck) Check() error { - if len(sc.Name) == 0 { - return fmt.Errorf("statsd.ServiceCheck name is required") - } - if byte(sc.Status) < 0 || byte(sc.Status) > 3 { - return fmt.Errorf("statsd.ServiceCheck status has invalid value") - } - return nil -} - -// Encode returns the dogstatsd wire protocol representation for an serviceCheck. -// Tags may be passed which will be added to the encoded output but not to -// the Event's list of tags, eg. for default tags. -func (sc ServiceCheck) Encode(tags ...string) (string, error) { - err := sc.Check() - if err != nil { - return "", err - } - message := sc.escapedMessage() - - var buffer bytes.Buffer - buffer.WriteString("_sc|") - buffer.WriteString(sc.Name) - buffer.WriteRune('|') - buffer.WriteString(strconv.FormatInt(int64(sc.Status), 10)) - - if !sc.Timestamp.IsZero() { - buffer.WriteString("|d:") - buffer.WriteString(strconv.FormatInt(int64(sc.Timestamp.Unix()), 10)) - } - - if len(sc.Hostname) != 0 { - buffer.WriteString("|h:") - buffer.WriteString(sc.Hostname) - } - - writeTagString(&buffer, tags, sc.Tags) - - if len(message) != 0 { - buffer.WriteString("|m:") - buffer.WriteString(message) - } - - return buffer.String(), nil -} - -func (e Event) escapedText() string { - return strings.Replace(e.Text, "\n", "\\n", -1) -} - -func (sc ServiceCheck) escapedMessage() string { - msg := strings.Replace(sc.Message, "\n", "\\n", -1) - return strings.Replace(msg, "m:", `m\:`, -1) -} - -func removeNewlines(str string) string { - return strings.Replace(str, "\n", "", -1) -} - -func writeTagString(w io.Writer, tagList1, tagList2 []string) { - // the tag lists may be shared with other callers, so we cannot modify - // them in any way (which means we cannot append to them either) - // therefore we must make an entirely separate copy just for this call - totalLen := len(tagList1) + len(tagList2) - if totalLen == 0 { - return - } - tags := make([]string, 0, totalLen) - tags = append(tags, tagList1...) - tags = append(tags, tagList2...) - - io.WriteString(w, "|#") - io.WriteString(w, removeNewlines(tags[0])) - for _, tag := range tags[1:] { - io.WriteString(w, ",") - io.WriteString(w, removeNewlines(tag)) - } -} diff --git a/vendor/github.com/DataDog/datadog-go/statsd/udp.go b/vendor/github.com/DataDog/datadog-go/statsd/udp.go deleted file mode 100644 index 8af522c5bb4..00000000000 --- a/vendor/github.com/DataDog/datadog-go/statsd/udp.go +++ /dev/null @@ -1,40 +0,0 @@ -package statsd - -import ( - "errors" - "net" - "time" -) - -// udpWriter is an internal class wrapping around management of UDP connection -type udpWriter struct { - conn net.Conn -} - -// New returns a pointer to a new udpWriter given an addr in the format "hostname:port". -func newUDPWriter(addr string) (*udpWriter, error) { - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - conn, err := net.DialUDP("udp", nil, udpAddr) - if err != nil { - return nil, err - } - writer := &udpWriter{conn: conn} - return writer, nil -} - -// SetWriteTimeout is not needed for UDP, returns error -func (w *udpWriter) SetWriteTimeout(d time.Duration) error { - return errors.New("SetWriteTimeout: not supported for UDP connections") -} - -// Write data to the UDP connection with no error handling -func (w *udpWriter) Write(data []byte) (int, error) { - return w.conn.Write(data) -} - -func (w *udpWriter) Close() error { - return w.conn.Close() -} diff --git a/vendor/github.com/DataDog/datadog-go/statsd/uds.go b/vendor/github.com/DataDog/datadog-go/statsd/uds.go deleted file mode 100644 index 31154ab4dd9..00000000000 --- a/vendor/github.com/DataDog/datadog-go/statsd/uds.go +++ /dev/null @@ -1,67 +0,0 @@ -package statsd - -import ( - "net" - "time" -) - -/* -UDSTimeout holds the default timeout for UDS socket writes, as they can get -blocking when the receiving buffer is full. -*/ -const defaultUDSTimeout = 1 * time.Millisecond - -// udsWriter is an internal class wrapping around management of UDS connection -type udsWriter struct { - // Address to send metrics to, needed to allow reconnection on error - addr net.Addr - // Established connection object, or nil if not connected yet - conn net.Conn - // write timeout - writeTimeout time.Duration -} - -// New returns a pointer to a new udsWriter given a socket file path as addr. -func newUdsWriter(addr string) (*udsWriter, error) { - udsAddr, err := net.ResolveUnixAddr("unixgram", addr) - if err != nil { - return nil, err - } - // Defer connection to first Write - writer := &udsWriter{addr: udsAddr, conn: nil, writeTimeout: defaultUDSTimeout} - return writer, nil -} - -// SetWriteTimeout allows the user to set a custom write timeout -func (w *udsWriter) SetWriteTimeout(d time.Duration) error { - w.writeTimeout = d - return nil -} - -// Write data to the UDS connection with write timeout and minimal error handling: -// create the connection if nil, and destroy it if the statsd server has disconnected -func (w *udsWriter) Write(data []byte) (int, error) { - // Try connecting (first packet or connection lost) - if w.conn == nil { - conn, err := net.Dial(w.addr.Network(), w.addr.String()) - if err != nil { - return 0, err - } - w.conn = conn - } - w.conn.SetWriteDeadline(time.Now().Add(w.writeTimeout)) - n, e := w.conn.Write(data) - if e != nil { - // Statsd server disconnected, retry connecting at next packet - w.conn = nil - return 0, e - } - return n, e -} - -func (w *udsWriter) Close() error { - if w.conn != nil { - return w.conn.Close() - } - return nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index eb42314afee..d914299f3ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,9 +7,6 @@ buf.build/gen/go/prometheus/prometheus/protocolbuffers/go # github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 ## explicit github.com/Azure/go-ntlmssp -# github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a -## explicit -github.com/DataDog/datadog-go/statsd # github.com/PuerkitoBio/goquery v1.9.2 ## explicit; go 1.18 github.com/PuerkitoBio/goquery