func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsTiming, 1)
	if c.aggHistDist != nil {
		return c.agg.timing(name, value, tags)
	}
	return c.send(metric{metricType: timing, name: name, fvalue: value, tags: tags, rate: 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)
package statsd
import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)
const OptimalUDPPayloadSize = 1432
const MaxUDPPayloadSize = 65467
const DefaultUDPBufferPoolSize = 2048
const DefaultUDSBufferPoolSize = 512
const DefaultMaxAgentPayloadSize = 8192
const UnixAddressPrefix = "unix://"
const WindowsPipeAddressPrefix = `\\.\pipe\`
const (
	agentHostEnvVarName = "DD_AGENT_HOST"
	agentPortEnvVarName = "DD_DOGSTATSD_PORT"
	defaultUDPPort      = "8125"
)
var ddEnvTagsMapping = map[string]string{
	"DD_ENTITY_ID": "dd.internal.entity_id",
	"DD_ENV": "env",
	"DD_SERVICE": "service",
	"DD_VERSION": "version",
}
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"
)
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
}
type noClientErr string
const ErrNoClient = noClientErr("statsd client is nil")
func (e noClientErr) Error() string {
	return string(e)
}
type ClientInterface interface {
	Gauge(name string, value float64, tags []string, rate float64) error
	Count(name string, value int64, tags []string, rate float64) error
	Histogram(name string, value float64, tags []string, rate float64) error
	Distribution(name string, value float64, tags []string, rate float64) error
	Decr(name string, tags []string, rate float64) error
	Incr(name string, tags []string, rate float64) error
	Set(name string, value string, tags []string, rate float64) error
	Timing(name string, value time.Duration, tags []string, rate float64) error
	TimeInMilliseconds(name string, value float64, tags []string, rate float64) error
	Event(e *Event) error
	SimpleEvent(title, text string) error
	ServiceCheck(sc *ServiceCheck) error
	SimpleServiceCheck(name string, status ServiceCheckStatus) error
	Close() error
	Flush() error
	SetWriteTimeout(d time.Duration) error
}
type Client struct {
	sender *sender
	Namespace string
	Tags []string
	SkipErrors  bool
	flushTime   time.Duration
	metrics     *ClientMetrics
	telemetry   *telemetryClient
	stop        chan struct{}
	wg          sync.WaitGroup
	workers     []*worker
	closerLock  sync.Mutex
	receiveMode ReceivingMode
	agg         *aggregator
	aggHistDist *aggregator
	options     []Option
	addrOption  string
}
type ClientMetrics struct {
	TotalMetrics             uint64
	TotalMetricsGauge        uint64
	TotalMetricsCount        uint64
	TotalMetricsHistogram    uint64
	TotalMetricsDistribution uint64
	TotalMetricsSet          uint64
	TotalMetricsTiming       uint64
	TotalEvents              uint64
	TotalServiceChecks       uint64
	TotalDroppedOnReceive    uint64
}
var _ ClientInterface = &Client{}
func resolveAddr(addr string) string {
	envPort := ""
	if addr == "" {
		addr = os.Getenv(agentHostEnvVarName)
		envPort = os.Getenv(agentPortEnvVarName)
	}
	if addr == "" {
		return ""
	}
	if !strings.HasPrefix(addr, WindowsPipeAddressPrefix) && !strings.HasPrefix(addr, UnixAddressPrefix) {
		if !strings.Contains(addr, ":") {
			if envPort != "" {
				addr = fmt.Sprintf("%s:%s", addr, envPort)
			} else {
				addr = fmt.Sprintf("%s:%s", addr, defaultUDPPort)
			}
		}
	}
	return addr
}
func createWriter(addr string) (statsdWriter, string, error) {
	addr = resolveAddr(addr)
	if addr == "" {
		return nil, "", errors.New("No address passed and autodetection from environment failed")
	}
	switch {
	case strings.HasPrefix(addr, WindowsPipeAddressPrefix):
		w, err := newWindowsPipeWriter(addr)
		return w, WriterWindowsPipe, err
	case strings.HasPrefix(addr, UnixAddressPrefix):
		w, err := newUDSWriter(addr[len(UnixAddressPrefix):])
		return w, WriterNameUDS, err
	default:
		w, err := newUDPWriter(addr)
		return w, WriterNameUDP, err
	}
}
func New(addr string, options ...Option) (*Client, error) {
	o, err := resolveOptions(options)
	if err != nil {
		return nil, err
	}
	w, writerType, err := createWriter(addr)
	if err != nil {
		return nil, err
	}
	client, err := newWithWriter(w, o, writerType)
	if err == nil {
		client.options = append(client.options, options...)
		client.addrOption = addr
	}
	return client, err
}
func NewWithWriter(w statsdWriter, options ...Option) (*Client, error) {
	o, err := resolveOptions(options)
	if err != nil {
		return nil, err
	}
	return newWithWriter(w, o, "custom")
}
func CloneWithExtraOptions(c *Client, options ...Option) (*Client, error) {
	if c == nil {
		return nil, ErrNoClient
	}
	if c.addrOption == "" {
		return nil, fmt.Errorf("can't clone client with no addrOption")
	}
	opt := append(c.options, options...)
	return New(c.addrOption, opt...)
}
func newWithWriter(w statsdWriter, o *Options, writerName string) (*Client, error) {
	w.SetWriteTimeout(o.WriteTimeoutUDS)
	c := Client{
		Namespace: o.Namespace,
		Tags:      o.Tags,
		metrics:   &ClientMetrics{},
	}
	if o.Aggregation || o.ExtendedAggregation {
		c.agg = newAggregator(&c)
		c.agg.start(o.AggregationFlushInterval)
		if o.ExtendedAggregation {
			c.aggHistDist = c.agg
		}
	}
	for envName, tagName := range ddEnvTagsMapping {
		if value := os.Getenv(envName); value != "" {
			c.Tags = append(c.Tags, fmt.Sprintf("%s:%s", tagName, value))
		}
	}
	if o.MaxBytesPerPayload == 0 {
		if writerName == WriterNameUDS {
			o.MaxBytesPerPayload = DefaultMaxAgentPayloadSize
		} else {
			o.MaxBytesPerPayload = OptimalUDPPayloadSize
		}
	}
	if o.BufferPoolSize == 0 {
		if writerName == WriterNameUDS {
			o.BufferPoolSize = DefaultUDSBufferPoolSize
		} else {
			o.BufferPoolSize = DefaultUDPBufferPoolSize
		}
	}
	if o.SenderQueueSize == 0 {
		if writerName == WriterNameUDS {
			o.SenderQueueSize = DefaultUDSBufferPoolSize
		} else {
			o.SenderQueueSize = DefaultUDPBufferPoolSize
		}
	}
	bufferPool := newBufferPool(o.BufferPoolSize, o.MaxBytesPerPayload, o.MaxMessagesPerPayload)
	c.sender = newSender(w, o.SenderQueueSize, bufferPool)
	c.receiveMode = o.ReceiveMode
	for i := 0; i < o.BufferShardCount; i++ {
		w := newWorker(bufferPool, c.sender)
		c.workers = append(c.workers, w)
		if c.receiveMode == ChannelMode {
			w.startReceivingMetric(o.ChannelModeBufferSize)
		}
	}
	c.flushTime = o.BufferFlushInterval
	c.stop = make(chan struct{}, 1)
	c.wg.Add(1)
	go func() {
		defer c.wg.Done()
		c.watch()
	}()
	if o.Telemetry {
		if o.TelemetryAddr == "" {
			c.telemetry = newTelemetryClient(&c, writerName, o.DevMode)
		} else {
			var err error
			c.telemetry, err = newTelemetryClientWithCustomAddr(&c, writerName, o.DevMode, o.TelemetryAddr, bufferPool)
			if err != nil {
				return nil, err
			}
		}
		c.telemetry.run(&c.wg, c.stop)
	}
	return &c, nil
}
func NewBuffered(addr string, buflen int) (*Client, error) {
	return New(addr, WithMaxMessagesPerPayload(buflen))
}
func (c *Client) SetWriteTimeout(d time.Duration) error {
	if c == nil {
		return ErrNoClient
	}
	return c.sender.transport.SetWriteTimeout(d)
}
func (c *Client) watch() {
	ticker := time.NewTicker(c.flushTime)
	for {
		select {
		case <-ticker.C:
			for _, w := range c.workers {
				w.flush()
			}
		case <-c.stop:
			ticker.Stop()
			return
		}
	}
}
func (c *Client) Flush() error {
	if c == nil {
		return ErrNoClient
	}
	if c.agg != nil {
		c.agg.sendMetrics()
	}
	for _, w := range c.workers {
		w.pause()
		defer w.unpause()
		w.flushUnsafe()
	}
	c.sender.flush()
	return nil
}
func (c *Client) FlushTelemetryMetrics() ClientMetrics {
	cm := ClientMetrics{
		TotalMetricsGauge:        atomic.SwapUint64(&c.metrics.TotalMetricsGauge, 0),
		TotalMetricsCount:        atomic.SwapUint64(&c.metrics.TotalMetricsCount, 0),
		TotalMetricsSet:          atomic.SwapUint64(&c.metrics.TotalMetricsSet, 0),
		TotalMetricsHistogram:    atomic.SwapUint64(&c.metrics.TotalMetricsHistogram, 0),
		TotalMetricsDistribution: atomic.SwapUint64(&c.metrics.TotalMetricsDistribution, 0),
		TotalMetricsTiming:       atomic.SwapUint64(&c.metrics.TotalMetricsTiming, 0),
		TotalEvents:              atomic.SwapUint64(&c.metrics.TotalEvents, 0),
		TotalServiceChecks:       atomic.SwapUint64(&c.metrics.TotalServiceChecks, 0),
		TotalDroppedOnReceive:    atomic.SwapUint64(&c.metrics.TotalDroppedOnReceive, 0),
	}
	cm.TotalMetrics = cm.TotalMetricsGauge + cm.TotalMetricsCount +
		cm.TotalMetricsSet + cm.TotalMetricsHistogram +
		cm.TotalMetricsDistribution + cm.TotalMetricsTiming
	return cm
}
func (c *Client) send(m metric) error {
	if c == nil {
		return ErrNoClient
	}
	m.globalTags = c.Tags
	m.namespace = c.Namespace
	h := hashString32(m.name)
	worker := c.workers[h%uint32(len(c.workers))]
	if c.receiveMode == ChannelMode {
		select {
		case worker.inputMetrics <- m:
		default:
			atomic.AddUint64(&c.metrics.TotalDroppedOnReceive, 1)
		}
		return nil
	}
	return worker.processMetric(m)
}
func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsGauge, 1)
	if c.agg != nil {
		return c.agg.gauge(name, value, tags)
	}
	return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate})
}
func (c *Client) Count(name string, value int64, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsCount, 1)
	if c.agg != nil {
		return c.agg.count(name, value, tags)
	}
	return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate})
}
func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsHistogram, 1)
	if c.aggHistDist != nil {
		return c.agg.histogram(name, value, tags)
	}
	return c.send(metric{metricType: histogram, name: name, fvalue: value, tags: tags, rate: rate})
}
func (c *Client) Distribution(name string, value float64, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsDistribution, 1)
	if c.aggHistDist != nil {
		return c.agg.distribution(name, value, tags)
	}
	return c.send(metric{metricType: distribution, name: name, fvalue: value, tags: tags, rate: rate})
}
func (c *Client) Decr(name string, tags []string, rate float64) error {
	return c.Count(name, -1, tags, rate)
}
func (c *Client) Incr(name string, tags []string, rate float64) error {
	return c.Count(name, 1, tags, rate)
}
func (c *Client) Set(name string, value string, tags []string, rate float64) error {
	if c == nil {
		return ErrNoClient
	}
	atomic.AddUint64(&c.metrics.TotalMetricsSet, 1)
	if c.agg != nil {
		return c.agg.set(name, value, tags)
	}
	return c.send(metric{metricType: set, name: name, svalue: value, tags: tags, rate: rate})
}
func (c *Client) Timing(name string, value time.Duration, tags []string, rate float64) error {
	return c.TimeInMilliseconds(name, value.Seconds()*1000, tags, rate)
}