Full Code of quipo/statsd for AI

master 3d6a5565f314 cached
33 files
107.1 KB
32.4k tokens
306 symbols
1 requests
Download .txt
Repository: quipo/statsd
Branch: master
Commit: 3d6a5565f314
Files: 33
Total size: 107.1 KB

Directory structure:
gitextract_x5826k8v/

├── .travis.yml
├── LICENSE
├── README.md
├── bufferedclient.go
├── bufferedclient_test.go
├── client.go
├── client_test.go
├── event/
│   ├── absolute.go
│   ├── absolute_test.go
│   ├── fabsolute.go
│   ├── fabsolute_test.go
│   ├── fgauge.go
│   ├── fgauge_test.go
│   ├── fgaugedelta.go
│   ├── fgaugedelta_test.go
│   ├── gauge.go
│   ├── gauge_test.go
│   ├── gaugedelta.go
│   ├── gaugedelta_test.go
│   ├── increment.go
│   ├── increment_test.go
│   ├── interface.go
│   ├── precisiontiming.go
│   ├── precisiontiming_test.go
│   ├── timing.go
│   ├── timing_test.go
│   ├── total.go
│   └── total_test.go
├── interface.go
├── mock/
│   ├── mockableclient.go
│   └── mockableclient_test.go
├── noopclient.go
└── stdoutclient.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .travis.yml
================================================
language: go

go:
  - 1.9.x
  - master


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 Lorenzo Alberton

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.

================================================
FILE: README.md
================================================
# StatsD client (Golang)

