<commit_before>// 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.

statsd is based on go-statsd-client.
*/
package statsd

//go:generate mockgen -source=statsd.go -destination=mocks/statsd.go

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

/*
OptimalUDPPayloadSize 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.
*/
const OptimalUDPPayloadSize = 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

// DefaultUDPBufferPoolSize is the default size of the buffer pool for UDP clients.
const DefaultUDPBufferPoolSize = 2048

// DefaultUDSBufferPoolSize is the default size of the buffer pool for UDS clients.
const DefaultUDSBufferPoolSize = 512

/*
DefaultMaxAgentPayloadSize is the default maximum payload size the agent
can receive. This can be adjusted by changing dogstatsd_buffer_size in the
agent configuration file datadog.yaml. This is also used as the optimal payload size
for UDS datagrams.
*/
const DefaultMaxAgentPayloadSize = 8192

/*
UnixAddressPrefix holds the prefix to use to enable Unix Domain Socket
traffic instead of UDP.
*/
const UnixAddressPrefix = "unix://"

/*
WindowsPipeAddressPrefix holds the prefix to use to enable Windows Named Pipes
traffic instead of UDP.
*/
const WindowsPipeAddressPrefix = `\\.\pipe\`

const (
	agentHostEnvVarName = "DD_AGENT_HOST"
	agentPortEnvVarName = "DD_DOGSTATSD_PORT"
	defaultUDPPort      = "8125"
)

const (
	// ddEntityID specifies client-side user-specified entity ID injection.
	// This env var can be set to the Pod UID on Kubernetes via the downward API.
	// Docs: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp
	ddEntityID = "DD_ENTITY_ID"

	// ddEntityIDTag specifies the tag name for the client-side entity ID injection
	// The Agent expects this tag to contain a non-prefixed Kubernetes Pod UID.
	ddEntityIDTag = "dd.internal.entity_id"

	// originDetectionEnabled specifies the env var to enable/disable sending the container ID field.
	originDetectionEnabled = "DD_ORIGIN_DETECTION_ENABLED"
)

/*
ddEnvTagsMapping is a mapping of each "DD_" prefixed environment variable
to a specific tag name. We use a slice to keep the order and simplify tests.
*/
var ddEnvTagsMapping = []struct{ envName, tagName string }{
	{ddEntityID, ddEntityIDTag}, // Client-side entity ID injection for container tagging.
	{"DD_ENV", "env"},           // The name of the env in which the service runs.
	{"DD_SERVICE", "service"},   // The name of the running service.
	{"DD_VERSION", "version"},   // The current version of the running service.
}

type metricType int

const (
	gauge metricType = iota
	count
	histogram
	histogramAggregated
	distribution
	distributionAggregated
	set
	timing
	timingAggregated
	event
	serviceCheck
)

type receivingMode int

const (
	mutexMode receivingMode = iota
	channelMode
)

const (
	writerNameUDP     string = "udp"
	writerNameUDS     string = "uds"
	writerWindowsPipe string = "pipe"
)

// noTimestamp is used as a value for metric without a given timestamp.
const noTimestamp = int64(0)

type metric struct {
	metricType metricType
	namespace  string
	globalTags []string
	name       string
	fvalue     float64
	fvalues    []float64
	ivalue     int64
	svalue     string
	evalue     *Event
	scvalue    *ServiceCheck
	tags       []string
	stags      string
	rate       float64
	timestamp  int64
}

type noClientErr string

// ErrNoClient is returned if statsd reporting methods are invoked on
// a nil client.
const ErrNoClient = noClientErr("statsd client is nil")

func (e noClientErr) Error() string {
	return string(e)
}

// ClientInterface is an interface that exposes the common client functions for the
// purpose of being able to provide a no-op client or even mocking. This can aid
// downstream users' with their testing.
type ClientInterface interface {
	// Gauge measures the value of a metric at a particular time.
	Gauge(name string, value float64, tags []string, rate float64) error

	// GaugeWithTimestamp measures the value of a metric at a given time.
	// The value will bypass any aggregation on the client side and agent side.
	// This is useful when sending points in the past.
	GaugeWithTimestamp(name string, value float64, tags []string, rate float64, timestamp time.Time) error

	// Count tracks how many times something happened per second.
	Count(name string, value int64, tags []string, rate float64) error

	// CountWithTimestamp tracks how many times something happened at the given second.
	// The value will bypass any aggregation on the client side and agent side.
	// This is useful when sending points in the past.
	CountWithTimestamp(name string, value int64, tags []string, rate float64, timestamp time.Time) error

	// Histogram tracks the statistical distribution of a set of values on each host.
	Histogram(name string, value float64, tags []string, rate float64) error

	// Distribution tracks the statistical distribution of a set of values across your infrastructure.
	Distribution(name string, value float64, tags []string, rate float64) error<commit_msg>statsd: add minimum Datadog Agent version for feature support.<commit_after>// 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.

statsd is based on go-statsd-client.
*/
package statsd

//go:generate mockgen -source=statsd.go -destination=mocks/statsd.go

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

/*
OptimalUDPPayloadSize 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.
*/
const OptimalUDPPayloadSize = 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

// DefaultUDPBufferPoolSize is the default size of the buffer pool for UDP clients.
const DefaultUDPBufferPoolSize = 2048

// DefaultUDSBufferPoolSize is the default size of the buffer pool for UDS clients.
const DefaultUDSBufferPoolSize = 512

/*
DefaultMaxAgentPayloadSize is the default maximum payload size the agent
can receive. This can be adjusted by changing dogstatsd_buffer_size in the
agent configuration file datadog.yaml. This is also used as the optimal payload size
for UDS datagrams.
*/
const DefaultMaxAgentPayloadSize = 8192

/*
UnixAddressPrefix holds the prefix to use to enable Unix Domain Socket
traffic instead of UDP.
*/
const UnixAddressPrefix = "unix://"

/*
WindowsPipeAddressPrefix holds the prefix to use to enable Windows Named Pipes
traffic instead of UDP.
*/
const WindowsPipeAddressPrefix = `\\.\pipe\`

const (
	agentHostEnvVarName = "DD_AGENT_HOST"
	agentPortEnvVarName = "DD_DOGSTATSD_PORT"
	defaultUDPPort      = "8125"
)

const (
	// ddEntityID specifies client-side user-specified entity ID injection.
	// This env var can be set to the Pod UID on Kubernetes via the downward API.
	// Docs: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp
	ddEntityID = "DD_ENTITY_ID"

	// ddEntityIDTag specifies the tag name for the client-side entity ID injection
	// The Agent expects this tag to contain a non-prefixed Kubernetes Pod UID.
	ddEntityIDTag = "dd.internal.entity_id"

	// originDetectionEnabled specifies the env var to enable/disable sending the container ID field.
	originDetectionEnabled = "DD_ORIGIN_DETECTION_ENABLED"
)

/*
ddEnvTagsMapping is a mapping of each "DD_" prefixed environment variable
to a specific tag name. We use a slice to keep the order and simplify tests.
*/
var ddEnvTagsMapping = []struct{ envName, tagName string }{
	{ddEntityID, ddEntityIDTag}, // Client-side entity ID injection for container tagging.
	{"DD_ENV", "env"},           // The name of the env in which the service runs.
	{"DD_SERVICE", "service"},   // The name of the running service.
	{"DD_VERSION", "version"},   // The current version of the running service.
}

type metricType int

const (
	gauge metricType = iota
	count
	histogram
	histogramAggregated
	distribution
	distributionAggregated
	set
	timing
	timingAggregated
	event
	serviceCheck
)

type receivingMode int

const (
	mutexMode receivingMode = iota
	channelMode
)

const (
	writerNameUDP     string = "udp"
	writerNameUDS     string = "uds"
	writerWindowsPipe string = "pipe"
)

// noTimestamp is used as a value for metric without a given timestamp.
const noTimestamp = int64(0)

type metric struct {
	metricType metricType
	namespace  string
	globalTags []string
	name       string
	fvalue     float64
	fvalues    []float64
	ivalue     int64
	svalue     string
	evalue     *Event
	scvalue    *ServiceCheck
	tags       []string
	stags      string
	rate       float64
	timestamp  int64
}

type noClientErr string

// ErrNoClient is returned if statsd reporting methods are invoked on
// a nil client.
const ErrNoClient = noClientErr("statsd client is nil")

func (e noClientErr) Error() string {
	return string(e)
}

// ClientInterface is an interface that exposes the common client functions for the
// purpose of being able to provide a no-op client or even mocking. This can aid
// downstream users' with their testing.
type ClientInterface interface {
	// Gauge measures the value of a metric at a particular time.
	Gauge(name string, value float64, tags []string, rate float64) error

	// GaugeWithTimestamp measures the value of a metric at a given time.
	// The value will bypass any aggregation on the client side and agent side.
	// This is useful when sending points in the past.
	// Minimum Datadog Agent version: 7.39.0
	GaugeWithTimestamp(name string, value float64, tags []string, rate float64, timestamp time.Time) error

	// Count tracks how many times something happened per second.
	Count(name string, value int64, tags []string, rate float64) error

	// CountWithTimestamp tracks how many times something happened at the given second.
	// The value will bypass any aggregation on the client side and agent side.
	// This is useful when sending points in the past.
	// Minimum Datadog Agent version: 7.39.0
	CountWithTimestamp(name string, value int64, tags []string, rate float64, timestamp time.Time) error

	// Histogram tracks the statistical distribution of a set of values on each host.
	Histogram(name string, value float64, tags []string, rate float64) error

	// Distribution tracks the statistical distribution of a set of values across your infrastructure.
	Distribution(name string, value float64, tags []string, rate float64) error
{'fraction_non_alphanumeric': 0.06503646503646504, 'fraction_numerical': 0.014586014586014585, 'mean_word_length': 4.067826086956522, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '573367', 'n_tokens_mistral': 3509, 'n_tokens_neox': 3300, 'n_words': 1441}