[![Build Status](https://travis-ci.org/quipo/statsd.png?branch=master)](https://travis-ci.org/quipo/statsd) 
[![GoDoc](https://godoc.org/github.com/quipo/statsd?status.png)](http://godoc.org/github.com/quipo/statsd)

## Introduction

Go Client library for [StatsD](https://github.com/etsy/statsd/). Contains a direct and a buffered client.
The buffered version will hold and aggregate values for the same key in memory before flushing them at the defined frequency.

This client library was inspired by the one embedded in the [Bit.ly NSQ](https://github.com/bitly/nsq/blob/master/util/statsd_client.go) project, and extended to support some extra custom events used at DataSift.

## Installation

    go get github.com/quipo/statsd

## Supported event types

* `Increment` - Count occurrences per second/minute of a specific event
* `Decrement` - Count occurrences per second/minute of a specific event
* `Timing` - To track a duration event
* `PrecisionTiming` - To track a duration event
* `Gauge` (int) / `FGauge` (float) - Gauges are a constant data type. They are not subject to averaging, and they don’t change unless you change them. That is, once you set a gauge value, it will be a flat line on the graph until you change it again
* `GaugeDelta` (int) / `FGaugeDelta` (float) - Same as above, but as a delta change to the previous value rather than a new absolute value
* `Absolute` (int) / `FAbsolute` (float) - Absolute-valued metric (not averaged/aggregated)
* `Total` - Continously increasing value, e.g. read operations since boot


## Sample usage

```go
package main

import (
	"log"
	"os"
	"time"

	"github.com/quipo/statsd"
)

func main() {
	// init
	prefix := "myproject."
	statsdclient := statsd.NewStatsdClient("localhost:8125", prefix)
	err := statsdclient.CreateSocket()
	if nil != err {
		log.Println(err)
		os.Exit(1)
	}
	interval := time.Second * 2 // aggregate stats and flush every 2 seconds
	stats := statsd.NewStatsdBuffer(interval, statsdclient)
	defer stats.Close()

	// not buffered: send immediately
	statsdclient.Incr("mymetric", 4)

	// buffered: aggregate in memory before flushing
	stats.Incr("mymetric", 1)
	stats.Incr("mymetric", 3)
	stats.Incr("mymetric", 1)
	stats.Incr("mymetric", 1)
}
```

The string `%HOST%` in the metric name will automatically be replaced with the hostname of the server the event is sent from.


## [Changelog](https://github.com/quipo/statsd/releases)

* `HEAD`:

    *

* [`v.1.4.0`](https://github.com/quipo/statsd/releases/tag/1.4.0)

    * Fixed behaviour of Gauge with positive numbers: the previous behaviour was the same as GaugeDelta
      (FGauge already had the correct behaviour)
    * Added more tests
    * Small optimisation: replace string formatting with concatenation (thanks to @agnivade)

* [`v.1.3.0`](https://github.com/quipo/statsd/releases/tag/v.1.3.0):

    * Added stdout client ("echo" service for debugging)
    * Fixed [issue #23](https://github.com/quipo/statsd/issues/23): GaugeDelta event Stats() should not send an absolute value of 0
    * Fixed FGauge's collation in the buffered client to only preserve the last value in the batch (it mistakenly had the same implementation of FGaugeDelta's collation)
    * Fixed FGaugeDelta with negative value not to send a 0 value first (it mistakenly had the same implementation of FGauge)
    * Added many tests
    * Added compile-time checks that the default events implement the Event interface

* [`v.1.2.0`](https://github.com/quipo/statsd/releases/tag/1.2.0): Sample rate support (thanks to [Hongjian Zhu](https://github.com/hongjianzhu))
*  [`v.1.1.0`](https://github.com/quipo/statsd/releases/tag/1.1.0):

    * Added `SendEvents` function to `Statsd` interface;
    * Using interface in buffered client constructor;
    * Added/Fixed tests

* [`v.1.0.0`](https://github.com/quipo/statsd/releases/tag/1.0.0): First stable release
* `v.0.0.9`: Added memoization to reduce memory allocations
* `v.0.0.8`: Pre-release

## Author

Lorenzo Alberton

* Web: [http://alberton.info](http://alberton.info)
* Twitter: [@lorenzoalberton](https://twitter.com/lorenzoalberton)
* Linkedin: [/in/lorenzoalberton](https://www.linkedin.com/in/lorenzoalberton)


## Copyright

See [LICENSE](LICENSE) document


================================================
FILE: bufferedclient.go
================================================
package statsd

import (
	"log"
	"os"
	"time"

	"github.com/quipo/statsd/event"
)

// request to close the buffered statsd collector
type closeRequest struct {
	reply chan error
}

// StatsdBuffer is a client library to aggregate events in memory before
// flushing aggregates to StatsD, useful if the frequency of events is extremely high
// and sampling is not desirable
type StatsdBuffer struct {
	statsd        Statsd
	flushInterval time.Duration
	eventChannel  chan event.Event
	events        map[string]event.Event
	closeChannel  chan closeRequest
	Logger        Logger
	Verbose       bool
}

// NewStatsdBuffer Factory
func NewStatsdBuffer(interval time.Duration, client Statsd) *StatsdBuffer {
	sb := &StatsdBuffer{
		flushInterval: interval,
		statsd:        client,
		eventChannel:  make(chan event.Event, 100),
		events:        make(map[string]event.Event),
		closeChannel:  make(chan closeRequest),
		Logger:        log.New(os.Stdout, "[BufferedStatsdClient] ", log.Ldate|log.Ltime),
		Verbose:       true,
	}
	go sb.collector()
	return sb
}

// CreateSocket creates a UDP connection to a StatsD server
func (sb *StatsdBuffer) CreateSocket() error {
	return sb.statsd.CreateSocket()
}

// CreateTCPSocket creates a TCP connection to a StatsD server
func (sb *StatsdBuffer) CreateTCPSocket() error {
	return sb.statsd.CreateTCPSocket()
}

// Incr - Increment a counter metric. Often used to note a particular event
func (sb *StatsdBuffer) Incr(stat string, count int64) error {
	if 0 != count {
		sb.eventChannel <- &event.Increment{Name: stat, Value: count}
	}
	return nil
}

// Decr - Decrement a counter metric. Often used to note a particular event
func (sb *StatsdBuffer) Decr(stat string, count int64) error {
	if 0 != count {
		sb.eventChannel <- &event.Increment{Name: stat, Value: -count}
	}
	return nil
}

// Timing - Track a duration event
func (sb *StatsdBuffer) Timing(stat string, delta int64) error {
	sb.eventChannel <- event.NewTiming(stat, delta)
	return nil
}

// PrecisionTiming - Track a duration event
// the time delta has to be a duration
func (sb *StatsdBuffer) PrecisionTiming(stat string, delta time.Duration) error {
	sb.eventChannel <- event.NewPrecisionTiming(stat, delta)
	return nil
}

// Gauge - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again
func (sb *StatsdBuffer) Gauge(stat string, value int64) error {
	sb.eventChannel <- &event.Gauge{Name: stat, Value: value}
	return nil
}

// GaugeDelta records a delta from the previous value (as int64)
func (sb *StatsdBuffer) GaugeDelta(stat string, value int64) error {
	sb.eventChannel <- &event.GaugeDelta{Name: stat, Value: value}
	return nil
}

// FGauge is a Gauge working with float64 values
func (sb *StatsdBuffer) FGauge(stat string, value float64) error {
	sb.eventChannel <- &event.FGauge{Name: stat, Value: value}
	return nil
}

// FGaugeDelta records a delta from the previous value (as float64)
func (sb *StatsdBuffer) FGaugeDelta(stat string, value float64) error {
	sb.eventChannel <- &event.FGaugeDelta{Name: stat, Value: value}
	return nil
}

// Absolute - Send absolute-valued metric (not averaged/aggregated)
func (sb *StatsdBuffer) Absolute(stat string, value int64) error {
	sb.eventChannel <- &event.Absolute{Name: stat, Values: []int64{value}}
	return nil
}

// FAbsolute - Send absolute-valued metric (not averaged/aggregated)
func (sb *StatsdBuffer) FAbsolute(stat string, value float64) error {
	sb.eventChannel <- &event.FAbsolute{Name: stat, Values: []float64{value}}
	return nil
}

// Total - Send a metric that is continously increasing, e.g. read operations since boot
func (sb *StatsdBuffer) Total(stat string, value int64) error {
	sb.eventChannel <- &event.Total{Name: stat, Value: value}
	return nil
}

// SendEvents - Sends stats from all the event objects.
func (sb *StatsdBuffer) SendEvents(events map[string]event.Event) error {
	for _, e := range events {
		sb.eventChannel <- e
	}
	return nil
}

// avoid too many allocations by memoizing the "type|key" pair for an event
// @see https://gobyexample.com/closures
func initMemoisedKeyMap() func(typ string, key string) string {
	m := make(map[string]map[string]string)
	return func(typ string, key string) string {
		if _, ok := m[typ]; !ok {
			m[typ] = make(map[string]string)
		}
		k, ok := m[typ][key]
		if !ok {
			m[typ][key] = typ + "|" + key
			return m[typ][key]
		}
		return k // memoized value
	}
}

// handle flushes and updates in one single thread (instead of locking the events map)
func (sb *StatsdBuffer) collector() {
	// on a panic event, flush all the pending stats before panicking
	defer func(sb *StatsdBuffer) {
		if r := recover(); r != nil {
			sb.Logger.Println("Caught panic, flushing stats before throwing the panic again")
			err := sb.flush()
			if nil != err {
				sb.Logger.Println("Error flushing stats", err.Error())
			}
			panic(r)
		}
	}(sb)

	keyFor := initMemoisedKeyMap() // avoid allocations (https://gobyexample.com/closures)

	ticker := time.NewTicker(sb.flushInterval)

	for {
		select {
		case <-ticker.C:
			//sb.Logger.Println("Flushing stats")
			err := sb.flush()
			if nil != err {
				sb.Logger.Println("Error flushing stats", err.Error())
			}
		case e := <-sb.eventChannel:
			//sb.Logger.Println("Received ", e.String())
			// issue #28: unable to use Incr and PrecisionTiming with the same key (also fixed #27)
			k := keyFor(e.TypeString(), e.Key()) // avoid allocations
			if e2, ok := sb.events[k]; ok {
				//sb.Logger.Println("Updating existing event")
				err := e2.Update(e)
				if nil != err {
					sb.Logger.Println("Error updating stats", err.Error())
				}
				sb.events[k] = e2
			} else {
				//sb.Logger.Println("Adding new event")
				sb.events[k] = e
			}
		case c := <-sb.closeChannel:
			if sb.Verbose {
				sb.Logger.Println("Asked to terminate. Flushing stats before returning.")
			}
			c.reply <- sb.flush()
			return
		}
	}
}

// Close sends a close event to the collector asking to stop & flush pending stats
// and closes the statsd client
func (sb *StatsdBuffer) Close() (err error) {
	// 1. send a close event to the collector
	req := closeRequest{reply: make(chan error)}
	sb.closeChannel <- req
	// 2. wait for the collector to drain the queue and respond
	err = <-req.reply
	// 3. close the statsd client
	err2 := sb.statsd.Close()
	if err != nil {
		return err
	}
	return err2
}

// send the events to StatsD and reset them.
// This function is NOT thread-safe, so it must only be invoked synchronously
// from within the collector() goroutine
func (sb *StatsdBuffer) flush() (err error) {
	n := len(sb.events)
	if n == 0 {
		return nil
	}
	if err := sb.statsd.SendEvents(sb.events); err != nil {
		sb.Logger.Println(err)
		return err
	}
	sb.events = make(map[string]event.Event)

	return nil
}


================================================
FILE: bufferedclient_test.go
================================================
package statsd

import (
	"math"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"testing"
	"time"
)

// -------------------------------------------------------------------

type KVint64 struct {
	Key   string
	Value int64
}
type KVfloat64 struct {
	Key   string
	Value float64
}

type KVint64Sorter []KVint64
type KVfloat64Sorter []KVfloat64

func (a KVint64Sorter) Len() int      { return len(a) }
func (a KVint64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a KVint64Sorter) Less(i, j int) bool {
	if a[i].Key == a[j].Key {
		return a[i].Value < a[j].Value
	}
	return a[i].Key < a[j].Key
}

func (a KVfloat64Sorter) Len() int      { return len(a) }
func (a KVfloat64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a KVfloat64Sorter) Less(i, j int) bool {
	if a[i].Key == a[j].Key {
		return a[i].Value < a[j].Value
	}
	return a[i].Key < a[j].Key
}

// Normalise the number of decimal places for easier comparisons in the tests
func (a KVfloat64Sorter) Normalise(precision int) {
	for k, v := range a {
		v.Value = toFixed(v.Value, precision)
		a[k] = v
	}
}

func round(num float64) int {
	return int(num + math.Copysign(0.5, num))
}

func toFixed(num float64, precision int) float64 {
	output := math.Pow(10, float64(precision))
	return float64(round(num*output)) / output
}

// -------------------------------------------------------------------

func TestBufferedInt64(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexTotal := regexp.MustCompile(`^(.*)\:([+\-]?\d+)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		function string
		suffix   string
		input    []KVint64
		expected []KVint64
	}{
		{
			name:     "total",
			function: "total",
			suffix:   "t",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"x:b:c", 5},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"x:b:c", 5},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "gauge",
			function: "gauge",
			suffix:   "g",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", 2}, // this should override the previous one
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 2},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "gaugedelta",
			function: "gaugedelta",
			suffix:   "g",
			input: []KVint64{
				{"a:b:c", +5},
				{"d:e:f", -2},
				{"a:b:c", -2},
				{"g.h.i", +1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 3},
				{"d:e:f", -2},
				{"g.h.i", +1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "increment",
			function: "increment",
			suffix:   "c",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", -2},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 3},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)
			buffered := NewStatsdBuffer(time.Millisecond*20, client)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = buffered.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer buffered.Close()

			for _, entry := range tc.input {
				switch tc.function { // send metric
				case "total":
					err = buffered.Total(entry.Key, entry.Value)
				case "gauge":
					err = buffered.Gauge(entry.Key, entry.Value)
				case "gaugedelta":
					err = buffered.GaugeDelta(entry.Key, entry.Value)
				case "increment":
					if entry.Value < 0 {
						err = buffered.Decr(entry.Key, int64(math.Abs(float64(entry.Value))))
					} else {
						err = buffered.Incr(entry.Key, entry.Value)
					}
				}
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual []KVint64
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexTotal.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseInt(vv[2], 10, 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			sort.Sort(KVint64Sorter(actual))

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestBufferedFloat64(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexTotal := regexp.MustCompile(`^(.*)\:([+\-]?\d+(?:\.\d+)?)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		function string
		suffix   string
		input    KVfloat64Sorter
		expected KVfloat64Sorter
	}{
		{
			name:     "fgauge",
			function: "fgauge",
			suffix:   "g",
			input: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.3},
				{"a:b:c", 2.2}, // this should override the previous one
				{"g.h.i", 1.2},
				{"zz.%HOST%", 1.1}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", 2.2},
				{"d:e:f", 2.3},
				{"g.h.i", 1.2},
				{"zz." + hostname, 1.1}, // also test %HOST% replacement
			},
		},
		{
			name:     "fgaugedelta",
			function: "fgaugedelta",
			suffix:   "g",
			input: KVfloat64Sorter{
				{"a:b:c", +5.1},
				{"d:e:f", -2.2},
				{"a:b:c", -2.1},
				{"g.h.i", +1.3},
				{"zz.%HOST%", 1.4}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", 3.0},
				{"d:e:f", -2.2},
				{"g.h.i", +1.3},
				{"zz." + hostname, 1.4}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)
			buffered := NewStatsdBuffer(time.Millisecond*20, client)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = buffered.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer buffered.Close()

			for _, entry := range tc.input {
				switch tc.function { // send metric
				case "fgauge":
					err = buffered.FGauge(entry.Key, entry.Value)
				case "fgaugedelta":
					err = buffered.FGaugeDelta(entry.Key, entry.Value)
				}
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual KVfloat64Sorter
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexTotal.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseFloat(vv[2], 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			actual.Normalise(2) // keep 2 decimal digits
			sort.Sort(actual)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: Expected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestBufferedAbsolute(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexAbsolute := regexp.MustCompile(`^(.*)\:([+\-]?\d+)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		suffix   string
		input    KVint64Sorter
		expected KVint64Sorter
	}{
		{
			name:   "absolute",
			suffix: "a",
			input: KVint64Sorter{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", 8},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: KVint64Sorter{
				{"a:b:c", 5},
				{"a:b:c", 8},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)
			buffered := NewStatsdBuffer(time.Millisecond*20, client)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = buffered.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer buffered.Close()

			for _, entry := range tc.input {
				err = buffered.Absolute(entry.Key, entry.Value)
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual KVint64Sorter
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexAbsolute.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseInt(vv[2], 10, 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			sort.Sort(actual)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: \nExpected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestBufferedFAbsolute(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexAbsolute := regexp.MustCompile(`^(.*)\:([+\-]?\d+(?:\.\d+)?)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		suffix   string
		input    KVfloat64Sorter
		expected KVfloat64Sorter
	}{
		{
			name:   "fabsolute",
			suffix: "a",
			input: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.1},
				{"x:b:c", 5.1},
				{"g.h.i", 1.1},
				{"zz.%HOST%", 1.5}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.1},
				{"g.h.i", 1.1},
				{"x:b:c", 5.1},
				{"zz." + hostname, 1.5}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)
			buffered := NewStatsdBuffer(time.Millisecond*20, client)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = buffered.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer buffered.Close()

			for _, entry := range tc.input {
				err = buffered.FAbsolute(entry.Key, entry.Value)
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual KVfloat64Sorter
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexAbsolute.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseFloat(vv[2], 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: toFixed(v, 2)})
				}
			}

			sort.Sort(actual)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: \nExpected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}


================================================
FILE: client.go
================================================
package statsd

import (
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"

	"github.com/quipo/statsd/event"
)

// Logger interface compatible with log.Logger
type Logger interface {
	Println(v ...interface{})
}

// UDPPayloadSize is the number of bytes to send at one go through the udp socket.
// SendEvents will try to pack as many events into one udp packet.
// Change this value as per network capabilities
// For example to change to 16KB
//  import "github.com/quipo/statsd"
//  func init() {
//   statsd.UDPPayloadSize = 16 * 1024
//  }
var UDPPayloadSize = 512

// Hostname is exported so clients can set it to something different than the default
var Hostname string

var errNotConnected = fmt.Errorf("cannot send stats, not connected to StatsD server")

// errors
var (
	ErrInvalidCount      = errors.New("count is less than 0")
	ErrInvalidSampleRate = errors.New("sample rate is larger than 1 or less then 0")
)

func init() {
	if host, err := os.Hostname(); nil == err {
		Hostname = host
	}
}

type socketType string

const (
	udpSocket socketType = "udp"
	tcpSocket socketType = "tcp"
)

// StatsdClient is a client library to send events to StatsD
type StatsdClient struct {
	conn     net.Conn
	addr     string
	prefix   string
	sockType socketType
	Logger   Logger
}

// NewStatsdClient - Factory
func NewStatsdClient(addr string, prefix string) *StatsdClient {
	// allow %HOST% in the prefix string
	prefix = strings.Replace(prefix, "%HOST%", Hostname, 1)
	return &StatsdClient{
		addr:   addr,
		prefix: prefix,
		Logger: log.New(os.Stdout, "[StatsdClient] ", log.Ldate|log.Ltime),
	}
}

// String returns the StatsD server address
func (c *StatsdClient) String() string {
	return c.addr
}

// CreateSocket creates a UDP connection to a StatsD server
func (c *StatsdClient) CreateSocket() error {
	conn, err := net.DialTimeout(string(udpSocket), c.addr, 5*time.Second)
	if err != nil {
		return err
	}
	c.conn = conn
	c.sockType = udpSocket
	return nil
}

// CreateTCPSocket creates a TCP connection to a StatsD server
func (c *StatsdClient) CreateTCPSocket() error {
	conn, err := net.DialTimeout(string(tcpSocket), c.addr, 5*time.Second)
	if err != nil {
		return err
	}
	c.conn = conn
	c.sockType = tcpSocket
	return nil
}

// Close the UDP connection
func (c *StatsdClient) Close() error {
	if nil == c.conn {
		return nil
	}
	return c.conn.Close()
}

// See statsd data types here: http://statsd.readthedocs.org/en/latest/types.html
// or also https://github.com/b/statsd_spec

// Incr - Increment a counter metric. Often used to note a particular event
func (c *StatsdClient) Incr(stat string, count int64) error {
	return c.IncrWithSampling(stat, count, 1)
}

// IncrWithSampling - Increment a counter metric with sampling between 0 and 1
func (c *StatsdClient) IncrWithSampling(stat string, count int64, sampleRate float32) error {
	if err := checkSampleRate(sampleRate); err != nil {
		return err
	}

	if !shouldFire(sampleRate) {
		return nil // ignore this call
	}

	if err := checkCount(count); err != nil {
		return err
	}

	return c.send(stat, "%d|c", count, sampleRate)
}

// Decr - Decrement a counter metric. Often used to note a particular event
func (c *StatsdClient) Decr(stat string, count int64) error {
	return c.DecrWithSampling(stat, count, 1)
}

// DecrWithSampling - Decrement a counter metric with sampling between 0 and 1
func (c *StatsdClient) DecrWithSampling(stat string, count int64, sampleRate float32) error {
	if err := checkSampleRate(sampleRate); err != nil {
		return err
	}

	if !shouldFire(sampleRate) {
		return nil // ignore this call
	}

	if err := checkCount(count); err != nil {
		return err
	}

	return c.send(stat, "%d|c", -count, sampleRate)
}

// Timing - Track a duration event
// the time delta must be given in milliseconds
func (c *StatsdClient) Timing(stat string, delta int64) error {
	return c.TimingWithSampling(stat, delta, 1)
}

// TimingWithSampling - Track a duration event
func (c *StatsdClient) TimingWithSampling(stat string, delta int64, sampleRate float32) error {
	if err := checkSampleRate(sampleRate); err != nil {
		return err
	}

	if !shouldFire(sampleRate) {
		return nil // ignore this call
	}

	return c.send(stat, "%d|ms", delta, sampleRate)
}

// PrecisionTiming - Track a duration event
// the time delta has to be a duration
func (c *StatsdClient) PrecisionTiming(stat string, delta time.Duration) error {
	return c.send(stat, "%.6f|ms", float64(delta)/float64(time.Millisecond), 1)
}

// Gauge - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again. If you specify
// delta to be true, that specifies that the gauge should be updated, not set. Due to the
// underlying protocol, you can't explicitly set a gauge to a negative number without
// first setting it to zero.
func (c *StatsdClient) Gauge(stat string, value int64) error {
	return c.GaugeWithSampling(stat, value, 1)
}

// GaugeWithSampling - Gauges are a constant data type.
func (c *StatsdClient) GaugeWithSampling(stat string, value int64, sampleRate float32) error {
	if err := checkSampleRate(sampleRate); err != nil {
		return err
	}

	if !shouldFire(sampleRate) {
		return nil // ignore this call
	}

	if value < 0 {
		err := c.send(stat, "%d|g", 0, 1)
		if nil != err {
			return err
		}
	}

	return c.send(stat, "%d|g", value, sampleRate)
}

// GaugeDelta -- Send a change for a gauge
func (c *StatsdClient) GaugeDelta(stat string, value int64) error {
	// Gauge Deltas are always sent with a leading '+' or '-'. The '-' takes care of itself but the '+' must added by hand
	if value < 0 {
		return c.send(stat, "%d|g", value, 1)
	}
	return c.send(stat, "+%d|g", value, 1)
}

// FGauge -- Send a floating point value for a gauge
func (c *StatsdClient) FGauge(stat string, value float64) error {
	return c.FGaugeWithSampling(stat, value, 1)
}

// FGaugeWithSampling - Gauges are a constant data type.
func (c *StatsdClient) FGaugeWithSampling(stat string, value float64, sampleRate float32) error {
	if err := checkSampleRate(sampleRate); err != nil {
		return err
	}

	if !shouldFire(sampleRate) {
		return nil
	}

	if value < 0 {
		err := c.send(stat, "%d|g", 0, 1)
		if nil != err {
			return err
		}
	}

	return c.send(stat, "%g|g", value, sampleRate)
}

// FGaugeDelta -- Send a floating point change for a gauge
func (c *StatsdClient) FGaugeDelta(stat string, value float64) error {
	if value < 0 {
		return c.send(stat, "%g|g", value, 1)
	}
	return c.send(stat, "+%g|g", value, 1)
}

// Absolute - Send absolute-valued metric (not averaged/aggregated)
func (c *StatsdClient) Absolute(stat string, value int64) error {
	return c.send(stat, "%d|a", value, 1)
}

// FAbsolute - Send absolute-valued floating point metric (not averaged/aggregated)
func (c *StatsdClient) FAbsolute(stat string, value float64) error {
	return c.send(stat, "%g|a", value, 1)
}

// Total - Send a metric that is continously increasing, e.g. read operations since boot
func (c *StatsdClient) Total(stat string, value int64) error {
	return c.send(stat, "%d|t", value, 1)
}

// write a UDP packet with the statsd event
func (c *StatsdClient) send(stat string, format string, value interface{}, sampleRate float32) error {
	if c.conn == nil {
		return errNotConnected
	}

	stat = strings.Replace(stat, "%HOST%", Hostname, 1)
	metricString := c.prefix + stat + ":" + fmt.Sprintf(format, value)

	if sampleRate != 1 {
		metricString = fmt.Sprintf("%s|@%f", metricString, sampleRate)
	}

	// if sending tcp append a newline
	if c.sockType == tcpSocket {
		metricString += "\n"
	}

	_, err := fmt.Fprint(c.conn, metricString)
	return err
}

// SendEvent - Sends stats from an event object
func (c *StatsdClient) SendEvent(e event.Event) error {
	if c.conn == nil {
		return errNotConnected
	}
	for _, stat := range e.Stats() {
		//fmt.Printf("SENDING EVENT %s%s\n", c.prefix, strings.Replace(stat, "%HOST%", Hostname, 1))
		_, err := fmt.Fprintf(c.conn, "%s%s", c.prefix, strings.Replace(stat, "%HOST%", Hostname, 1))
		if nil != err {
			return err
		}
	}
	return nil
}

// SendEvents - Sends stats from all the event objects.
// Tries to bundle many together into one fmt.Fprintf based on UDPPayloadSize.
func (c *StatsdClient) SendEvents(events map[string]event.Event) error {
	if c.conn == nil {
		return errNotConnected
	}

	var n int
	var stats = make([]string, 0)

	for _, e := range events {
		for _, stat := range e.Stats() {

			stat = c.prefix + strings.Replace(stat, "%HOST%", Hostname, 1)
			_n := n + len(stat) + 1

			if _n > UDPPayloadSize {
				// with this last event, the UDP payload would be too big
				if _, err := fmt.Fprintf(c.conn, strings.Join(stats, "\n")+"\n"); err != nil {
					return err
				}
				// reset payload after flushing, and add the last event
				stats = []string{stat}
				n = len(stat)
				continue
			}

			// can fit more into the current payload
			n = _n
			stats = append(stats, stat)
		}
	}

	if len(stats) != 0 {
		if _, err := fmt.Fprintf(c.conn, strings.Join(stats, "\n")+"\n"); err != nil {
			return err
		}
	}

	return nil
}

func checkCount(c int64) error {
	if c <= 0 {
		return ErrInvalidCount
	}

	return nil
}

func checkSampleRate(r float32) error {
	if r < 0 || r > 1 {
		return ErrInvalidSampleRate
	}

	return nil
}

func shouldFire(sampleRate float32) bool {
	if sampleRate == 1 {
		return true
	}

	r := rand.New(rand.NewSource(time.Now().Unix()))

	return r.Float32() <= sampleRate
}


================================================
FILE: client_test.go
================================================
package statsd

import (
	"bytes"
	"fmt"
	"math"
	"net"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/quipo/statsd/event"
)

// MockNetConn is a mock for net.Conn
type MockNetConn struct {
	buf bytes.Buffer
}

func (mock *MockNetConn) Read(b []byte) (n int, err error) {
	return mock.buf.Read(b)
}
func (mock *MockNetConn) Write(b []byte) (n int, err error) {
	return mock.buf.Write(append(b, '\n'))
}
func (mock MockNetConn) Close() error {
	mock.buf.Truncate(0)
	return nil
}
func (mock MockNetConn) LocalAddr() net.Addr {
	return nil
}
func (mock MockNetConn) RemoteAddr() net.Addr {
	return nil
}
func (mock MockNetConn) SetDeadline(t time.Time) error {
	return nil
}
func (mock MockNetConn) SetReadDeadline(t time.Time) error {
	return nil
}
func (mock MockNetConn) SetWriteDeadline(t time.Time) error {
	return nil
}

/*
// TODO: use this function instead mocking net.Conn
// usage: client, server := GetTestConnection("tcp", t)
// usage: client, server := GetTestConnection("udp", t)
func GetTestConnection(connType string, t *testing.T) (client, server net.Conn) {
	ln, err := net.Listen(connType, "127.0.0.1")
	if nil != err {
		t.Error("TCP errpr:", err)
	}
	go func() {
		defer ln.Close()
		server, err = ln.Accept()
		if nil != err {
			t.Error("TCP Accept errpr:", err)
		}
	}()

	client, err = net.Dial(connType, ln.Addr().String())
	if nil != err {
		t.Error("TCP Dial error:", err)
	}
	return client, server
}
*/

func TestClientInt64(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexTotal := regexp.MustCompile(`^(.*)\:([+\-]?\d+)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		function string
		suffix   string
		input    []KVint64
		expected []KVint64
	}{
		{
			name:     "total",
			function: "total",
			suffix:   "t",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"x:b:c", 5},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"x:b:c", 5},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "gauge",
			function: "gauge",
			suffix:   "g",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", 2},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 5},
				{"a:b:c", 2},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "gaugedelta",
			function: "gaugedelta",
			suffix:   "g",
			input: []KVint64{
				{"a:b:c", +5},
				{"d:e:f", -2},
				{"a:b:c", -2},
				{"g.h.i", +1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", +5},
				{"d:e:f", -2},
				{"a:b:c", -2},
				{"g.h.i", +1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
		{
			name:     "increment",
			function: "increment",
			suffix:   "c",
			input: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", -2},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: []KVint64{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", -2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = client.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer client.Close()

			for _, entry := range tc.input {
				switch tc.function { // send metric
				case "total":
					err = client.Total(entry.Key, entry.Value)
				case "gauge":
					err = client.Gauge(entry.Key, entry.Value)
				case "gaugedelta":
					err = client.GaugeDelta(entry.Key, entry.Value)
				case "increment":
					if entry.Value < 0 {
						err = client.Decr(entry.Key, int64(math.Abs(float64(entry.Value))))
					} else {
						err = client.Incr(entry.Key, entry.Value)
					}
				}
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual []KVint64
			for batch := range ch {
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexTotal.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseInt(vv[2], 10, 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			sort.Sort(KVint64Sorter(actual))
			sort.Sort(KVint64Sorter(tc.expected))

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: \nExpected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestClientFloat64(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexTotal := regexp.MustCompile(`^(.*)\:([+\-]?\d+(?:\.\d+)?)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		function string
		suffix   string
		input    KVfloat64Sorter
		expected KVfloat64Sorter
	}{
		{
			name:     "fgauge",
			function: "fgauge",
			suffix:   "g",
			input: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.3},
				{"a:b:c", -2.2},
				{"g.h.i", 1.2},
				{"zz.%HOST%", 1.1}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.3},
				{"a:b:c", 0},
				{"a:b:c", -2.2},
				{"g.h.i", 1.2},
				{"zz." + hostname, 1.1}, // also test %HOST% replacement
			},
		},
		{
			name:     "fgaugedelta",
			function: "fgaugedelta",
			suffix:   "g",
			input: KVfloat64Sorter{
				{"a:b:c", +5.1},
				{"d:e:f", -2.2},
				{"a:b:c", -2.1},
				{"g.h.i", +1.3},
				{"zz.%HOST%", 1.4}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", +5.1},
				{"d:e:f", -2.2},
				{"a:b:c", -2.1},
				{"g.h.i", +1.3},
				{"zz." + hostname, 1.4}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = client.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			//defer client.Close()

			for _, entry := range tc.input {
				switch tc.function { // send metric
				case "fgauge":
					err = client.FGauge(entry.Key, entry.Value)
				case "fgaugedelta":
					err = client.FGaugeDelta(entry.Key, entry.Value)
				}
				if nil != err {
					t.Error(err)
				}
			}
			client.Close()

			received := 0
			var actual KVfloat64Sorter
			for batch := range ch {
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexTotal.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseFloat(vv[2], 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			actual.Normalise(2) // keep 2 decimal digits
			sort.Sort(actual)
			sort.Sort(tc.expected)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: Expected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestClientAbsolute(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexAbsolute := regexp.MustCompile(`^(.*)\:([+\-]?\d+)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		suffix   string
		input    KVint64Sorter
		expected KVint64Sorter
	}{
		{
			name:   "absolute",
			suffix: "a",
			input: KVint64Sorter{
				{"a:b:c", 5},
				{"d:e:f", 2},
				{"a:b:c", 8},
				{"g.h.i", 1},
				{"zz.%HOST%", 1}, // also test %HOST% replacement
			},
			expected: KVint64Sorter{
				{"a:b:c", 5},
				{"a:b:c", 8},
				{"d:e:f", 2},
				{"g.h.i", 1},
				{"zz." + hostname, 1}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = client.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer client.Close()

			for _, entry := range tc.input {
				err = client.Absolute(entry.Key, entry.Value)
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual KVint64Sorter
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexAbsolute.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseInt(vv[2], 10, 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})
				}
			}

			sort.Sort(actual)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: \nExpected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func TestClientFAbsolute(t *testing.T) {
	hostname, err := os.Hostname()
	if nil != err {
		t.Fatal("Cannot read host name:", err)
	}

	regexAbsolute := regexp.MustCompile(`^(.*)\:([+\-]?\d+(?:\.\d+)?)\|(\w).*$`)
	prefix := "myproject."

	tt := []struct {
		name     string
		suffix   string
		input    KVfloat64Sorter
		expected KVfloat64Sorter
	}{
		{
			name:   "fabsolute",
			suffix: "a",
			input: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.1},
				{"x:b:c", 5.1},
				{"g.h.i", 1.1},
				{"zz.%HOST%", 1.5}, // also test %HOST% replacement
			},
			expected: KVfloat64Sorter{
				{"a:b:c", 5.2},
				{"d:e:f", 2.1},
				{"g.h.i", 1.1},
				{"x:b:c", 5.1},
				{"zz." + hostname, 1.5}, // also test %HOST% replacement
			},
		},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {

			ln, udpAddr := newLocalListenerUDP(t)
			defer ln.Close()

			t.Log("Starting new UDP listener at", udpAddr.String())
			time.Sleep(50 * time.Millisecond)

			client := NewStatsdClient(udpAddr.String(), prefix)

			ch := make(chan string)

			go doListenUDP(t, ln, ch, len(tc.expected))
			time.Sleep(50 * time.Millisecond)

			err = client.CreateSocket()
			if nil != err {
				t.Fatal(err)
			}
			defer client.Close()

			for _, entry := range tc.input {
				err = client.FAbsolute(entry.Key, entry.Value)
				if nil != err {
					t.Error(err)
				}
			}

			received := 0
			var actual KVfloat64Sorter
			for received < len(tc.expected) {
				batch := <-ch
				for _, x := range strings.Split(batch, "\n") {
					x = strings.TrimSpace(x)
					if "" == x {
						continue
					}
					if !strings.HasPrefix(x, prefix) {
						t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
						return
					}
					received++
					vv := regexAbsolute.FindStringSubmatch(x)
					//t.Log(vv, x)
					if len(vv) < 4 {
						t.Error("Expecting more tokens", len(vv))
						continue
					}
					if vv[3] != tc.suffix {
						t.Errorf("Metric without expected suffix: expected '%s', actual '%s'", tc.suffix, vv[3])
					}
					v, err := strconv.ParseFloat(vv[2], 64)
					if err != nil {
						t.Error(err)
					}
					actual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: toFixed(v, 2)})
				}
			}

			sort.Sort(actual)

			if !reflect.DeepEqual(tc.expected, actual) {
				t.Errorf("did not receive all metrics: \nExpected: \n%T %v, \nActual: \n%T %v ", tc.expected, tc.expected, actual, actual)
			}

			time.Sleep(500 * time.Millisecond)
		})
	}
}

func newLocalListenerUDP(t *testing.T) (*net.UDPConn, *net.UDPAddr) {
	addr := fmt.Sprintf(":%d", getFreePort())
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		t.Error("UDP error:", err)
		return nil, nil
	}
	ln, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		t.Error("UDP Listen error:", err)
		return ln, udpAddr
	}
	t.Logf("Started new local UDP listener @ %s\n", udpAddr)
	return ln, udpAddr
}

func doListenUDP(t *testing.T, conn *net.UDPConn, ch chan string, n int) {
	var wg sync.WaitGroup
	wg.Add(n)

	for n > 0 {
		// Handle the connection in a new goroutine.
		// The loop then returns to accepting, so that
		// multiple connections may be served concurrently.
		go func(c *net.UDPConn, ch chan string, wg *sync.WaitGroup) {
			t.Logf("Reading from UDP socket @ %s\n", conn.LocalAddr().String())
			buffer := make([]byte, 1024)
			size, err := c.Read(buffer)
			// size, address, err := sock.ReadFrom(buffer) <- This starts printing empty and nil values below immediatly
			if err != nil {
				t.Logf("Error reading from UDP socket. Buffer: %s, Size: %d, Error: %s\n", string(buffer), size, err)
				//t.Fatal(err)
			}
			t.Logf("Read buffer: \n------------------\n%s\n------------------\n* Size: %d\n", string(buffer), size)
			ch <- string(buffer[:size])
			wg.Done()
		}(conn, ch, &wg)
		n--
	}
	wg.Wait()
	close(ch)
	t.Logf("Finished listening on UDP socket @ %s\n", conn.LocalAddr().String())
}

func doListenTCP(t *testing.T, conn net.Listener, ch chan string, n int) {
	for n > 0 { // read n non-empty lines from TCP socket
		t.Logf("doListenTCP iteration")
		client, err := conn.Accept()

		if err != nil {
			t.Error(err)
			return
		}

		buf := make([]byte, 1024)
		size, err := client.Read(buf)
		if err != nil {
			if err.Error() == "EOF" {
				return
			}
			t.Error(err)
			return
		}
		t.Logf("Read from TCP socket:\n----------\n%s\n----------\n", string(buf))
		for _, s := range bytes.Split(buf[:size], []byte{'\n'}) {
			if len(s) > 0 {
				n--
				ch <- string(s)
			}
		}
	}
	close(ch)
}

func newLocalListenerTCP(t *testing.T) (string, net.Listener) {
	addr := fmt.Sprintf("127.0.0.1:%d", getFreePort())
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		t.Fatal(err)
	}
	return addr, ln
}

func TestTCP(t *testing.T) {
	addr, ln := newLocalListenerTCP(t)
	defer ln.Close()

	t.Log("Starting new TCP listener at", addr)
	time.Sleep(50 * time.Millisecond)

	prefix := "myproject."
	client := NewStatsdClient(addr, prefix)

	ch := make(chan string)

	s := map[string]int64{
		"a:b:c": 5,
		"d:e:f": 2,
		"x:b:c": 5,
		"g.h.i": 1,
	}

	expected := make(map[string]int64)
	for k, v := range s {
		expected[k] = v
	}

	// also test %HOST% replacement
	s["zz.%HOST%"] = 1
	hostname, err := os.Hostname()
	expected["zz."+hostname] = 1
	if nil != err {
		t.Error("Cannot read host name:", err.Error())
	}

	t.Logf("Sending stats to TCP Socket")
	err = client.CreateTCPSocket()
	if nil != err {
		t.Error(err)
	}
	defer client.Close()

	for k, v := range s {
		err = client.Total(k, v)
		if nil != err {
			t.Error(err)
		}
	}
	time.Sleep(60 * time.Millisecond)

	go doListenTCP(t, ln, ch, len(s))
	time.Sleep(50 * time.Millisecond)

	actual := make(map[string]int64)

	re := regexp.MustCompile(`^(.*)\:(\d+)\|(\w).*$`)

	for i := len(s); i > 0; i-- {
		//t.Logf("ITERATION %d\n", i)
		x, open := <-ch
		if !open {
			//t.Logf("CLOSED _____")
			break
		}
		x = strings.TrimSpace(x)
		if "" == x {
			//t.Logf("EMPTY STRING *****")
			break
		}
		//fmt.Println(x)
		if !strings.HasPrefix(x, prefix) {
			t.Errorf("Metric without expected prefix: expected '%s', actual '%s'", prefix, x)
			break
		}
		vv := re.FindStringSubmatch(x)
		if vv[3] != "t" {
			t.Errorf("Metric without expected suffix: expected 't', actual '%s'", vv[3])
		}
		v, err := strconv.ParseInt(vv[2], 10, 64)
		if err != nil {
			t.Error(err)
		}
		actual[vv[1][len(prefix):]] = v
	}

	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v \n", expected, expected, actual, actual)
	}
}

func TestSendEvents(t *testing.T) {
	c := NewStatsdClient("127.0.0.1:1201", "test")
	c.conn = &MockNetConn{} // mock connection

	// override with a small size
	UDPPayloadSize = 40

	e1 := &event.Increment{Name: "test1", Value: 123}
	e2 := &event.Increment{Name: "test2", Value: 432}
	e3 := &event.Increment{Name: "test3", Value: 111}
	e4 := &event.Gauge{Name: "test4", Value: 12435}

	events := map[string]event.Event{
		"test1": e1,
		"test2": e2,
		"test3": e3,
		"test4": e4,
	}

	err := c.SendEvents(events)
	if nil != err {
		t.Error(err)
	}

	b1 := make([]byte, UDPPayloadSize*3)
	n, err2 := c.conn.Read(b1)
	if nil != err2 {
		t.Error(err2)
	}
	cleanPayload := strings.Replace(strings.TrimSpace(string(b1[:n])), "\n\n", "\n", -1)
	nStats := len(strings.Split(cleanPayload, "\n"))

	if nStats != len(events) {
		t.Errorf("Was expecting %d events, got %d:  %s", len(events), nStats, string(b1))
	}
}

// getFreePort Ask the kernel for a free open port that is ready to use
func getFreePort() int {
	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
	if err != nil {
		panic(err)
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		panic(err)
	}
	defer l.Close()
	return l.Addr().(*net.TCPAddr).Port
}


================================================
FILE: event/absolute.go
================================================
package event

import "fmt"

// Absolute is a metric that is not averaged/aggregated.
// We keep each value distinct and then we flush them all individually.
type Absolute struct {
	Name   string
	Values []int64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *Absolute) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Values = append(e.Values, e2.Payload().([]int64)...)
	return nil
}

// Payload returns the aggregated value for this event
func (e Absolute) Payload() interface{} {
	return e.Values
}

// Stats returns an array of StatsD events as they travel over UDP
func (e Absolute) Stats() []string {
	ret := make([]string, 0, len(e.Values))
	for _, v := range e.Values {
		ret = append(ret, fmt.Sprintf("%s:%d|a", e.Name, v))
	}
	return ret
}

// Key returns the name of this metric
func (e Absolute) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *Absolute) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e Absolute) Type() int {
	return EventAbsolute
}

// TypeString returns a name for this type of metric
func (e Absolute) TypeString() string {
	return "Absolute"
}

// String returns a debug-friendly representation of this metric
func (e Absolute) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Values: %v}", e.TypeString(), e.Name, e.Values)
}


================================================
FILE: event/absolute_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestAbsoluteUpdate(t *testing.T) {
	e1 := &Absolute{Name: "test", Values: []int64{15}}
	e2 := &Absolute{Name: "test", Values: []int64{-10}}
	e3 := &Absolute{Name: "test", Values: []int64{8}}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:15|a", "test:-10|a", "test:8|a"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/fabsolute.go
================================================
package event

import "fmt"

// FAbsolute is a metric that is not averaged/aggregated.
// We keep each value distinct and then we flush them all individually.
type FAbsolute struct {
	Name   string
	Values []float64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *FAbsolute) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Values = append(e.Values, e2.Payload().([]float64)...)
	return nil
}

// Payload returns the aggregated value for this event
func (e FAbsolute) Payload() interface{} {
	return e.Values
}

// Stats returns an array of StatsD events as they travel over UDP
func (e FAbsolute) Stats() []string {
	ret := make([]string, 0, len(e.Values))
	for _, v := range e.Values {
		ret = append(ret, fmt.Sprintf("%s:%g|a", e.Name, v))
	}
	return ret
}

// Key returns the name of this metric
func (e FAbsolute) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *FAbsolute) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e FAbsolute) Type() int {
	return EventFAbsolute
}

// TypeString returns a name for this type of metric
func (e FAbsolute) TypeString() string {
	return "FAbsolute"
}

// String returns a debug-friendly representation of this metric
func (e FAbsolute) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Values: %v}", e.TypeString(), e.Name, e.Values)
}


================================================
FILE: event/fabsolute_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestFAbsoluteUpdate(t *testing.T) {
	e1 := &FAbsolute{Name: "test", Values: []float64{15.3}}
	e2 := &FAbsolute{Name: "test", Values: []float64{-10.1}}
	e3 := &FAbsolute{Name: "test", Values: []float64{8.3}}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:15.3|a", "test:-10.1|a", "test:8.3|a"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/fgauge.go
================================================
package event

import "fmt"

// FGauge - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again
type FGauge struct {
	Name  string
	Value float64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *FGauge) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value = e2.Payload().(float64)
	return nil
}

// Payload returns the aggregated value for this event
func (e FGauge) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e FGauge) Stats() []string {
	if e.Value < 0 {
		// because a leading '+' or '-' in the value of a gauge denotes a delta, to send
		// a negative gauge value we first set the gauge absolutely to 0, then send the
		// negative value as a delta from 0 (that's just how the spec works :-)
		return []string{
			fmt.Sprintf("%s:%d|g", e.Name, 0),
			fmt.Sprintf("%s:%g|g", e.Name, e.Value),
		}
	}
	return []string{fmt.Sprintf("%s:%g|g", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e FGauge) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *FGauge) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e FGauge) Type() int {
	return EventFGauge
}

// TypeString returns a name for this type of metric
func (e FGauge) TypeString() string {
	return "FGauge"
}

// String returns a debug-friendly representation of this metric
func (e FGauge) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %g}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/fgauge_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestFGaugeUpdate(t *testing.T) {
	e1 := &FGauge{Name: "test", Value: float64(15.1)}
	e2 := &FGauge{Name: "test", Value: float64(-10.1)}
	e3 := &FGauge{Name: "test", Value: float64(8.4)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:8.4|g"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}

func TestFGaugeUpdateNegative(t *testing.T) {
	e1 := &FGauge{Name: "test", Value: float64(-10.1)}
	e2 := &FGauge{Name: "test", Value: float64(-3.4)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:0|g", "test:-3.4|g"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/fgaugedelta.go
================================================
package event

import "fmt"

// FGaugeDelta - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again
type FGaugeDelta struct {
	Name  string
	Value float64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *FGaugeDelta) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value += e2.Payload().(float64)
	return nil
}

// Payload returns the aggregated value for this event
func (e FGaugeDelta) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e FGaugeDelta) Stats() []string {
	return []string{fmt.Sprintf("%s:%+g|g", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e FGaugeDelta) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *FGaugeDelta) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e FGaugeDelta) Type() int {
	return EventFGaugeDelta
}

// TypeString returns a name for this type of metric
func (e FGaugeDelta) TypeString() string {
	return "FGaugeDelta"
}

// String returns a debug-friendly representation of this metric
func (e FGaugeDelta) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %g}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/fgaugedelta_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestFGaugeDeltaUpdate(t *testing.T) {
	e1 := &FGaugeDelta{Name: "test", Value: float64(15.1)}
	e2 := &FGaugeDelta{Name: "test", Value: float64(-10.0)}
	e3 := &FGaugeDelta{Name: "test", Value: float64(15.1)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:+20.2|g"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}

func TestFGaugeDeltaUpdateNegative(t *testing.T) {
	e1 := &FGaugeDelta{Name: "test", Value: float64(-15.1)}
	e2 := &FGaugeDelta{Name: "test", Value: float64(10.0)}
	e3 := &FGaugeDelta{Name: "test", Value: float64(-15.1)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:-20.2|g"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/gauge.go
================================================
package event

import "fmt"

// Gauge - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again
type Gauge struct {
	Name  string
	Value int64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *Gauge) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value = e2.Payload().(int64)
	return nil
}

// Payload returns the aggregated value for this event
func (e Gauge) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e Gauge) Stats() []string {
	if e.Value < 0 {
		// because a leading '+' or '-' in the value of a gauge denotes a delta, to send
		// a negative gauge value we first set the gauge absolutely to 0, then send the
		// negative value as a delta from 0 (that's just how the spec works :-)
		return []string{
			fmt.Sprintf("%s:%d|g", e.Name, 0),
			fmt.Sprintf("%s:%d|g", e.Name, e.Value),
		}
	}
	return []string{fmt.Sprintf("%s:%d|g", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e Gauge) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *Gauge) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e Gauge) Type() int {
	return EventGauge
}

// TypeString returns a name for this type of metric
func (e Gauge) TypeString() string {
	return "Gauge"
}

// String returns a debug-friendly representation of this metric
func (e Gauge) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %d}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/gauge_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestGaugeUpdate(t *testing.T) {
	e1 := &Gauge{Name: "test", Value: int64(15)}
	e2 := &Gauge{Name: "test", Value: int64(-10)}
	e3 := &Gauge{Name: "test", Value: int64(8)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:8|g"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}

func TestGaugeUpdateNegative(t *testing.T) {
	e1 := &Gauge{Name: "test", Value: int64(-10)}
	e2 := &Gauge{Name: "test", Value: int64(-3)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:0|g", "test:-3|g"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/gaugedelta.go
================================================
package event

import "fmt"

// GaugeDelta - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again
type GaugeDelta struct {
	Name  string
	Value int64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *GaugeDelta) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value += e2.Payload().(int64)
	return nil
}

// Payload returns the aggregated value for this event
func (e GaugeDelta) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e GaugeDelta) Stats() []string {
	return []string{fmt.Sprintf("%s:%+d|g", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e GaugeDelta) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *GaugeDelta) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e GaugeDelta) Type() int {
	return EventGaugeDelta
}

// TypeString returns a name for this type of metric
func (e GaugeDelta) TypeString() string {
	return "GaugeDelta"
}

// String returns a debug-friendly representation of this metric
func (e GaugeDelta) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %d}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/gaugedelta_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestGaugeDeltaUpdate(t *testing.T) {
	e1 := &GaugeDelta{Name: "test", Value: int64(15)}
	e2 := &GaugeDelta{Name: "test", Value: int64(-10)}
	e3 := &GaugeDelta{Name: "test", Value: int64(15)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:+20|g"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}

func TestGaugeDeltaUpdateNegative(t *testing.T) {
	e1 := &GaugeDelta{Name: "test", Value: int64(-15)}
	e2 := &GaugeDelta{Name: "test", Value: int64(10)}
	e3 := &GaugeDelta{Name: "test", Value: int64(-15)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:-20|g"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/increment.go
================================================
package event

import "fmt"

// Increment represents a metric whose value is averaged over a minute
type Increment struct {
	Name  string
	Value int64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *Increment) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value += e2.Payload().(int64)
	return nil
}

// Payload returns the aggregated value for this event
func (e Increment) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e Increment) Stats() []string {
	return []string{fmt.Sprintf("%s:%d|c", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e Increment) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *Increment) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e Increment) Type() int {
	return EventIncr
}

// TypeString returns a name for this type of metric
func (e Increment) TypeString() string {
	return "Increment"
}

// String returns a debug-friendly representation of this metric
func (e Increment) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %d}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/increment_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestIncrementUpdate(t *testing.T) {
	e1 := &Increment{Name: "test", Value: int64(15)}
	e2 := &Increment{Name: "test", Value: int64(-10)}
	e3 := &Increment{Name: "test", Value: int64(8)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:13|c"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/interface.go
================================================
package event

// constant event type identifiers
const (
	EventIncr = iota
	EventTiming
	EventAbsolute
	EventTotal
	EventGauge
	EventGaugeDelta
	EventFGauge
	EventFGaugeDelta
	EventFAbsolute
	EventPrecisionTiming
)

// Event is an interface to a generic StatsD event, used by the buffered client collator
type Event interface {
	Stats() []string
	Type() int
	TypeString() string
	Payload() interface{}
	Update(e2 Event) error
	String() string
	Key() string
	SetKey(string)
}

// compile-time assertion to verify default events implement the Event interface
func _() {
	var _ Event = (*Absolute)(nil)        // assert *Absolute implements Event
	var _ Event = (*FAbsolute)(nil)       // assert *FAbsolute implements Event
	var _ Event = (*Gauge)(nil)           // assert *Gauge implements Event
	var _ Event = (*FGauge)(nil)          // assert *FGauge implements Event
	var _ Event = (*GaugeDelta)(nil)      // assert *GaugeDelta implements Event
	var _ Event = (*FGaugeDelta)(nil)     // assert *FGaugeDelta implements Event
	var _ Event = (*Increment)(nil)       // assert *Increment implements Event
	var _ Event = (*PrecisionTiming)(nil) // assert *PrecisionTiming implements Event
	var _ Event = (*Timing)(nil)          // assert *Timing implements Event
	var _ Event = (*Total)(nil)           // assert *Total implements Event
}


================================================
FILE: event/precisiontiming.go
================================================
package event

import (
	"fmt"
	"time"
)

// PrecisionTiming keeps min/max/avg information about a timer over a certain interval
type PrecisionTiming struct {
	Name  string
	Min   time.Duration
	Max   time.Duration
	Value time.Duration
	Count int64
}

// NewPrecisionTiming is a factory for a Timing event, setting the Count to 1 to prevent div_by_0 errors
func NewPrecisionTiming(k string, delta time.Duration) *PrecisionTiming {
	return &PrecisionTiming{Name: k, Min: delta, Max: delta, Value: delta, Count: 1}
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *PrecisionTiming) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	p := e2.Payload().(PrecisionTiming)
	e.Count += p.Count
	e.Value += p.Value
	e.Min = time.Duration(minInt64(int64(e.Min), int64(p.Min)))
	e.Max = time.Duration(maxInt64(int64(e.Max), int64(p.Min)))
	return nil
}

// Payload returns the aggregated value for this event
func (e PrecisionTiming) Payload() interface{} {
	return e
}

// Stats returns an array of StatsD events as they travel over UDP
func (e PrecisionTiming) Stats() []string {
	return []string{
		fmt.Sprintf("%s.count:%d|c", e.Name, e.Count),
		fmt.Sprintf("%s.avg:%.6f|ms", e.Name, float64(int64(e.Value)/e.Count)/1000000), // make sure e.Count != 0
		fmt.Sprintf("%s.min:%.6f|ms", e.Name, e.durationToMs(e.Min)),
		fmt.Sprintf("%s.max:%.6f|ms", e.Name, e.durationToMs(e.Max)),
	}
}

// durationToMs converts time.Duration into the corresponding value in milliseconds
func (e PrecisionTiming) durationToMs(x time.Duration) float64 {
	return float64(x) / float64(time.Millisecond)
}

// Key returns the name of this metric
func (e PrecisionTiming) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *PrecisionTiming) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e PrecisionTiming) Type() int {
	return EventPrecisionTiming
}

// TypeString returns a name for this type of metric
func (e PrecisionTiming) TypeString() string {
	return "PrecisionTiming"
}

// String returns a debug-friendly representation of this metric
func (e PrecisionTiming) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %+v}", e.TypeString(), e.Name, e.Payload())
}


================================================
FILE: event/precisiontiming_test.go
================================================
package event

import (
	"reflect"
	"testing"
	"time"
)

func TestPrecisionTimingUpdate(t *testing.T) {
	e1 := NewPrecisionTiming("test", 5*time.Microsecond)
	e2 := NewPrecisionTiming("test", 3*time.Microsecond)
	e3 := NewPrecisionTiming("test", 7*time.Microsecond)
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test.count:3|c", "test.avg:0.005000|ms", "test.min:0.003000|ms", "test.max:0.007000|ms"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/timing.go
================================================
package event

import "fmt"

// Timing keeps min/max/avg information about a timer over a certain interval
type Timing struct {
	Name  string
	Min   int64
	Max   int64
	Value int64
	Count int64
}

// NewTiming is a factory for a Timing event, setting the Count to 1 to prevent div_by_0 errors
func NewTiming(k string, delta int64) *Timing {
	return &Timing{Name: k, Min: delta, Max: delta, Value: delta, Count: 1}
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *Timing) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	p := e2.Payload().(map[string]int64)
	e.Count += p["cnt"]
	e.Value += p["val"]
	e.Min = minInt64(e.Min, p["min"])
	e.Max = maxInt64(e.Max, p["max"])
	return nil
}

// Payload returns the aggregated value for this event
func (e Timing) Payload() interface{} {
	return map[string]int64{
		"min": e.Min,
		"max": e.Max,
		"val": e.Value,
		"cnt": e.Count,
	}
}

// Stats returns an array of StatsD events as they travel over UDP
func (e Timing) Stats() []string {
	return []string{
		fmt.Sprintf("%s.count:%d|c", e.Name, e.Count),
		fmt.Sprintf("%s.avg:%d|ms", e.Name, int64(e.Value/e.Count)), // make sure e.Count != 0
		fmt.Sprintf("%s.min:%d|ms", e.Name, e.Min),
		fmt.Sprintf("%s.max:%d|ms", e.Name, e.Max),
	}
}

// Key returns the name of this metric
func (e Timing) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *Timing) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e Timing) Type() int {
	return EventTiming
}

// TypeString returns a name for this type of metric
func (e Timing) TypeString() string {
	return "Timing"
}

// String returns a debug-friendly representation of this metric
func (e Timing) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %+v}", e.TypeString(), e.Name, e.Payload())
}

func minInt64(v1, v2 int64) int64 {
	if v1 <= v2 {
		return v1
	}
	return v2
}
func maxInt64(v1, v2 int64) int64 {
	if v1 >= v2 {
		return v1
	}
	return v2
}


================================================
FILE: event/timing_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestTimingUpdate(t *testing.T) {
	e1 := NewTiming("test", 5)
	e2 := NewTiming("test", 3)
	e3 := NewTiming("test", 7)
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test.count:3|c", "test.avg:5|ms", "test.min:3|ms", "test.max:7|ms"}
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: event/total.go
================================================
package event

import "fmt"

// Total represents a metric that is continously increasing, e.g. read operations since boot
type Total struct {
	Name  string
	Value int64
}

// Update the event with metrics coming from a new one of the same type and with the same key
func (e *Total) Update(e2 Event) error {
	if e.Type() != e2.Type() {
		return fmt.Errorf("statsd event type conflict: %s vs %s ", e.String(), e2.String())
	}
	e.Value += e2.Payload().(int64)
	return nil
}

// Payload returns the aggregated value for this event
func (e Total) Payload() interface{} {
	return e.Value
}

// Stats returns an array of StatsD events as they travel over UDP
func (e Total) Stats() []string {
	return []string{fmt.Sprintf("%s:%d|t", e.Name, e.Value)}
}

// Key returns the name of this metric
func (e Total) Key() string {
	return e.Name
}

// SetKey sets the name of this metric
func (e *Total) SetKey(key string) {
	e.Name = key
}

// Type returns an integer identifier for this type of metric
func (e Total) Type() int {
	return EventTotal
}

// TypeString returns a name for this type of metric
func (e Total) TypeString() string {
	return "Total"
}

// String returns a debug-friendly representation of this metric
func (e Total) String() string {
	return fmt.Sprintf("{Type: %s, Key: %s, Value: %d}", e.TypeString(), e.Name, e.Value)
}


================================================
FILE: event/total_test.go
================================================
package event

import (
	"reflect"
	"testing"
)

func TestTotalUpdate(t *testing.T) {
	e1 := &Total{Name: "test", Value: int64(15)}
	e2 := &Total{Name: "test", Value: int64(-10)}
	e3 := &Total{Name: "test", Value: int64(8)}
	err := e1.Update(e2)
	if nil != err {
		t.Error(err)
	}
	err = e1.Update(e3)
	if nil != err {
		t.Error(err)
	}

	expected := []string{"test:13|t"} // only the last value is flushed
	actual := e1.Stats()
	if !reflect.DeepEqual(expected, actual) {
		t.Errorf("did not receive all metrics: Expected: %T %v, Actual: %T %v ", expected, expected, actual, actual)
	}
}


================================================
FILE: interface.go
================================================
package statsd

import (
	"time"

	"github.com/quipo/statsd/event"
)

// Statsd is an interface to a StatsD client (buffered/unbuffered)
type Statsd interface {
	CreateSocket() error
	CreateTCPSocket() error
	Close() error
	Incr(stat string, count int64) error
	Decr(stat string, count int64) error
	Timing(stat string, delta int64) error
	PrecisionTiming(stat string, delta time.Duration) error
	Gauge(stat string, value int64) error
	GaugeDelta(stat string, value int64) error
	Absolute(stat string, value int64) error
	Total(stat string, value int64) error

	FGauge(stat string, value float64) error
	FGaugeDelta(stat string, value float64) error
	FAbsolute(stat string, value float64) error

	SendEvents(events map[string]event.Event) error
}


================================================
FILE: mock/mockableclient.go
================================================
package mock

//
// A mockable client allowing arbitrary functions to be called statsd.Statsd methods.
//
// This is particularly helpful in unit test scenarios where it is desired to simulate calls
// without actually writing to a network or filesystem.
//
// A default implementation is provided that records all calls and can be used for verification
// in unit tests.
//
// The default implementations of these methods are no-ops, so without any further configuration,
// &MockStatsdClient{} is equivalent to statsd.NoopClient. But utility methods are also provided that
// allow recording calls for the purposes of verification during unit testing.
//

import (
	"sync"
	"time"

	"github.com/quipo/statsd/event"
)

type statelessStatsdFunction func() error
type intMetricStatsdFunction func(string, int64) error
type floatMetricStatsdFunction func(string, float64) error
type durationMetricStatsdFunction func(string, time.Duration) error
type eventsStatsdFunction func(events map[string]event.Event) error

// MockStatsdClient at its simplest provides a layer of indirection so that
// arbitrary functions can be used as the targets of calls to the Statsd interface
//
// In its basic state, it will act like a noop client.
//
// There are also helper functions that will set functions for specific
// calls to record those events to caller-provided slices.
// This is particularly helpful for unit testing that needs to verify
// that certain metrics have been recorded by the system under test

type MockStatsdClient struct {
	CreateSocketFn    statelessStatsdFunction
	CreateTCPSocketFn statelessStatsdFunction
	CloseFn           statelessStatsdFunction
	IncrFn            intMetricStatsdFunction
	DecrFn            intMetricStatsdFunction
	TimingFn          intMetricStatsdFunction
	PrecisionTimingFn durationMetricStatsdFunction
	GaugeFn           intMetricStatsdFunction
	GaugeDeltaFn      intMetricStatsdFunction
	AbsoluteFn        intMetricStatsdFunction
	TotalFn           intMetricStatsdFunction

	FGaugeFn      floatMetricStatsdFunction
	FGaugeDeltaFn floatMetricStatsdFunction
	FAbsoluteFn   floatMetricStatsdFunction

	SendEventsFn eventsStatsdFunction
}

// Implement statsd interface

func (msc *MockStatsdClient) CreateSocket() error {
	if msc.CreateSocketFn == nil {
		return nil
	}
	return msc.CreateSocketFn()
}

func (msc *MockStatsdClient) CreateTCPSocket() error {
	if msc.CreateTCPSocketFn == nil {
		return nil
	}
	return msc.CreateTCPSocketFn()
}

func (msc *MockStatsdClient) Close() error {
	if msc.CloseFn == nil {
		return nil
	}
	return msc.CloseFn()
}

func (msc *MockStatsdClient) Incr(stat string, count int64) error {
	if msc.IncrFn == nil {
		return nil
	}
	return msc.IncrFn(stat, count)
}

func (msc *MockStatsdClient) Decr(stat string, count int64) error {
	if msc.DecrFn == nil {
		return nil
	}
	return msc.DecrFn(stat, count)
}

func (msc *MockStatsdClient) Timing(stat string, delta int64) error {
	if msc.TimingFn == nil {
		return nil
	}
	return msc.TimingFn(stat, delta)
}

func (msc *MockStatsdClient) PrecisionTiming(stat string, delta time.Duration) error {
	if msc.PrecisionTimingFn == nil {
		return nil
	}
	return msc.PrecisionTimingFn(stat, delta)
}

func (msc *MockStatsdClient) Gauge(stat string, value int64) error {
	if msc.GaugeFn == nil {
		return nil
	}
	return msc.GaugeFn(stat, value)
}

func (msc *MockStatsdClient) GaugeDelta(stat string, value int64) error {
	if msc.GaugeDeltaFn == nil {
		return nil
	}
	return msc.GaugeDeltaFn(stat, value)
}

func (msc *MockStatsdClient) Absolute(stat string, value int64) error {
	if msc.AbsoluteFn == nil {
		return nil
	}
	return msc.AbsoluteFn(stat, value)
}

func (msc *MockStatsdClient) Total(stat string, value int64) error {
	if msc.TotalFn == nil {
		return nil
	}
	return msc.TotalFn(stat, value)
}

func (msc *MockStatsdClient) FGauge(stat string, value float64) error {
	if msc.FGaugeFn == nil {
		return nil
	}
	return msc.FGaugeFn(stat, value)
}

func (msc *MockStatsdClient) FGaugeDelta(stat string, value float64) error {
	if msc.FGaugeDeltaFn == nil {
		return nil
	}
	return msc.FGaugeDeltaFn(stat, value)
}

func (msc *MockStatsdClient) FAbsolute(stat string, value float64) error {
	if msc.FAbsoluteFn == nil {
		return nil
	}
	return msc.FAbsoluteFn(stat, value)
}

func (msc *MockStatsdClient) SendEvents(events map[string]event.Event) error {
	if msc.SendEventsFn == nil {
		return nil
	}
	return msc.SendEventsFn(events)
}

// Mocking helpers that record seen events for verification during unit testing

type Int64Event struct {
	MetricName string
	EventValue int64
}

type Float64Event struct {
	MetricName string
	EventValue float64
}

type DurationEvent struct {
	MetricName string
	EventValue time.Duration
}

// UnvaluedEvents are useful for recording things like calls to Close() or CreateSocket()
type UnvaluedEvent struct {
}

// Fluent-style constructors for recording events to caller-provided slices
// This means that during unit tests, one can do something like
//
// incrEvents := []statsd.Int64Event
// decrEvents := []statsd.Int64Event
// statsdClient = &MockStatsdClient{}.RecordIncrEventsTo(&incrEvents).RecordDecrEventsTo(&decrEvents)
// ... Execute code under test that records metrics
// ... Verify that incrEvents and decrEvents have seen the expected events

func (msc *MockStatsdClient) RecordCreateSocketEventsTo(createSocketEvents *[]UnvaluedEvent) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.CreateSocketFn = func() error {
		recordUnvaluedEvent(eventLock, createSocketEvents)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordCreateTCPSocketEventsTo(createTcpSocketEvents *[]UnvaluedEvent) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.CreateTCPSocketFn = func() error {
		recordUnvaluedEvent(eventLock, createTcpSocketEvents)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordCloseEventsTo(closeEvents *[]UnvaluedEvent) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.CloseFn = func() error {
		recordUnvaluedEvent(eventLock, closeEvents)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordIncrEventsTo(incrEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.IncrFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, incrEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordDecrEventsTo(decrEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.DecrFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, decrEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordTimingEventsTo(timingEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.TimingFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, timingEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordPrecisionTimingEventsTo(timingEvents *[]DurationEvent) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.PrecisionTimingFn = func(metricName string, eventValue time.Duration) error {
		recordDurationEvent(eventLock, timingEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordGaugeEventsTo(gaugeEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.GaugeFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, gaugeEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordGaugeDeltaEventsTo(gaugeDeltaEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.GaugeDeltaFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, gaugeDeltaEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordAbsoluteEventsTo(absoluteEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.AbsoluteFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, absoluteEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordTotalEventsTo(totalEvents *[]Int64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.TotalFn = func(metricName string, eventValue int64) error {
		recordInt64Event(eventLock, totalEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordFGaugeEventsTo(fgaugeEvents *[]Float64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.FGaugeFn = func(metricName string, eventValue float64) error {
		recordFloat64Event(eventLock, fgaugeEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordFGaugeDeltaEventsTo(fgaugeDeltaEvents *[]Float64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.FGaugeDeltaFn = func(metricName string, eventValue float64) error {
		recordFloat64Event(eventLock, fgaugeDeltaEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func (msc *MockStatsdClient) RecordFAbsoluteEventsTo(fabsoluteEvents *[]Float64Event) *MockStatsdClient {
	eventLock := &sync.Mutex{}
	msc.FAbsoluteFn = func(metricName string, eventValue float64) error {
		recordFloat64Event(eventLock, fabsoluteEvents, metricName, eventValue)
		return nil
	}
	return msc
}

func recordDurationEvent(eventLock sync.Locker, events *[]DurationEvent, metricName string, eventValue time.Duration) {
	newEvent := DurationEvent{
		MetricName: metricName,
		EventValue: eventValue,
	}
	eventLock.Lock()
	defer eventLock.Unlock()
	*events = append(*events, newEvent)
}

func recordFloat64Event(eventLock sync.Locker, events *[]Float64Event, metricName string, eventValue float64) {
	newEvent := Float64Event{
		MetricName: metricName,
		EventValue: eventValue,
	}
	eventLock.Lock()
	defer eventLock.Unlock()
	*events = append(*events, newEvent)
}

func recordInt64Event(eventLock sync.Locker, events *[]Int64Event, metricName string, eventValue int64) {
	newEvent := Int64Event{
		MetricName: metricName,
		EventValue: eventValue,
	}
	eventLock.Lock()
	defer eventLock.Unlock()
	*events = append(*events, newEvent)
}

func recordUnvaluedEvent(eventLock sync.Locker, events *[]UnvaluedEvent) {
	eventLock.Lock()
	defer eventLock.Unlock()
	*events = append(*events, UnvaluedEvent{})
}


================================================
FILE: mock/mockableclient_test.go
================================================
package mock

import (
	"reflect"
	"testing"

	"github.com/quipo/statsd/event"
)

func TestNoopBehavior(t *testing.T) {
	mockClient := MockStatsdClient{}
	err := mockClient.CreateSocket()
	if err != nil {
		t.Fail()
	}
	err = mockClient.CreateTCPSocket()
	if err != nil {
		t.Fail()
	}
	err = mockClient.Close()
	if err != nil {
		t.Fail()
	}
	err = mockClient.Incr("incr", 1)
	if err != nil {
		t.Fail()
	}
	err = mockClient.Decr("decr", 2)
	if err != nil {
		t.Fail()
	}
	err = mockClient.Timing("timing", 3)
	if err != nil {
		t.Fail()
	}
	err = mockClient.PrecisionTiming("precisionTiming", 4)
	if err != nil {
		t.Fail()
	}
	err = mockClient.Gauge("gauge", 5)
	if err != nil {
		t.Fail()
	}
	err = mockClient.GaugeDelta("gaugeDelta", 6)
	if err != nil {
		t.Fail()
	}
	err = mockClient.Absolute("absolute", 7)
	if err != nil {
		t.Fail()
	}
	err = mockClient.Total("total", 8)
	if err != nil {
		t.Fail()
	}

	err = mockClient.FGauge("fgauge", 10.0)
	if err != nil {
		t.Fail()
	}
	err = mockClient.FGaugeDelta("fgaugeDelta", 11.0)
	if err != nil {
		t.Fail()
	}
	err = mockClient.FAbsolute("fabsolute", 12.0)
	if err != nil {
		t.Fail()
	}

	err = mockClient.SendEvents(make(map[string]event.Event))
	if err != nil {
		t.Fail()
	}
}

func TestMockStatsdClient_RecordCreateSocketEventsTo(t *testing.T) {
	var createSocketEvents []UnvaluedEvent
	mockClient := (&MockStatsdClient{}).RecordCreateSocketEventsTo(&createSocketEvents)
	err := mockClient.CreateSocket()
	if err != nil {
		t.Logf("Got non-nil err from mock CreateSocket")
		t.Fail()
	}
	expectedEvents := []UnvaluedEvent{UnvaluedEvent{}}
	if !reflect.DeepEqual(createSocketEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, createSocketEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordCreateTCPSocketEventsTo(t *testing.T) {
	var createTCPSocketEvents []UnvaluedEvent
	mockClient := (&MockStatsdClient{}).RecordCreateTCPSocketEventsTo(&createTCPSocketEvents)
	err := mockClient.CreateTCPSocket()
	if err != nil {
		t.Logf("Got non-nil err from mock CreateTCPSocket")
		t.Fail()
	}
	expectedEvents := []UnvaluedEvent{UnvaluedEvent{}}
	if !reflect.DeepEqual(createTCPSocketEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, createTCPSocketEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordCloseEventsTo(t *testing.T) {
	var closeEvents []UnvaluedEvent
	mockClient := (&MockStatsdClient{}).RecordCloseEventsTo(&closeEvents)
	err := mockClient.Close()
	if err != nil {
		t.Logf("Got non-nil err from mock Close")
		t.Fail()
	}
	expectedEvents := []UnvaluedEvent{UnvaluedEvent{}}
	if !reflect.DeepEqual(closeEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, closeEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordIncrEventsTo(t *testing.T) {
	var incrEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordIncrEventsTo(&incrEvents)
	err := mockClient.Incr("incr", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Incr")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "incr", EventValue: 1}}
	if !reflect.DeepEqual(incrEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, incrEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_Decr(t *testing.T) {
	var decrEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordDecrEventsTo(&decrEvents)
	err := mockClient.Decr("decr", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Decr")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "decr", EventValue: 1}}
	if !reflect.DeepEqual(decrEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, decrEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_Timing(t *testing.T) {
	var timingEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordIncrEventsTo(&timingEvents)
	err := mockClient.Incr("timing", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Timing")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "timing", EventValue: 1}}
	if !reflect.DeepEqual(timingEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, timingEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordPrecisionTimingEventsTo(t *testing.T) {
	var durationEvents []DurationEvent
	mockClient := (&MockStatsdClient{}).RecordPrecisionTimingEventsTo(&durationEvents)
	err := mockClient.PrecisionTiming("precisionTiming", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock PrecisionTiming")
		t.Fail()
	}
	expectedEvents := []DurationEvent{DurationEvent{MetricName: "precisionTiming", EventValue: 1}}
	if !reflect.DeepEqual(durationEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, durationEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordGaugeEventsTo(t *testing.T) {
	var gaugeEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordGaugeEventsTo(&gaugeEvents)
	err := mockClient.Gauge("gauge", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Gauge")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "gauge", EventValue: 1}}
	if !reflect.DeepEqual(gaugeEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, gaugeEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordFGaugeDeltaEventsTo(t *testing.T) {
	var gaugeDeltaEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordGaugeDeltaEventsTo(&gaugeDeltaEvents)
	err := mockClient.GaugeDelta("gaugeDelta", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock GaugeDelta")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "gaugeDelta", EventValue: 1}}
	if !reflect.DeepEqual(gaugeDeltaEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, gaugeDeltaEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordAbsoluteEventsTo(t *testing.T) {
	var absoluteEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordAbsoluteEventsTo(&absoluteEvents)
	err := mockClient.Absolute("absolute", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Absolute")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "absolute", EventValue: 1}}
	if !reflect.DeepEqual(absoluteEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, absoluteEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordTotalEventsTo(t *testing.T) {
	var totalEvents []Int64Event
	mockClient := (&MockStatsdClient{}).RecordTotalEventsTo(&totalEvents)
	err := mockClient.Total("total", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock Total")
		t.Fail()
	}
	expectedEvents := []Int64Event{Int64Event{MetricName: "total", EventValue: 1}}
	if !reflect.DeepEqual(totalEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, totalEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordFGaugeEventsTo(t *testing.T) {
	var fgaugeEvents []Float64Event
	mockClient := (&MockStatsdClient{}).RecordFGaugeEventsTo(&fgaugeEvents)
	err := mockClient.FGauge("fgauge", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock FGauge")
		t.Fail()
	}
	expectedEvents := []Float64Event{Float64Event{MetricName: "fgauge", EventValue: 1}}
	if !reflect.DeepEqual(fgaugeEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, fgaugeEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordFGaugeDeltaEventsTo2(t *testing.T) {
	var fgaugeDeltaEvents []Float64Event
	mockClient := (&MockStatsdClient{}).RecordFGaugeDeltaEventsTo(&fgaugeDeltaEvents)
	err := mockClient.FGaugeDelta("fgaugeDelta", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock FGaugeDelta")
		t.Fail()
	}
	expectedEvents := []Float64Event{Float64Event{MetricName: "fgaugeDelta", EventValue: 1}}
	if !reflect.DeepEqual(fgaugeDeltaEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, fgaugeDeltaEvents)
		t.Fail()
	}
}

func TestMockStatsdClient_RecordFAbsoluteEventsTo(t *testing.T) {
	var fabsoluteEvents []Float64Event
	mockClient := (&MockStatsdClient{}).RecordFAbsoluteEventsTo(&fabsoluteEvents)
	err := mockClient.FAbsolute("fabsolute", 1)
	if err != nil {
		t.Logf("Got non-nil err from mock FAbsolute")
		t.Fail()
	}
	expectedEvents := []Float64Event{Float64Event{MetricName: "fabsolute", EventValue: 1}}
	if !reflect.DeepEqual(fabsoluteEvents, expectedEvents) {
		t.Logf("Expected %s, saw %s", expectedEvents, fabsoluteEvents)
		t.Fail()
	}
}

//func TestRecordingBuilders(t *testing.T) {
//	var createTcpSocketEvents []UnvaluedEvent
//	var closeEvents []UnvaluedEvent
//	var incrEvents []Int64Event
//	var decrEvents []Int64Event
//	var timingEvents []Int64Event
//	var precidionTimingEvents []DurationEvent
//	var gaugeEvents []Int64Event
//	var gaugeDeltaEvents []Int64Event
//	var absoluteEvents []Int64Event
//	var totalEvents []Int64Event
//	var fgaugeEvents []Float64Event
//	var fgaugeDeltaEvents []Float64Event
//	var fabsoluteEvents []Float64Event
//
//	mockClient := &MockStatsdClient{}.
//		RecordCreateSocketEventsTo(&createSocketEvents).
//		RecordCreateTCPSocketEventsTo(&createTcpSocketEvents).
//		RecordCloseEventsTo(&closeEvents).
//		RecordIncrEventsTo(&incrEvents).
//		RecordDecrEventsTo(&decrEvents).
//		RecordTimingEventsTo(&timingEvents).
//		RecordPrecisionTimingEventsTo(&precidionTimingEvents).
//		RecordGaugeEventsTo(&gaugeEvents).
//		RecordGaugeDeltaEventsTo(&gaugeDeltaEvents).
//		RecordAbsoluteEventsTo(&absoluteEvents).
//		RecordTotalEventsTo(&totalEvents).
//		RecordFGaugeEventsTo(&fgaugeEvents).
//		RecordFGaugeDeltaEventsTo(&fgaugeDeltaEvents).
//		RecordFAbsoluteEventsTo(&fabsoluteEvents)
//
//	err := mockClient.CreateSocket()
//	if err != nil {
//		t.Fail()
//	}
//	err = mockClient.CreateTCPSocket()
//	if err != nil {
//		t.Fail()
//	}
//	err = mockClient.Close()
//	if err != nil {
//		t.Fail()
//	}
//	err = mockClient.Incr("incr", 1)
//	if err != nil {
//		t.Fail()
//	}
//	err = mockClient.Decr("decr", 1)
//	if err != nil {
//		t.Fail()
//	}
//	err = mockClient.Timing("timing", 1)
//	if err != nil {
//		t.Fail()
//	}
//}


================================================
FILE: noopclient.go
================================================
package statsd

//@author https://github.com/wyndhblb/statsd

import (
	"time"

	"github.com/quipo/statsd/event"
)

// NoopClient implements a "no-op" statsd in case there is no statsd server
type NoopClient struct{}

// CreateSocket does nothing
func (s NoopClient) CreateSocket() error {
	return nil
}

// CreateTCPSocket does nothing
func (s NoopClient) CreateTCPSocket() error {
	return nil
}

// Close does nothing
func (s NoopClient) Close() error {
	return nil
}

// Incr does nothing
func (s NoopClient) Incr(stat string, count int64) error {
	return nil
}

// Decr does nothing
func (s NoopClient) Decr(stat string, count int64) error {
	return nil
}

// Timing does nothing
func (s NoopClient) Timing(stat string, count int64) error {
	return nil
}

// PrecisionTiming does nothing
func (s NoopClient) PrecisionTiming(stat string, delta time.Duration) error {
	return nil
}

// Gauge does nothing
func (s NoopClient) Gauge(stat string, value int64) error {
	return nil
}

// GaugeDelta does nothing
func (s NoopClient) GaugeDelta(stat string, value int64) error {
	return nil
}

// Absolute does nothing
func (s NoopClient) Absolute(stat string, value int64) error {
	return nil
}

// Total does nothing
func (s NoopClient) Total(stat string, value int64) error {
	return nil
}

// FGauge does nothing
func (s NoopClient) FGauge(stat string, value float64) error {
	return nil
}

// FGaugeDelta does nothing
func (s NoopClient) FGaugeDelta(stat string, value float64) error {
	return nil
}

// FAbsolute does nothing
func (s NoopClient) FAbsolute(stat string, value float64) error {
	return nil
}

// SendEvents does nothing
func (s NoopClient) SendEvents(events map[string]event.Event) error {
	return nil
}


================================================
FILE: stdoutclient.go
================================================
package statsd

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"github.com/quipo/statsd/event"
)

// StdoutClient implements a "no-op" statsd in case there is no statsd server
type StdoutClient struct {
	FD     *os.File
	prefix string
	Logger Logger
}

// NewStdoutClient - Factory
func NewStdoutClient(filename string, prefix string) *StdoutClient {
	var err error
	// allow %HOST% in the prefix string
	prefix = strings.Replace(prefix, "%HOST%", Hostname, 1)
	var fh *os.File
	if filename == "" {
		fh = os.Stdout
	} else {
		fh, err = os.OpenFile(filename, os.O_WRONLY, 0644)
		if nil != err {
			fmt.Printf("Cannot open file '%s' for stats output: %s\n", filename, err.Error())
		}
	}
	return &StdoutClient{
		FD:     fh,
		prefix: prefix,
		Logger: log.New(os.Stdout, "[StdoutClient] ", log.Ldate|log.Ltime),
	}
}

// CreateSocket does nothing
func (s *StdoutClient) CreateSocket() error {
	if s.FD == nil {
		s.FD = os.Stdout
	}
	return nil
}

// CreateTCPSocket does nothing
func (s *StdoutClient) CreateTCPSocket() error {
	if s.FD == nil {
		s.FD = os.Stdout
	}
	return nil
}

// Close does nothing
func (s *StdoutClient) Close() error {
	return nil
}

// Incr - Increment a counter metric. Often used to note a particular event
func (s *StdoutClient) Incr(stat string, count int64) error {
	if 0 != count {
		return s.send(stat, "%d|c", count)
	}
	return nil
}

// Decr - Decrement a counter metric. Often used to note a particular event
func (s *StdoutClient) Decr(stat string, count int64) error {
	if 0 != count {
		return s.send(stat, "%d|c", -count)
	}
	return nil
}

// Timing - Track a duration event
// the time delta must be given in milliseconds
func (s *StdoutClient) Timing(stat string, delta int64) error {
	return s.send(stat, "%d|ms", delta)
}

// PrecisionTiming - Track a duration event
// the time delta has to be a duration
func (s *StdoutClient) PrecisionTiming(stat string, delta time.Duration) error {
	return s.send(stat, "%.6f|ms", float64(delta)/float64(time.Millisecond))
}

// Gauge - Gauges are a constant data type. They are not subject to averaging,
// and they don’t change unless you change them. That is, once you set a gauge value,
// it will be a flat line on the graph until you change it again. If you specify
// delta to be true, that specifies that the gauge should be updated, not set. Due to the
// underlying protocol, you can't explicitly set a gauge to a negative number without
// first setting it to zero.
func (s *StdoutClient) Gauge(stat string, value int64) error {
	if value < 0 {
		err := s.send(stat, "%d|g", 0)
		if nil != err {
			return err
		}
		return s.send(stat, "%d|g", value)
	}
	return s.send(stat, "%d|g", value)
}

// GaugeDelta -- Send a change for a gauge
func (s *StdoutClient) GaugeDelta(stat string, value int64) error {
	// Gauge Deltas are always sent with a leading '+' or '-'. The '-' takes care of itself but the '+' must added by hand
	if value < 0 {
		return s.send(stat, "%d|g", value)
	}
	return s.send(stat, "+%d|g", value)
}

// FGauge -- Send a floating point value for a gauge
func (s *StdoutClient) FGauge(stat string, value float64) error {
	if value < 0 {
		err := s.send(stat, "%d|g", 0)
		if nil != err {
			return err
		}
		return s.send(stat, "%g|g", value)
	}
	return s.send(stat, "%g|g", value)
}

// FGaugeDelta -- Send a floating point change for a gauge
func (s *StdoutClient) FGaugeDelta(stat string, value float64) error {
	if value < 0 {
		return s.send(stat, "%g|g", value)
	}
	return s.send(stat, "+%g|g", value)
}

// Absolute - Send absolute-valued metric (not averaged/aggregated)
func (s *StdoutClient) Absolute(stat string, value int64) error {
	return s.send(stat, "%d|a", value)
}

// FAbsolute - Send absolute-valued floating point metric (not averaged/aggregated)
func (s *StdoutClient) FAbsolute(stat string, value float64) error {
	return s.send(stat, "%g|a", value)
}

// Total - Send a metric that is continously increasing, e.g. read operations since boot
func (s *StdoutClient) Total(stat string, value int64) error {
	return s.send(stat, "%d|t", value)
}

// write a UDP packet with the statsd event
func (s *StdoutClient) send(stat string, format string, value interface{}) error {
	stat = strings.Replace(stat, "%HOST%", Hostname, 1)
	// if sending tcp append a newline
	format = fmt.Sprintf("%s%s:%s\n", s.prefix, stat, format)
	_, err := fmt.Fprintf(s.FD, format, value)
	return err
}

// SendEvent - Sends stats from an event object
func (s *StdoutClient) SendEvent(e event.Event) error {
	for _, stat := range e.Stats() {
		//fmt.Printf("SENDING EVENT %s%s\n", s.prefix, strings.Replace(stat, "%HOST%", Hostname, 1))
		_, err := fmt.Fprintf(s.FD, "%s%s", s.prefix, strings.Replace(stat, "%HOST%", Hostname, 1))
		if nil != err {
			return err
		}
	}
	return nil
}

// SendEvents - Sends stats from all the event objects.
// Tries to bundle many together into one fmt.Fprintf based on UDPPayloadSize.
func (s *StdoutClient) SendEvents(events map[string]event.Event) error {
	var n int
	var stats = make([]string, 0)

	for _, e := range events {
		for _, stat := range e.Stats() {

			stat = fmt.Sprintf("%s%s", s.prefix, strings.Replace(stat, "%HOST%", Hostname, 1))
			_n := n + len(stat) + 1

			if _n > UDPPayloadSize {
				// with this last event, the UDP payload would be too big
				if _, err := fmt.Fprintf(s.FD, strings.Join(stats, "\n")); err != nil {
					return err
				}
				// reset payload after flushing, and add the last event
				stats = []string{stat}
				n = len(stat)
				continue
			}

			// can fit more into the current payload
			n = _n
			stats = append(stats, stat)
		}
	}

	if len(stats) != 0 {
		if _, err := fmt.Fprintf(s.FD, strings.Join(stats, "\n")); err != nil {
			return err
		}
	}

	return nil
}
Download .txt
gitextract_x5826k8v/

├── .travis.yml
├── LICENSE
├── README.md
├── bufferedclient.go
├── bufferedclient_test.go
├── client.go
├── client_test.go
├── event/
│   ├── absolute.go
│   ├── absolute_test.go
│   ├── fabsolute.go
│   ├── fabsolute_test.go
│   ├── fgauge.go
│   ├── fgauge_test.go
│   ├── fgaugedelta.go
│   ├── fgaugedelta_test.go
│   ├── gauge.go
│   ├── gauge_test.go
│   ├── gaugedelta.go
│   ├── gaugedelta_test.go
│   ├── increment.go
│   ├── increment_test.go
│   ├── interface.go
│   ├── precisiontiming.go
│   ├── precisiontiming_test.go
│   ├── timing.go
│   ├── timing_test.go
│   ├── total.go
│   └── total_test.go
├── interface.go
├── mock/
│   ├── mockableclient.go
│   └── mockableclient_test.go
├── noopclient.go
└── stdoutclient.go
Download .txt
SYMBOL INDEX (306 symbols across 30 files)

FILE: bufferedclient.go
  type closeRequest (line 12) | type closeRequest struct
  type StatsdBuffer (line 19) | type StatsdBuffer struct
    method CreateSocket (line 45) | func (sb *StatsdBuffer) CreateSocket() error {
    method CreateTCPSocket (line 50) | func (sb *StatsdBuffer) CreateTCPSocket() error {
    method Incr (line 55) | func (sb *StatsdBuffer) Incr(stat string, count int64) error {
    method Decr (line 63) | func (sb *StatsdBuffer) Decr(stat string, count int64) error {
    method Timing (line 71) | func (sb *StatsdBuffer) Timing(stat string, delta int64) error {
    method PrecisionTiming (line 78) | func (sb *StatsdBuffer) PrecisionTiming(stat string, delta time.Durati...
    method Gauge (line 86) | func (sb *StatsdBuffer) Gauge(stat string, value int64) error {
    method GaugeDelta (line 92) | func (sb *StatsdBuffer) GaugeDelta(stat string, value int64) error {
    method FGauge (line 98) | func (sb *StatsdBuffer) FGauge(stat string, value float64) error {
    method FGaugeDelta (line 104) | func (sb *StatsdBuffer) FGaugeDelta(stat string, value float64) error {
    method Absolute (line 110) | func (sb *StatsdBuffer) Absolute(stat string, value int64) error {
    method FAbsolute (line 116) | func (sb *StatsdBuffer) FAbsolute(stat string, value float64) error {
    method Total (line 122) | func (sb *StatsdBuffer) Total(stat string, value int64) error {
    method SendEvents (line 128) | func (sb *StatsdBuffer) SendEvents(events map[string]event.Event) error {
    method collector (line 153) | func (sb *StatsdBuffer) collector() {
    method Close (line 205) | func (sb *StatsdBuffer) Close() (err error) {
    method flush (line 222) | func (sb *StatsdBuffer) flush() (err error) {
  function NewStatsdBuffer (line 30) | func NewStatsdBuffer(interval time.Duration, client Statsd) *StatsdBuffer {
  function initMemoisedKeyMap (line 137) | func initMemoisedKeyMap() func(typ string, key string) string {

FILE: bufferedclient_test.go
  type KVint64 (line 17) | type KVint64 struct
  type KVfloat64 (line 21) | type KVfloat64 struct
  type KVint64Sorter (line 26) | type KVint64Sorter
    method Len (line 29) | func (a KVint64Sorter) Len() int      { return len(a) }
    method Swap (line 30) | func (a KVint64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    method Less (line 31) | func (a KVint64Sorter) Less(i, j int) bool {
  type KVfloat64Sorter (line 27) | type KVfloat64Sorter
    method Len (line 38) | func (a KVfloat64Sorter) Len() int      { return len(a) }
    method Swap (line 39) | func (a KVfloat64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    method Less (line 40) | func (a KVfloat64Sorter) Less(i, j int) bool {
    method Normalise (line 48) | func (a KVfloat64Sorter) Normalise(precision int) {
  function round (line 55) | func round(num float64) int {
  function toFixed (line 59) | func toFixed(num float64, precision int) float64 {
  function TestBufferedInt64 (line 66) | func TestBufferedInt64(t *testing.T) {
  function TestBufferedFloat64 (line 242) | func TestBufferedFloat64(t *testing.T) {
  function TestBufferedAbsolute (line 374) | func TestBufferedAbsolute(t *testing.T) {
  function TestBufferedFAbsolute (line 481) | func TestBufferedFAbsolute(t *testing.T) {

FILE: client.go
  type Logger (line 17) | type Logger interface
  function init (line 42) | func init() {
  type socketType (line 48) | type socketType
  constant udpSocket (line 51) | udpSocket socketType = "udp"
  constant tcpSocket (line 52) | tcpSocket socketType = "tcp"
  type StatsdClient (line 56) | type StatsdClient struct
    method String (line 76) | func (c *StatsdClient) String() string {
    method CreateSocket (line 81) | func (c *StatsdClient) CreateSocket() error {
    method CreateTCPSocket (line 92) | func (c *StatsdClient) CreateTCPSocket() error {
    method Close (line 103) | func (c *StatsdClient) Close() error {
    method Incr (line 114) | func (c *StatsdClient) Incr(stat string, count int64) error {
    method IncrWithSampling (line 119) | func (c *StatsdClient) IncrWithSampling(stat string, count int64, samp...
    method Decr (line 136) | func (c *StatsdClient) Decr(stat string, count int64) error {
    method DecrWithSampling (line 141) | func (c *StatsdClient) DecrWithSampling(stat string, count int64, samp...
    method Timing (line 159) | func (c *StatsdClient) Timing(stat string, delta int64) error {
    method TimingWithSampling (line 164) | func (c *StatsdClient) TimingWithSampling(stat string, delta int64, sa...
    method PrecisionTiming (line 178) | func (c *StatsdClient) PrecisionTiming(stat string, delta time.Duratio...
    method Gauge (line 188) | func (c *StatsdClient) Gauge(stat string, value int64) error {
    method GaugeWithSampling (line 193) | func (c *StatsdClient) GaugeWithSampling(stat string, value int64, sam...
    method GaugeDelta (line 213) | func (c *StatsdClient) GaugeDelta(stat string, value int64) error {
    method FGauge (line 222) | func (c *StatsdClient) FGauge(stat string, value float64) error {
    method FGaugeWithSampling (line 227) | func (c *StatsdClient) FGaugeWithSampling(stat string, value float64, ...
    method FGaugeDelta (line 247) | func (c *StatsdClient) FGaugeDelta(stat string, value float64) error {
    method Absolute (line 255) | func (c *StatsdClient) Absolute(stat string, value int64) error {
    method FAbsolute (line 260) | func (c *StatsdClient) FAbsolute(stat string, value float64) error {
    method Total (line 265) | func (c *StatsdClient) Total(stat string, value int64) error {
    method send (line 270) | func (c *StatsdClient) send(stat string, format string, value interfac...
    method SendEvent (line 292) | func (c *StatsdClient) SendEvent(e event.Event) error {
    method SendEvents (line 308) | func (c *StatsdClient) SendEvents(events map[string]event.Event) error {
  function NewStatsdClient (line 65) | func NewStatsdClient(addr string, prefix string) *StatsdClient {
  function checkCount (line 348) | func checkCount(c int64) error {
  function checkSampleRate (line 356) | func checkSampleRate(r float32) error {
  function shouldFire (line 364) | func shouldFire(sampleRate float32) bool {

FILE: client_test.go
  type MockNetConn (line 22) | type MockNetConn struct
    method Read (line 26) | func (mock *MockNetConn) Read(b []byte) (n int, err error) {
    method Write (line 29) | func (mock *MockNetConn) Write(b []byte) (n int, err error) {
    method Close (line 32) | func (mock MockNetConn) Close() error {
    method LocalAddr (line 36) | func (mock MockNetConn) LocalAddr() net.Addr {
    method RemoteAddr (line 39) | func (mock MockNetConn) RemoteAddr() net.Addr {
    method SetDeadline (line 42) | func (mock MockNetConn) SetDeadline(t time.Time) error {
    method SetReadDeadline (line 45) | func (mock MockNetConn) SetReadDeadline(t time.Time) error {
    method SetWriteDeadline (line 48) | func (mock MockNetConn) SetWriteDeadline(t time.Time) error {
  function TestClientInt64 (line 77) | func TestClientInt64(t *testing.T) {
  function TestClientFloat64 (line 255) | func TestClientFloat64(t *testing.T) {
  function TestClientAbsolute (line 390) | func TestClientAbsolute(t *testing.T) {
  function TestClientFAbsolute (line 496) | func TestClientFAbsolute(t *testing.T) {
  function newLocalListenerUDP (line 602) | func newLocalListenerUDP(t *testing.T) (*net.UDPConn, *net.UDPAddr) {
  function doListenUDP (line 618) | func doListenUDP(t *testing.T, conn *net.UDPConn, ch chan string, n int) {
  function doListenTCP (line 646) | func doListenTCP(t *testing.T, conn net.Listener, ch chan string, n int) {
  function newLocalListenerTCP (line 676) | func newLocalListenerTCP(t *testing.T) (string, net.Listener) {
  function TestTCP (line 685) | func TestTCP(t *testing.T) {
  function TestSendEvents (line 772) | func TestSendEvents(t *testing.T) {
  function getFreePort (line 810) | func getFreePort() int {

FILE: event/absolute.go
  type Absolute (line 7) | type Absolute struct
    method Update (line 13) | func (e *Absolute) Update(e2 Event) error {
    method Payload (line 22) | func (e Absolute) Payload() interface{} {
    method Stats (line 27) | func (e Absolute) Stats() []string {
    method Key (line 36) | func (e Absolute) Key() string {
    method SetKey (line 41) | func (e *Absolute) SetKey(key string) {
    method Type (line 46) | func (e Absolute) Type() int {
    method TypeString (line 51) | func (e Absolute) TypeString() string {
    method String (line 56) | func (e Absolute) String() string {

FILE: event/absolute_test.go
  function TestAbsoluteUpdate (line 8) | func TestAbsoluteUpdate(t *testing.T) {

FILE: event/fabsolute.go
  type FAbsolute (line 7) | type FAbsolute struct
    method Update (line 13) | func (e *FAbsolute) Update(e2 Event) error {
    method Payload (line 22) | func (e FAbsolute) Payload() interface{} {
    method Stats (line 27) | func (e FAbsolute) Stats() []string {
    method Key (line 36) | func (e FAbsolute) Key() string {
    method SetKey (line 41) | func (e *FAbsolute) SetKey(key string) {
    method Type (line 46) | func (e FAbsolute) Type() int {
    method TypeString (line 51) | func (e FAbsolute) TypeString() string {
    method String (line 56) | func (e FAbsolute) String() string {

FILE: event/fabsolute_test.go
  function TestFAbsoluteUpdate (line 8) | func TestFAbsoluteUpdate(t *testing.T) {

FILE: event/fgauge.go
  type FGauge (line 8) | type FGauge struct
    method Update (line 14) | func (e *FGauge) Update(e2 Event) error {
    method Payload (line 23) | func (e FGauge) Payload() interface{} {
    method Stats (line 28) | func (e FGauge) Stats() []string {
    method Key (line 42) | func (e FGauge) Key() string {
    method SetKey (line 47) | func (e *FGauge) SetKey(key string) {
    method Type (line 52) | func (e FGauge) Type() int {
    method TypeString (line 57) | func (e FGauge) TypeString() string {
    method String (line 62) | func (e FGauge) String() string {

FILE: event/fgauge_test.go
  function TestFGaugeUpdate (line 8) | func TestFGaugeUpdate(t *testing.T) {
  function TestFGaugeUpdateNegative (line 28) | func TestFGaugeUpdateNegative(t *testing.T) {

FILE: event/fgaugedelta.go
  type FGaugeDelta (line 8) | type FGaugeDelta struct
    method Update (line 14) | func (e *FGaugeDelta) Update(e2 Event) error {
    method Payload (line 23) | func (e FGaugeDelta) Payload() interface{} {
    method Stats (line 28) | func (e FGaugeDelta) Stats() []string {
    method Key (line 33) | func (e FGaugeDelta) Key() string {
    method SetKey (line 38) | func (e *FGaugeDelta) SetKey(key string) {
    method Type (line 43) | func (e FGaugeDelta) Type() int {
    method TypeString (line 48) | func (e FGaugeDelta) TypeString() string {
    method String (line 53) | func (e FGaugeDelta) String() string {

FILE: event/fgaugedelta_test.go
  function TestFGaugeDeltaUpdate (line 8) | func TestFGaugeDeltaUpdate(t *testing.T) {
  function TestFGaugeDeltaUpdateNegative (line 28) | func TestFGaugeDeltaUpdateNegative(t *testing.T) {

FILE: event/gauge.go
  type Gauge (line 8) | type Gauge struct
    method Update (line 14) | func (e *Gauge) Update(e2 Event) error {
    method Payload (line 23) | func (e Gauge) Payload() interface{} {
    method Stats (line 28) | func (e Gauge) Stats() []string {
    method Key (line 42) | func (e Gauge) Key() string {
    method SetKey (line 47) | func (e *Gauge) SetKey(key string) {
    method Type (line 52) | func (e Gauge) Type() int {
    method TypeString (line 57) | func (e Gauge) TypeString() string {
    method String (line 62) | func (e Gauge) String() string {

FILE: event/gauge_test.go
  function TestGaugeUpdate (line 8) | func TestGaugeUpdate(t *testing.T) {
  function TestGaugeUpdateNegative (line 28) | func TestGaugeUpdateNegative(t *testing.T) {

FILE: event/gaugedelta.go
  type GaugeDelta (line 8) | type GaugeDelta struct
    method Update (line 14) | func (e *GaugeDelta) Update(e2 Event) error {
    method Payload (line 23) | func (e GaugeDelta) Payload() interface{} {
    method Stats (line 28) | func (e GaugeDelta) Stats() []string {
    method Key (line 33) | func (e GaugeDelta) Key() string {
    method SetKey (line 38) | func (e *GaugeDelta) SetKey(key string) {
    method Type (line 43) | func (e GaugeDelta) Type() int {
    method TypeString (line 48) | func (e GaugeDelta) TypeString() string {
    method String (line 53) | func (e GaugeDelta) String() string {

FILE: event/gaugedelta_test.go
  function TestGaugeDeltaUpdate (line 8) | func TestGaugeDeltaUpdate(t *testing.T) {
  function TestGaugeDeltaUpdateNegative (line 28) | func TestGaugeDeltaUpdateNegative(t *testing.T) {

FILE: event/increment.go
  type Increment (line 6) | type Increment struct
    method Update (line 12) | func (e *Increment) Update(e2 Event) error {
    method Payload (line 21) | func (e Increment) Payload() interface{} {
    method Stats (line 26) | func (e Increment) Stats() []string {
    method Key (line 31) | func (e Increment) Key() string {
    method SetKey (line 36) | func (e *Increment) SetKey(key string) {
    method Type (line 41) | func (e Increment) Type() int {
    method TypeString (line 46) | func (e Increment) TypeString() string {
    method String (line 51) | func (e Increment) String() string {

FILE: event/increment_test.go
  function TestIncrementUpdate (line 8) | func TestIncrementUpdate(t *testing.T) {

FILE: event/interface.go
  constant EventIncr (line 5) | EventIncr = iota
  constant EventTiming (line 6) | EventTiming
  constant EventAbsolute (line 7) | EventAbsolute
  constant EventTotal (line 8) | EventTotal
  constant EventGauge (line 9) | EventGauge
  constant EventGaugeDelta (line 10) | EventGaugeDelta
  constant EventFGauge (line 11) | EventFGauge
  constant EventFGaugeDelta (line 12) | EventFGaugeDelta
  constant EventFAbsolute (line 13) | EventFAbsolute
  constant EventPrecisionTiming (line 14) | EventPrecisionTiming
  type Event (line 18) | type Event interface
  function _ (line 30) | func _() {

FILE: event/precisiontiming.go
  type PrecisionTiming (line 9) | type PrecisionTiming struct
    method Update (line 23) | func (e *PrecisionTiming) Update(e2 Event) error {
    method Payload (line 36) | func (e PrecisionTiming) Payload() interface{} {
    method Stats (line 41) | func (e PrecisionTiming) Stats() []string {
    method durationToMs (line 51) | func (e PrecisionTiming) durationToMs(x time.Duration) float64 {
    method Key (line 56) | func (e PrecisionTiming) Key() string {
    method SetKey (line 61) | func (e *PrecisionTiming) SetKey(key string) {
    method Type (line 66) | func (e PrecisionTiming) Type() int {
    method TypeString (line 71) | func (e PrecisionTiming) TypeString() string {
    method String (line 76) | func (e PrecisionTiming) String() string {
  function NewPrecisionTiming (line 18) | func NewPrecisionTiming(k string, delta time.Duration) *PrecisionTiming {

FILE: event/precisiontiming_test.go
  function TestPrecisionTimingUpdate (line 9) | func TestPrecisionTimingUpdate(t *testing.T) {

FILE: event/timing.go
  type Timing (line 6) | type Timing struct
    method Update (line 20) | func (e *Timing) Update(e2 Event) error {
    method Payload (line 33) | func (e Timing) Payload() interface{} {
    method Stats (line 43) | func (e Timing) Stats() []string {
    method Key (line 53) | func (e Timing) Key() string {
    method SetKey (line 58) | func (e *Timing) SetKey(key string) {
    method Type (line 63) | func (e Timing) Type() int {
    method TypeString (line 68) | func (e Timing) TypeString() string {
    method String (line 73) | func (e Timing) String() string {
  function NewTiming (line 15) | func NewTiming(k string, delta int64) *Timing {
  function minInt64 (line 77) | func minInt64(v1, v2 int64) int64 {
  function maxInt64 (line 83) | func maxInt64(v1, v2 int64) int64 {

FILE: event/timing_test.go
  function TestTimingUpdate (line 8) | func TestTimingUpdate(t *testing.T) {

FILE: event/total.go
  type Total (line 6) | type Total struct
    method Update (line 12) | func (e *Total) Update(e2 Event) error {
    method Payload (line 21) | func (e Total) Payload() interface{} {
    method Stats (line 26) | func (e Total) Stats() []string {
    method Key (line 31) | func (e Total) Key() string {
    method SetKey (line 36) | func (e *Total) SetKey(key string) {
    method Type (line 41) | func (e Total) Type() int {
    method TypeString (line 46) | func (e Total) TypeString() string {
    method String (line 51) | func (e Total) String() string {

FILE: event/total_test.go
  function TestTotalUpdate (line 8) | func TestTotalUpdate(t *testing.T) {

FILE: interface.go
  type Statsd (line 10) | type Statsd interface

FILE: mock/mockableclient.go
  type statelessStatsdFunction (line 24) | type statelessStatsdFunction
  type intMetricStatsdFunction (line 25) | type intMetricStatsdFunction
  type floatMetricStatsdFunction (line 26) | type floatMetricStatsdFunction
  type durationMetricStatsdFunction (line 27) | type durationMetricStatsdFunction
  type eventsStatsdFunction (line 28) | type eventsStatsdFunction
  type MockStatsdClient (line 40) | type MockStatsdClient struct
    method CreateSocket (line 62) | func (msc *MockStatsdClient) CreateSocket() error {
    method CreateTCPSocket (line 69) | func (msc *MockStatsdClient) CreateTCPSocket() error {
    method Close (line 76) | func (msc *MockStatsdClient) Close() error {
    method Incr (line 83) | func (msc *MockStatsdClient) Incr(stat string, count int64) error {
    method Decr (line 90) | func (msc *MockStatsdClient) Decr(stat string, count int64) error {
    method Timing (line 97) | func (msc *MockStatsdClient) Timing(stat string, delta int64) error {
    method PrecisionTiming (line 104) | func (msc *MockStatsdClient) PrecisionTiming(stat string, delta time.D...
    method Gauge (line 111) | func (msc *MockStatsdClient) Gauge(stat string, value int64) error {
    method GaugeDelta (line 118) | func (msc *MockStatsdClient) GaugeDelta(stat string, value int64) error {
    method Absolute (line 125) | func (msc *MockStatsdClient) Absolute(stat string, value int64) error {
    method Total (line 132) | func (msc *MockStatsdClient) Total(stat string, value int64) error {
    method FGauge (line 139) | func (msc *MockStatsdClient) FGauge(stat string, value float64) error {
    method FGaugeDelta (line 146) | func (msc *MockStatsdClient) FGaugeDelta(stat string, value float64) e...
    method FAbsolute (line 153) | func (msc *MockStatsdClient) FAbsolute(stat string, value float64) err...
    method SendEvents (line 160) | func (msc *MockStatsdClient) SendEvents(events map[string]event.Event)...
    method RecordCreateSocketEventsTo (line 197) | func (msc *MockStatsdClient) RecordCreateSocketEventsTo(createSocketEv...
    method RecordCreateTCPSocketEventsTo (line 206) | func (msc *MockStatsdClient) RecordCreateTCPSocketEventsTo(createTcpSo...
    method RecordCloseEventsTo (line 215) | func (msc *MockStatsdClient) RecordCloseEventsTo(closeEvents *[]Unvalu...
    method RecordIncrEventsTo (line 224) | func (msc *MockStatsdClient) RecordIncrEventsTo(incrEvents *[]Int64Eve...
    method RecordDecrEventsTo (line 233) | func (msc *MockStatsdClient) RecordDecrEventsTo(decrEvents *[]Int64Eve...
    method RecordTimingEventsTo (line 242) | func (msc *MockStatsdClient) RecordTimingEventsTo(timingEvents *[]Int6...
    method RecordPrecisionTimingEventsTo (line 251) | func (msc *MockStatsdClient) RecordPrecisionTimingEventsTo(timingEvent...
    method RecordGaugeEventsTo (line 260) | func (msc *MockStatsdClient) RecordGaugeEventsTo(gaugeEvents *[]Int64E...
    method RecordGaugeDeltaEventsTo (line 269) | func (msc *MockStatsdClient) RecordGaugeDeltaEventsTo(gaugeDeltaEvents...
    method RecordAbsoluteEventsTo (line 278) | func (msc *MockStatsdClient) RecordAbsoluteEventsTo(absoluteEvents *[]...
    method RecordTotalEventsTo (line 287) | func (msc *MockStatsdClient) RecordTotalEventsTo(totalEvents *[]Int64E...
    method RecordFGaugeEventsTo (line 296) | func (msc *MockStatsdClient) RecordFGaugeEventsTo(fgaugeEvents *[]Floa...
    method RecordFGaugeDeltaEventsTo (line 305) | func (msc *MockStatsdClient) RecordFGaugeDeltaEventsTo(fgaugeDeltaEven...
    method RecordFAbsoluteEventsTo (line 314) | func (msc *MockStatsdClient) RecordFAbsoluteEventsTo(fabsoluteEvents *...
  type Int64Event (line 169) | type Int64Event struct
  type Float64Event (line 174) | type Float64Event struct
  type DurationEvent (line 179) | type DurationEvent struct
  type UnvaluedEvent (line 185) | type UnvaluedEvent struct
  function recordDurationEvent (line 323) | func recordDurationEvent(eventLock sync.Locker, events *[]DurationEvent,...
  function recordFloat64Event (line 333) | func recordFloat64Event(eventLock sync.Locker, events *[]Float64Event, m...
  function recordInt64Event (line 343) | func recordInt64Event(eventLock sync.Locker, events *[]Int64Event, metri...
  function recordUnvaluedEvent (line 353) | func recordUnvaluedEvent(eventLock sync.Locker, events *[]UnvaluedEvent) {

FILE: mock/mockableclient_test.go
  function TestNoopBehavior (line 10) | func TestNoopBehavior(t *testing.T) {
  function TestMockStatsdClient_RecordCreateSocketEventsTo (line 76) | func TestMockStatsdClient_RecordCreateSocketEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordCreateTCPSocketEventsTo (line 91) | func TestMockStatsdClient_RecordCreateTCPSocketEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordCloseEventsTo (line 106) | func TestMockStatsdClient_RecordCloseEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordIncrEventsTo (line 121) | func TestMockStatsdClient_RecordIncrEventsTo(t *testing.T) {
  function TestMockStatsdClient_Decr (line 136) | func TestMockStatsdClient_Decr(t *testing.T) {
  function TestMockStatsdClient_Timing (line 151) | func TestMockStatsdClient_Timing(t *testing.T) {
  function TestMockStatsdClient_RecordPrecisionTimingEventsTo (line 166) | func TestMockStatsdClient_RecordPrecisionTimingEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordGaugeEventsTo (line 181) | func TestMockStatsdClient_RecordGaugeEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordFGaugeDeltaEventsTo (line 196) | func TestMockStatsdClient_RecordFGaugeDeltaEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordAbsoluteEventsTo (line 211) | func TestMockStatsdClient_RecordAbsoluteEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordTotalEventsTo (line 226) | func TestMockStatsdClient_RecordTotalEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordFGaugeEventsTo (line 241) | func TestMockStatsdClient_RecordFGaugeEventsTo(t *testing.T) {
  function TestMockStatsdClient_RecordFGaugeDeltaEventsTo2 (line 256) | func TestMockStatsdClient_RecordFGaugeDeltaEventsTo2(t *testing.T) {
  function TestMockStatsdClient_RecordFAbsoluteEventsTo (line 271) | func TestMockStatsdClient_RecordFAbsoluteEventsTo(t *testing.T) {

FILE: noopclient.go
  type NoopClient (line 12) | type NoopClient struct
    method CreateSocket (line 15) | func (s NoopClient) CreateSocket() error {
    method CreateTCPSocket (line 20) | func (s NoopClient) CreateTCPSocket() error {
    method Close (line 25) | func (s NoopClient) Close() error {
    method Incr (line 30) | func (s NoopClient) Incr(stat string, count int64) error {
    method Decr (line 35) | func (s NoopClient) Decr(stat string, count int64) error {
    method Timing (line 40) | func (s NoopClient) Timing(stat string, count int64) error {
    method PrecisionTiming (line 45) | func (s NoopClient) PrecisionTiming(stat string, delta time.Duration) ...
    method Gauge (line 50) | func (s NoopClient) Gauge(stat string, value int64) error {
    method GaugeDelta (line 55) | func (s NoopClient) GaugeDelta(stat string, value int64) error {
    method Absolute (line 60) | func (s NoopClient) Absolute(stat string, value int64) error {
    method Total (line 65) | func (s NoopClient) Total(stat string, value int64) error {
    method FGauge (line 70) | func (s NoopClient) FGauge(stat string, value float64) error {
    method FGaugeDelta (line 75) | func (s NoopClient) FGaugeDelta(stat string, value float64) error {
    method FAbsolute (line 80) | func (s NoopClient) FAbsolute(stat string, value float64) error {
    method SendEvents (line 85) | func (s NoopClient) SendEvents(events map[string]event.Event) error {

FILE: stdoutclient.go
  type StdoutClient (line 14) | type StdoutClient struct
    method CreateSocket (line 42) | func (s *StdoutClient) CreateSocket() error {
    method CreateTCPSocket (line 50) | func (s *StdoutClient) CreateTCPSocket() error {
    method Close (line 58) | func (s *StdoutClient) Close() error {
    method Incr (line 63) | func (s *StdoutClient) Incr(stat string, count int64) error {
    method Decr (line 71) | func (s *StdoutClient) Decr(stat string, count int64) error {
    method Timing (line 80) | func (s *StdoutClient) Timing(stat string, delta int64) error {
    method PrecisionTiming (line 86) | func (s *StdoutClient) PrecisionTiming(stat string, delta time.Duratio...
    method Gauge (line 96) | func (s *StdoutClient) Gauge(stat string, value int64) error {
    method GaugeDelta (line 108) | func (s *StdoutClient) GaugeDelta(stat string, value int64) error {
    method FGauge (line 117) | func (s *StdoutClient) FGauge(stat string, value float64) error {
    method FGaugeDelta (line 129) | func (s *StdoutClient) FGaugeDelta(stat string, value float64) error {
    method Absolute (line 137) | func (s *StdoutClient) Absolute(stat string, value int64) error {
    method FAbsolute (line 142) | func (s *StdoutClient) FAbsolute(stat string, value float64) error {
    method Total (line 147) | func (s *StdoutClient) Total(stat string, value int64) error {
    method send (line 152) | func (s *StdoutClient) send(stat string, format string, value interfac...
    method SendEvent (line 161) | func (s *StdoutClient) SendEvent(e event.Event) error {
    method SendEvents (line 174) | func (s *StdoutClient) SendEvents(events map[string]event.Event) error {
  function NewStdoutClient (line 21) | func NewStdoutClient(filename string, prefix string) *StdoutClient {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (122K chars).
[
  {
    "path": ".travis.yml",
    "chars": 39,
    "preview": "language: go\n\ngo:\n  - 1.9.x\n  - master\n"
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Lorenzo Alberton\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 4267,
    "preview": "# StatsD client (Golang)\n\n[![Build Status](https://travis-ci.org/quipo/statsd.png?branch=master)](https://travis-ci.org/"
  },
  {
    "path": "bufferedclient.go",
    "chars": 6932,
    "preview": "package statsd\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\n// request to close the buffered stat"
  },
  {
    "path": "bufferedclient_test.go",
    "chars": 13763,
    "preview": "package statsd\n\nimport (\n\t\"math\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\n// -----"
  },
  {
    "path": "client.go",
    "chars": 9579,
    "preview": "package statsd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd"
  },
  {
    "path": "client_test.go",
    "chars": 18954,
    "preview": "package statsd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\""
  },
  {
    "path": "event/absolute.go",
    "chars": 1517,
    "preview": "package event\n\nimport \"fmt\"\n\n// Absolute is a metric that is not averaged/aggregated.\n// We keep each value distinct and"
  },
  {
    "path": "event/absolute_test.go",
    "chars": 635,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestAbsoluteUpdate(t *testing.T) {\n\te1 := &Absolute{Name: \"test\", "
  },
  {
    "path": "event/fabsolute.go",
    "chars": 1533,
    "preview": "package event\n\nimport \"fmt\"\n\n// FAbsolute is a metric that is not averaged/aggregated.\n// We keep each value distinct an"
  },
  {
    "path": "event/fabsolute_test.go",
    "chars": 657,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFAbsoluteUpdate(t *testing.T) {\n\te1 := &FAbsolute{Name: \"test\""
  },
  {
    "path": "event/fgauge.go",
    "chars": 1852,
    "preview": "package event\n\nimport \"fmt\"\n\n// FGauge - Gauges are a constant data type. They are not subject to averaging,\n// and they"
  },
  {
    "path": "event/fgauge_test.go",
    "chars": 1077,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFGaugeUpdate(t *testing.T) {\n\te1 := &FGauge{Name: \"test\", Valu"
  },
  {
    "path": "event/fgaugedelta.go",
    "chars": 1549,
    "preview": "package event\n\nimport \"fmt\"\n\n// FGaugeDelta - Gauges are a constant data type. They are not subject to averaging,\n// and"
  },
  {
    "path": "event/fgaugedelta_test.go",
    "chars": 1149,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFGaugeDeltaUpdate(t *testing.T) {\n\te1 := &FGaugeDelta{Name: \"t"
  },
  {
    "path": "event/gauge.go",
    "chars": 1836,
    "preview": "package event\n\nimport \"fmt\"\n\n// Gauge - Gauges are a constant data type. They are not subject to averaging,\n// and they "
  },
  {
    "path": "event/gauge_test.go",
    "chars": 1046,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGaugeUpdate(t *testing.T) {\n\te1 := &Gauge{Name: \"test\", Value:"
  },
  {
    "path": "event/gaugedelta.go",
    "chars": 1533,
    "preview": "package event\n\nimport \"fmt\"\n\n// GaugeDelta - Gauges are a constant data type. They are not subject to averaging,\n// and "
  },
  {
    "path": "event/gaugedelta_test.go",
    "chars": 1113,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGaugeDeltaUpdate(t *testing.T) {\n\te1 := &GaugeDelta{Name: \"tes"
  },
  {
    "path": "event/increment.go",
    "chars": 1352,
    "preview": "package event\n\nimport \"fmt\"\n\n// Increment represents a metric whose value is averaged over a minute\ntype Increment struc"
  },
  {
    "path": "event/increment_test.go",
    "chars": 604,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestIncrementUpdate(t *testing.T) {\n\te1 := &Increment{Name: \"test\""
  },
  {
    "path": "event/interface.go",
    "chars": 1335,
    "preview": "package event\n\n// constant event type identifiers\nconst (\n\tEventIncr = iota\n\tEventTiming\n\tEventAbsolute\n\tEventTotal\n\tEve"
  },
  {
    "path": "event/precisiontiming.go",
    "chars": 2391,
    "preview": "package event\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// PrecisionTiming keeps min/max/avg information about a timer over a certain "
  },
  {
    "path": "event/precisiontiming_test.go",
    "chars": 673,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestPrecisionTimingUpdate(t *testing.T) {\n\te1 := NewPrecis"
  },
  {
    "path": "event/timing.go",
    "chars": 2145,
    "preview": "package event\n\nimport \"fmt\"\n\n// Timing keeps min/max/avg information about a timer over a certain interval\ntype Timing s"
  },
  {
    "path": "event/timing_test.go",
    "chars": 557,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTimingUpdate(t *testing.T) {\n\te1 := NewTiming(\"test\", 5)\n\te2 :"
  },
  {
    "path": "event/total.go",
    "chars": 1335,
    "preview": "package event\n\nimport \"fmt\"\n\n// Total represents a metric that is continously increasing, e.g. read operations since boo"
  },
  {
    "path": "event/total_test.go",
    "chars": 588,
    "preview": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTotalUpdate(t *testing.T) {\n\te1 := &Total{Name: \"test\", Value:"
  },
  {
    "path": "interface.go",
    "chars": 747,
    "preview": "package statsd\n\nimport (\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\n// Statsd is an interface to a StatsD client (buff"
  },
  {
    "path": "mock/mockableclient.go",
    "chars": 10367,
    "preview": "package mock\n\n//\n// A mockable client allowing arbitrary functions to be called statsd.Statsd methods.\n//\n// This is par"
  },
  {
    "path": "mock/mockableclient_test.go",
    "chars": 9971,
    "preview": "package mock\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\nfunc TestNoopBehavior(t *testing.T) {\n"
  },
  {
    "path": "noopclient.go",
    "chars": 1719,
    "preview": "package statsd\n\n//@author https://github.com/wyndhblb/statsd\n\nimport (\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\n// N"
  },
  {
    "path": "stdoutclient.go",
    "chars": 5765,
    "preview": "package statsd\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\n// StdoutClient imp"
  }
]

About this extraction

This page contains the full source code of the quipo/statsd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (107.1 KB), approximately 32.4k tokens, and a symbol index with 306 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!