[
  {
    "path": ".travis.yml",
    "content": "language: go\n\ngo:\n  - 1.9.x\n  - master\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Lorenzo Alberton\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# StatsD client (Golang)\n\n[![Build Status](https://travis-ci.org/quipo/statsd.png?branch=master)](https://travis-ci.org/quipo/statsd) \n[![GoDoc](https://godoc.org/github.com/quipo/statsd?status.png)](http://godoc.org/github.com/quipo/statsd)\n\n## Introduction\n\nGo Client library for [StatsD](https://github.com/etsy/statsd/). Contains a direct and a buffered client.\nThe buffered version will hold and aggregate values for the same key in memory before flushing them at the defined frequency.\n\nThis 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.\n\n## Installation\n\n    go get github.com/quipo/statsd\n\n## Supported event types\n\n* `Increment` - Count occurrences per second/minute of a specific event\n* `Decrement` - Count occurrences per second/minute of a specific event\n* `Timing` - To track a duration event\n* `PrecisionTiming` - To track a duration event\n* `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\n* `GaugeDelta` (int) / `FGaugeDelta` (float) - Same as above, but as a delta change to the previous value rather than a new absolute value\n* `Absolute` (int) / `FAbsolute` (float) - Absolute-valued metric (not averaged/aggregated)\n* `Total` - Continously increasing value, e.g. read operations since boot\n\n\n## Sample usage\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd\"\n)\n\nfunc main() {\n\t// init\n\tprefix := \"myproject.\"\n\tstatsdclient := statsd.NewStatsdClient(\"localhost:8125\", prefix)\n\terr := statsdclient.CreateSocket()\n\tif nil != err {\n\t\tlog.Println(err)\n\t\tos.Exit(1)\n\t}\n\tinterval := time.Second * 2 // aggregate stats and flush every 2 seconds\n\tstats := statsd.NewStatsdBuffer(interval, statsdclient)\n\tdefer stats.Close()\n\n\t// not buffered: send immediately\n\tstatsdclient.Incr(\"mymetric\", 4)\n\n\t// buffered: aggregate in memory before flushing\n\tstats.Incr(\"mymetric\", 1)\n\tstats.Incr(\"mymetric\", 3)\n\tstats.Incr(\"mymetric\", 1)\n\tstats.Incr(\"mymetric\", 1)\n}\n```\n\nThe string `%HOST%` in the metric name will automatically be replaced with the hostname of the server the event is sent from.\n\n\n## [Changelog](https://github.com/quipo/statsd/releases)\n\n* `HEAD`:\n\n    *\n\n* [`v.1.4.0`](https://github.com/quipo/statsd/releases/tag/1.4.0)\n\n    * Fixed behaviour of Gauge with positive numbers: the previous behaviour was the same as GaugeDelta\n      (FGauge already had the correct behaviour)\n    * Added more tests\n    * Small optimisation: replace string formatting with concatenation (thanks to @agnivade)\n\n* [`v.1.3.0`](https://github.com/quipo/statsd/releases/tag/v.1.3.0):\n\n    * Added stdout client (\"echo\" service for debugging)\n    * Fixed [issue #23](https://github.com/quipo/statsd/issues/23): GaugeDelta event Stats() should not send an absolute value of 0\n    * 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)\n    * Fixed FGaugeDelta with negative value not to send a 0 value first (it mistakenly had the same implementation of FGauge)\n    * Added many tests\n    * Added compile-time checks that the default events implement the Event interface\n\n* [`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))\n*  [`v.1.1.0`](https://github.com/quipo/statsd/releases/tag/1.1.0):\n\n    * Added `SendEvents` function to `Statsd` interface;\n    * Using interface in buffered client constructor;\n    * Added/Fixed tests\n\n* [`v.1.0.0`](https://github.com/quipo/statsd/releases/tag/1.0.0): First stable release\n* `v.0.0.9`: Added memoization to reduce memory allocations\n* `v.0.0.8`: Pre-release\n\n## Author\n\nLorenzo Alberton\n\n* Web: [http://alberton.info](http://alberton.info)\n* Twitter: [@lorenzoalberton](https://twitter.com/lorenzoalberton)\n* Linkedin: [/in/lorenzoalberton](https://www.linkedin.com/in/lorenzoalberton)\n\n\n## Copyright\n\nSee [LICENSE](LICENSE) document\n"
  },
  {
    "path": "bufferedclient.go",
    "content": "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 statsd collector\ntype closeRequest struct {\n\treply chan error\n}\n\n// StatsdBuffer is a client library to aggregate events in memory before\n// flushing aggregates to StatsD, useful if the frequency of events is extremely high\n// and sampling is not desirable\ntype StatsdBuffer struct {\n\tstatsd        Statsd\n\tflushInterval time.Duration\n\teventChannel  chan event.Event\n\tevents        map[string]event.Event\n\tcloseChannel  chan closeRequest\n\tLogger        Logger\n\tVerbose       bool\n}\n\n// NewStatsdBuffer Factory\nfunc NewStatsdBuffer(interval time.Duration, client Statsd) *StatsdBuffer {\n\tsb := &StatsdBuffer{\n\t\tflushInterval: interval,\n\t\tstatsd:        client,\n\t\teventChannel:  make(chan event.Event, 100),\n\t\tevents:        make(map[string]event.Event),\n\t\tcloseChannel:  make(chan closeRequest),\n\t\tLogger:        log.New(os.Stdout, \"[BufferedStatsdClient] \", log.Ldate|log.Ltime),\n\t\tVerbose:       true,\n\t}\n\tgo sb.collector()\n\treturn sb\n}\n\n// CreateSocket creates a UDP connection to a StatsD server\nfunc (sb *StatsdBuffer) CreateSocket() error {\n\treturn sb.statsd.CreateSocket()\n}\n\n// CreateTCPSocket creates a TCP connection to a StatsD server\nfunc (sb *StatsdBuffer) CreateTCPSocket() error {\n\treturn sb.statsd.CreateTCPSocket()\n}\n\n// Incr - Increment a counter metric. Often used to note a particular event\nfunc (sb *StatsdBuffer) Incr(stat string, count int64) error {\n\tif 0 != count {\n\t\tsb.eventChannel <- &event.Increment{Name: stat, Value: count}\n\t}\n\treturn nil\n}\n\n// Decr - Decrement a counter metric. Often used to note a particular event\nfunc (sb *StatsdBuffer) Decr(stat string, count int64) error {\n\tif 0 != count {\n\t\tsb.eventChannel <- &event.Increment{Name: stat, Value: -count}\n\t}\n\treturn nil\n}\n\n// Timing - Track a duration event\nfunc (sb *StatsdBuffer) Timing(stat string, delta int64) error {\n\tsb.eventChannel <- event.NewTiming(stat, delta)\n\treturn nil\n}\n\n// PrecisionTiming - Track a duration event\n// the time delta has to be a duration\nfunc (sb *StatsdBuffer) PrecisionTiming(stat string, delta time.Duration) error {\n\tsb.eventChannel <- event.NewPrecisionTiming(stat, delta)\n\treturn nil\n}\n\n// Gauge - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again\nfunc (sb *StatsdBuffer) Gauge(stat string, value int64) error {\n\tsb.eventChannel <- &event.Gauge{Name: stat, Value: value}\n\treturn nil\n}\n\n// GaugeDelta records a delta from the previous value (as int64)\nfunc (sb *StatsdBuffer) GaugeDelta(stat string, value int64) error {\n\tsb.eventChannel <- &event.GaugeDelta{Name: stat, Value: value}\n\treturn nil\n}\n\n// FGauge is a Gauge working with float64 values\nfunc (sb *StatsdBuffer) FGauge(stat string, value float64) error {\n\tsb.eventChannel <- &event.FGauge{Name: stat, Value: value}\n\treturn nil\n}\n\n// FGaugeDelta records a delta from the previous value (as float64)\nfunc (sb *StatsdBuffer) FGaugeDelta(stat string, value float64) error {\n\tsb.eventChannel <- &event.FGaugeDelta{Name: stat, Value: value}\n\treturn nil\n}\n\n// Absolute - Send absolute-valued metric (not averaged/aggregated)\nfunc (sb *StatsdBuffer) Absolute(stat string, value int64) error {\n\tsb.eventChannel <- &event.Absolute{Name: stat, Values: []int64{value}}\n\treturn nil\n}\n\n// FAbsolute - Send absolute-valued metric (not averaged/aggregated)\nfunc (sb *StatsdBuffer) FAbsolute(stat string, value float64) error {\n\tsb.eventChannel <- &event.FAbsolute{Name: stat, Values: []float64{value}}\n\treturn nil\n}\n\n// Total - Send a metric that is continously increasing, e.g. read operations since boot\nfunc (sb *StatsdBuffer) Total(stat string, value int64) error {\n\tsb.eventChannel <- &event.Total{Name: stat, Value: value}\n\treturn nil\n}\n\n// SendEvents - Sends stats from all the event objects.\nfunc (sb *StatsdBuffer) SendEvents(events map[string]event.Event) error {\n\tfor _, e := range events {\n\t\tsb.eventChannel <- e\n\t}\n\treturn nil\n}\n\n// avoid too many allocations by memoizing the \"type|key\" pair for an event\n// @see https://gobyexample.com/closures\nfunc initMemoisedKeyMap() func(typ string, key string) string {\n\tm := make(map[string]map[string]string)\n\treturn func(typ string, key string) string {\n\t\tif _, ok := m[typ]; !ok {\n\t\t\tm[typ] = make(map[string]string)\n\t\t}\n\t\tk, ok := m[typ][key]\n\t\tif !ok {\n\t\t\tm[typ][key] = typ + \"|\" + key\n\t\t\treturn m[typ][key]\n\t\t}\n\t\treturn k // memoized value\n\t}\n}\n\n// handle flushes and updates in one single thread (instead of locking the events map)\nfunc (sb *StatsdBuffer) collector() {\n\t// on a panic event, flush all the pending stats before panicking\n\tdefer func(sb *StatsdBuffer) {\n\t\tif r := recover(); r != nil {\n\t\t\tsb.Logger.Println(\"Caught panic, flushing stats before throwing the panic again\")\n\t\t\terr := sb.flush()\n\t\t\tif nil != err {\n\t\t\t\tsb.Logger.Println(\"Error flushing stats\", err.Error())\n\t\t\t}\n\t\t\tpanic(r)\n\t\t}\n\t}(sb)\n\n\tkeyFor := initMemoisedKeyMap() // avoid allocations (https://gobyexample.com/closures)\n\n\tticker := time.NewTicker(sb.flushInterval)\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\t//sb.Logger.Println(\"Flushing stats\")\n\t\t\terr := sb.flush()\n\t\t\tif nil != err {\n\t\t\t\tsb.Logger.Println(\"Error flushing stats\", err.Error())\n\t\t\t}\n\t\tcase e := <-sb.eventChannel:\n\t\t\t//sb.Logger.Println(\"Received \", e.String())\n\t\t\t// issue #28: unable to use Incr and PrecisionTiming with the same key (also fixed #27)\n\t\t\tk := keyFor(e.TypeString(), e.Key()) // avoid allocations\n\t\t\tif e2, ok := sb.events[k]; ok {\n\t\t\t\t//sb.Logger.Println(\"Updating existing event\")\n\t\t\t\terr := e2.Update(e)\n\t\t\t\tif nil != err {\n\t\t\t\t\tsb.Logger.Println(\"Error updating stats\", err.Error())\n\t\t\t\t}\n\t\t\t\tsb.events[k] = e2\n\t\t\t} else {\n\t\t\t\t//sb.Logger.Println(\"Adding new event\")\n\t\t\t\tsb.events[k] = e\n\t\t\t}\n\t\tcase c := <-sb.closeChannel:\n\t\t\tif sb.Verbose {\n\t\t\t\tsb.Logger.Println(\"Asked to terminate. Flushing stats before returning.\")\n\t\t\t}\n\t\t\tc.reply <- sb.flush()\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Close sends a close event to the collector asking to stop & flush pending stats\n// and closes the statsd client\nfunc (sb *StatsdBuffer) Close() (err error) {\n\t// 1. send a close event to the collector\n\treq := closeRequest{reply: make(chan error)}\n\tsb.closeChannel <- req\n\t// 2. wait for the collector to drain the queue and respond\n\terr = <-req.reply\n\t// 3. close the statsd client\n\terr2 := sb.statsd.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn err2\n}\n\n// send the events to StatsD and reset them.\n// This function is NOT thread-safe, so it must only be invoked synchronously\n// from within the collector() goroutine\nfunc (sb *StatsdBuffer) flush() (err error) {\n\tn := len(sb.events)\n\tif n == 0 {\n\t\treturn nil\n\t}\n\tif err := sb.statsd.SendEvents(sb.events); err != nil {\n\t\tsb.Logger.Println(err)\n\t\treturn err\n\t}\n\tsb.events = make(map[string]event.Event)\n\n\treturn nil\n}\n"
  },
  {
    "path": "bufferedclient_test.go",
    "content": "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// -------------------------------------------------------------------\n\ntype KVint64 struct {\n\tKey   string\n\tValue int64\n}\ntype KVfloat64 struct {\n\tKey   string\n\tValue float64\n}\n\ntype KVint64Sorter []KVint64\ntype KVfloat64Sorter []KVfloat64\n\nfunc (a KVint64Sorter) Len() int      { return len(a) }\nfunc (a KVint64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a KVint64Sorter) Less(i, j int) bool {\n\tif a[i].Key == a[j].Key {\n\t\treturn a[i].Value < a[j].Value\n\t}\n\treturn a[i].Key < a[j].Key\n}\n\nfunc (a KVfloat64Sorter) Len() int      { return len(a) }\nfunc (a KVfloat64Sorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a KVfloat64Sorter) Less(i, j int) bool {\n\tif a[i].Key == a[j].Key {\n\t\treturn a[i].Value < a[j].Value\n\t}\n\treturn a[i].Key < a[j].Key\n}\n\n// Normalise the number of decimal places for easier comparisons in the tests\nfunc (a KVfloat64Sorter) Normalise(precision int) {\n\tfor k, v := range a {\n\t\tv.Value = toFixed(v.Value, precision)\n\t\ta[k] = v\n\t}\n}\n\nfunc round(num float64) int {\n\treturn int(num + math.Copysign(0.5, num))\n}\n\nfunc toFixed(num float64, precision int) float64 {\n\toutput := math.Pow(10, float64(precision))\n\treturn float64(round(num*output)) / output\n}\n\n// -------------------------------------------------------------------\n\nfunc TestBufferedInt64(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexTotal := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tfunction string\n\t\tsuffix   string\n\t\tinput    []KVint64\n\t\texpected []KVint64\n\t}{\n\t\t{\n\t\t\tname:     \"total\",\n\t\t\tfunction: \"total\",\n\t\t\tsuffix:   \"t\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"x:b:c\", 5},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"x:b:c\", 5},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"gauge\",\n\t\t\tfunction: \"gauge\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", 2}, // this should override the previous one\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 2},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"gaugedelta\",\n\t\t\tfunction: \"gaugedelta\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", +5},\n\t\t\t\t{\"d:e:f\", -2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", +1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 3},\n\t\t\t\t{\"d:e:f\", -2},\n\t\t\t\t{\"g.h.i\", +1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"increment\",\n\t\t\tfunction: \"increment\",\n\t\t\tsuffix:   \"c\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 3},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\t\t\tbuffered := NewStatsdBuffer(time.Millisecond*20, client)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = buffered.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer buffered.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\tswitch tc.function { // send metric\n\t\t\t\tcase \"total\":\n\t\t\t\t\terr = buffered.Total(entry.Key, entry.Value)\n\t\t\t\tcase \"gauge\":\n\t\t\t\t\terr = buffered.Gauge(entry.Key, entry.Value)\n\t\t\t\tcase \"gaugedelta\":\n\t\t\t\t\terr = buffered.GaugeDelta(entry.Key, entry.Value)\n\t\t\t\tcase \"increment\":\n\t\t\t\t\tif entry.Value < 0 {\n\t\t\t\t\t\terr = buffered.Decr(entry.Key, int64(math.Abs(float64(entry.Value))))\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = buffered.Incr(entry.Key, entry.Value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual []KVint64\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexTotal.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseInt(vv[2], 10, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(KVint64Sorter(actual))\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestBufferedFloat64(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexTotal := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+(?:\\.\\d+)?)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tfunction string\n\t\tsuffix   string\n\t\tinput    KVfloat64Sorter\n\t\texpected KVfloat64Sorter\n\t}{\n\t\t{\n\t\t\tname:     \"fgauge\",\n\t\t\tfunction: \"fgauge\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.3},\n\t\t\t\t{\"a:b:c\", 2.2}, // this should override the previous one\n\t\t\t\t{\"g.h.i\", 1.2},\n\t\t\t\t{\"zz.%HOST%\", 1.1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 2.2},\n\t\t\t\t{\"d:e:f\", 2.3},\n\t\t\t\t{\"g.h.i\", 1.2},\n\t\t\t\t{\"zz.\" + hostname, 1.1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"fgaugedelta\",\n\t\t\tfunction: \"fgaugedelta\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", +5.1},\n\t\t\t\t{\"d:e:f\", -2.2},\n\t\t\t\t{\"a:b:c\", -2.1},\n\t\t\t\t{\"g.h.i\", +1.3},\n\t\t\t\t{\"zz.%HOST%\", 1.4}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 3.0},\n\t\t\t\t{\"d:e:f\", -2.2},\n\t\t\t\t{\"g.h.i\", +1.3},\n\t\t\t\t{\"zz.\" + hostname, 1.4}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\t\t\tbuffered := NewStatsdBuffer(time.Millisecond*20, client)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = buffered.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer buffered.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\tswitch tc.function { // send metric\n\t\t\t\tcase \"fgauge\":\n\t\t\t\t\terr = buffered.FGauge(entry.Key, entry.Value)\n\t\t\t\tcase \"fgaugedelta\":\n\t\t\t\t\terr = buffered.FGaugeDelta(entry.Key, entry.Value)\n\t\t\t\t}\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual KVfloat64Sorter\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexTotal.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseFloat(vv[2], 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tactual.Normalise(2) // keep 2 decimal digits\n\t\t\tsort.Sort(actual)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: Expected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestBufferedAbsolute(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexAbsolute := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tsuffix   string\n\t\tinput    KVint64Sorter\n\t\texpected KVint64Sorter\n\t}{\n\t\t{\n\t\t\tname:   \"absolute\",\n\t\t\tsuffix: \"a\",\n\t\t\tinput: KVint64Sorter{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", 8},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVint64Sorter{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"a:b:c\", 8},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\t\t\tbuffered := NewStatsdBuffer(time.Millisecond*20, client)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = buffered.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer buffered.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\terr = buffered.Absolute(entry.Key, entry.Value)\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual KVint64Sorter\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexAbsolute.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseInt(vv[2], 10, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(actual)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: \\nExpected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestBufferedFAbsolute(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexAbsolute := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+(?:\\.\\d+)?)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tsuffix   string\n\t\tinput    KVfloat64Sorter\n\t\texpected KVfloat64Sorter\n\t}{\n\t\t{\n\t\t\tname:   \"fabsolute\",\n\t\t\tsuffix: \"a\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.1},\n\t\t\t\t{\"x:b:c\", 5.1},\n\t\t\t\t{\"g.h.i\", 1.1},\n\t\t\t\t{\"zz.%HOST%\", 1.5}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.1},\n\t\t\t\t{\"g.h.i\", 1.1},\n\t\t\t\t{\"x:b:c\", 5.1},\n\t\t\t\t{\"zz.\" + hostname, 1.5}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\t\t\tbuffered := NewStatsdBuffer(time.Millisecond*20, client)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = buffered.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer buffered.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\terr = buffered.FAbsolute(entry.Key, entry.Value)\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual KVfloat64Sorter\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexAbsolute.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseFloat(vv[2], 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: toFixed(v, 2)})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(actual)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: \\nExpected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "client.go",
    "content": "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/event\"\n)\n\n// Logger interface compatible with log.Logger\ntype Logger interface {\n\tPrintln(v ...interface{})\n}\n\n// UDPPayloadSize is the number of bytes to send at one go through the udp socket.\n// SendEvents will try to pack as many events into one udp packet.\n// Change this value as per network capabilities\n// For example to change to 16KB\n//  import \"github.com/quipo/statsd\"\n//  func init() {\n//   statsd.UDPPayloadSize = 16 * 1024\n//  }\nvar UDPPayloadSize = 512\n\n// Hostname is exported so clients can set it to something different than the default\nvar Hostname string\n\nvar errNotConnected = fmt.Errorf(\"cannot send stats, not connected to StatsD server\")\n\n// errors\nvar (\n\tErrInvalidCount      = errors.New(\"count is less than 0\")\n\tErrInvalidSampleRate = errors.New(\"sample rate is larger than 1 or less then 0\")\n)\n\nfunc init() {\n\tif host, err := os.Hostname(); nil == err {\n\t\tHostname = host\n\t}\n}\n\ntype socketType string\n\nconst (\n\tudpSocket socketType = \"udp\"\n\ttcpSocket socketType = \"tcp\"\n)\n\n// StatsdClient is a client library to send events to StatsD\ntype StatsdClient struct {\n\tconn     net.Conn\n\taddr     string\n\tprefix   string\n\tsockType socketType\n\tLogger   Logger\n}\n\n// NewStatsdClient - Factory\nfunc NewStatsdClient(addr string, prefix string) *StatsdClient {\n\t// allow %HOST% in the prefix string\n\tprefix = strings.Replace(prefix, \"%HOST%\", Hostname, 1)\n\treturn &StatsdClient{\n\t\taddr:   addr,\n\t\tprefix: prefix,\n\t\tLogger: log.New(os.Stdout, \"[StatsdClient] \", log.Ldate|log.Ltime),\n\t}\n}\n\n// String returns the StatsD server address\nfunc (c *StatsdClient) String() string {\n\treturn c.addr\n}\n\n// CreateSocket creates a UDP connection to a StatsD server\nfunc (c *StatsdClient) CreateSocket() error {\n\tconn, err := net.DialTimeout(string(udpSocket), c.addr, 5*time.Second)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.conn = conn\n\tc.sockType = udpSocket\n\treturn nil\n}\n\n// CreateTCPSocket creates a TCP connection to a StatsD server\nfunc (c *StatsdClient) CreateTCPSocket() error {\n\tconn, err := net.DialTimeout(string(tcpSocket), c.addr, 5*time.Second)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.conn = conn\n\tc.sockType = tcpSocket\n\treturn nil\n}\n\n// Close the UDP connection\nfunc (c *StatsdClient) Close() error {\n\tif nil == c.conn {\n\t\treturn nil\n\t}\n\treturn c.conn.Close()\n}\n\n// See statsd data types here: http://statsd.readthedocs.org/en/latest/types.html\n// or also https://github.com/b/statsd_spec\n\n// Incr - Increment a counter metric. Often used to note a particular event\nfunc (c *StatsdClient) Incr(stat string, count int64) error {\n\treturn c.IncrWithSampling(stat, count, 1)\n}\n\n// IncrWithSampling - Increment a counter metric with sampling between 0 and 1\nfunc (c *StatsdClient) IncrWithSampling(stat string, count int64, sampleRate float32) error {\n\tif err := checkSampleRate(sampleRate); err != nil {\n\t\treturn err\n\t}\n\n\tif !shouldFire(sampleRate) {\n\t\treturn nil // ignore this call\n\t}\n\n\tif err := checkCount(count); err != nil {\n\t\treturn err\n\t}\n\n\treturn c.send(stat, \"%d|c\", count, sampleRate)\n}\n\n// Decr - Decrement a counter metric. Often used to note a particular event\nfunc (c *StatsdClient) Decr(stat string, count int64) error {\n\treturn c.DecrWithSampling(stat, count, 1)\n}\n\n// DecrWithSampling - Decrement a counter metric with sampling between 0 and 1\nfunc (c *StatsdClient) DecrWithSampling(stat string, count int64, sampleRate float32) error {\n\tif err := checkSampleRate(sampleRate); err != nil {\n\t\treturn err\n\t}\n\n\tif !shouldFire(sampleRate) {\n\t\treturn nil // ignore this call\n\t}\n\n\tif err := checkCount(count); err != nil {\n\t\treturn err\n\t}\n\n\treturn c.send(stat, \"%d|c\", -count, sampleRate)\n}\n\n// Timing - Track a duration event\n// the time delta must be given in milliseconds\nfunc (c *StatsdClient) Timing(stat string, delta int64) error {\n\treturn c.TimingWithSampling(stat, delta, 1)\n}\n\n// TimingWithSampling - Track a duration event\nfunc (c *StatsdClient) TimingWithSampling(stat string, delta int64, sampleRate float32) error {\n\tif err := checkSampleRate(sampleRate); err != nil {\n\t\treturn err\n\t}\n\n\tif !shouldFire(sampleRate) {\n\t\treturn nil // ignore this call\n\t}\n\n\treturn c.send(stat, \"%d|ms\", delta, sampleRate)\n}\n\n// PrecisionTiming - Track a duration event\n// the time delta has to be a duration\nfunc (c *StatsdClient) PrecisionTiming(stat string, delta time.Duration) error {\n\treturn c.send(stat, \"%.6f|ms\", float64(delta)/float64(time.Millisecond), 1)\n}\n\n// Gauge - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again. If you specify\n// delta to be true, that specifies that the gauge should be updated, not set. Due to the\n// underlying protocol, you can't explicitly set a gauge to a negative number without\n// first setting it to zero.\nfunc (c *StatsdClient) Gauge(stat string, value int64) error {\n\treturn c.GaugeWithSampling(stat, value, 1)\n}\n\n// GaugeWithSampling - Gauges are a constant data type.\nfunc (c *StatsdClient) GaugeWithSampling(stat string, value int64, sampleRate float32) error {\n\tif err := checkSampleRate(sampleRate); err != nil {\n\t\treturn err\n\t}\n\n\tif !shouldFire(sampleRate) {\n\t\treturn nil // ignore this call\n\t}\n\n\tif value < 0 {\n\t\terr := c.send(stat, \"%d|g\", 0, 1)\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn c.send(stat, \"%d|g\", value, sampleRate)\n}\n\n// GaugeDelta -- Send a change for a gauge\nfunc (c *StatsdClient) GaugeDelta(stat string, value int64) error {\n\t// Gauge Deltas are always sent with a leading '+' or '-'. The '-' takes care of itself but the '+' must added by hand\n\tif value < 0 {\n\t\treturn c.send(stat, \"%d|g\", value, 1)\n\t}\n\treturn c.send(stat, \"+%d|g\", value, 1)\n}\n\n// FGauge -- Send a floating point value for a gauge\nfunc (c *StatsdClient) FGauge(stat string, value float64) error {\n\treturn c.FGaugeWithSampling(stat, value, 1)\n}\n\n// FGaugeWithSampling - Gauges are a constant data type.\nfunc (c *StatsdClient) FGaugeWithSampling(stat string, value float64, sampleRate float32) error {\n\tif err := checkSampleRate(sampleRate); err != nil {\n\t\treturn err\n\t}\n\n\tif !shouldFire(sampleRate) {\n\t\treturn nil\n\t}\n\n\tif value < 0 {\n\t\terr := c.send(stat, \"%d|g\", 0, 1)\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn c.send(stat, \"%g|g\", value, sampleRate)\n}\n\n// FGaugeDelta -- Send a floating point change for a gauge\nfunc (c *StatsdClient) FGaugeDelta(stat string, value float64) error {\n\tif value < 0 {\n\t\treturn c.send(stat, \"%g|g\", value, 1)\n\t}\n\treturn c.send(stat, \"+%g|g\", value, 1)\n}\n\n// Absolute - Send absolute-valued metric (not averaged/aggregated)\nfunc (c *StatsdClient) Absolute(stat string, value int64) error {\n\treturn c.send(stat, \"%d|a\", value, 1)\n}\n\n// FAbsolute - Send absolute-valued floating point metric (not averaged/aggregated)\nfunc (c *StatsdClient) FAbsolute(stat string, value float64) error {\n\treturn c.send(stat, \"%g|a\", value, 1)\n}\n\n// Total - Send a metric that is continously increasing, e.g. read operations since boot\nfunc (c *StatsdClient) Total(stat string, value int64) error {\n\treturn c.send(stat, \"%d|t\", value, 1)\n}\n\n// write a UDP packet with the statsd event\nfunc (c *StatsdClient) send(stat string, format string, value interface{}, sampleRate float32) error {\n\tif c.conn == nil {\n\t\treturn errNotConnected\n\t}\n\n\tstat = strings.Replace(stat, \"%HOST%\", Hostname, 1)\n\tmetricString := c.prefix + stat + \":\" + fmt.Sprintf(format, value)\n\n\tif sampleRate != 1 {\n\t\tmetricString = fmt.Sprintf(\"%s|@%f\", metricString, sampleRate)\n\t}\n\n\t// if sending tcp append a newline\n\tif c.sockType == tcpSocket {\n\t\tmetricString += \"\\n\"\n\t}\n\n\t_, err := fmt.Fprint(c.conn, metricString)\n\treturn err\n}\n\n// SendEvent - Sends stats from an event object\nfunc (c *StatsdClient) SendEvent(e event.Event) error {\n\tif c.conn == nil {\n\t\treturn errNotConnected\n\t}\n\tfor _, stat := range e.Stats() {\n\t\t//fmt.Printf(\"SENDING EVENT %s%s\\n\", c.prefix, strings.Replace(stat, \"%HOST%\", Hostname, 1))\n\t\t_, err := fmt.Fprintf(c.conn, \"%s%s\", c.prefix, strings.Replace(stat, \"%HOST%\", Hostname, 1))\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// SendEvents - Sends stats from all the event objects.\n// Tries to bundle many together into one fmt.Fprintf based on UDPPayloadSize.\nfunc (c *StatsdClient) SendEvents(events map[string]event.Event) error {\n\tif c.conn == nil {\n\t\treturn errNotConnected\n\t}\n\n\tvar n int\n\tvar stats = make([]string, 0)\n\n\tfor _, e := range events {\n\t\tfor _, stat := range e.Stats() {\n\n\t\t\tstat = c.prefix + strings.Replace(stat, \"%HOST%\", Hostname, 1)\n\t\t\t_n := n + len(stat) + 1\n\n\t\t\tif _n > UDPPayloadSize {\n\t\t\t\t// with this last event, the UDP payload would be too big\n\t\t\t\tif _, err := fmt.Fprintf(c.conn, strings.Join(stats, \"\\n\")+\"\\n\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// reset payload after flushing, and add the last event\n\t\t\t\tstats = []string{stat}\n\t\t\t\tn = len(stat)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// can fit more into the current payload\n\t\t\tn = _n\n\t\t\tstats = append(stats, stat)\n\t\t}\n\t}\n\n\tif len(stats) != 0 {\n\t\tif _, err := fmt.Fprintf(c.conn, strings.Join(stats, \"\\n\")+\"\\n\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc checkCount(c int64) error {\n\tif c <= 0 {\n\t\treturn ErrInvalidCount\n\t}\n\n\treturn nil\n}\n\nfunc checkSampleRate(r float32) error {\n\tif r < 0 || r > 1 {\n\t\treturn ErrInvalidSampleRate\n\t}\n\n\treturn nil\n}\n\nfunc shouldFire(sampleRate float32) bool {\n\tif sampleRate == 1 {\n\t\treturn true\n\t}\n\n\tr := rand.New(rand.NewSource(time.Now().Unix()))\n\n\treturn r.Float32() <= sampleRate\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "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\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\n// MockNetConn is a mock for net.Conn\ntype MockNetConn struct {\n\tbuf bytes.Buffer\n}\n\nfunc (mock *MockNetConn) Read(b []byte) (n int, err error) {\n\treturn mock.buf.Read(b)\n}\nfunc (mock *MockNetConn) Write(b []byte) (n int, err error) {\n\treturn mock.buf.Write(append(b, '\\n'))\n}\nfunc (mock MockNetConn) Close() error {\n\tmock.buf.Truncate(0)\n\treturn nil\n}\nfunc (mock MockNetConn) LocalAddr() net.Addr {\n\treturn nil\n}\nfunc (mock MockNetConn) RemoteAddr() net.Addr {\n\treturn nil\n}\nfunc (mock MockNetConn) SetDeadline(t time.Time) error {\n\treturn nil\n}\nfunc (mock MockNetConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\nfunc (mock MockNetConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n\n/*\n// TODO: use this function instead mocking net.Conn\n// usage: client, server := GetTestConnection(\"tcp\", t)\n// usage: client, server := GetTestConnection(\"udp\", t)\nfunc GetTestConnection(connType string, t *testing.T) (client, server net.Conn) {\n\tln, err := net.Listen(connType, \"127.0.0.1\")\n\tif nil != err {\n\t\tt.Error(\"TCP errpr:\", err)\n\t}\n\tgo func() {\n\t\tdefer ln.Close()\n\t\tserver, err = ln.Accept()\n\t\tif nil != err {\n\t\t\tt.Error(\"TCP Accept errpr:\", err)\n\t\t}\n\t}()\n\n\tclient, err = net.Dial(connType, ln.Addr().String())\n\tif nil != err {\n\t\tt.Error(\"TCP Dial error:\", err)\n\t}\n\treturn client, server\n}\n*/\n\nfunc TestClientInt64(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexTotal := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tfunction string\n\t\tsuffix   string\n\t\tinput    []KVint64\n\t\texpected []KVint64\n\t}{\n\t\t{\n\t\t\tname:     \"total\",\n\t\t\tfunction: \"total\",\n\t\t\tsuffix:   \"t\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"x:b:c\", 5},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"x:b:c\", 5},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"gauge\",\n\t\t\tfunction: \"gauge\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"a:b:c\", 2},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"gaugedelta\",\n\t\t\tfunction: \"gaugedelta\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", +5},\n\t\t\t\t{\"d:e:f\", -2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", +1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", +5},\n\t\t\t\t{\"d:e:f\", -2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", +1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"increment\",\n\t\t\tfunction: \"increment\",\n\t\t\tsuffix:   \"c\",\n\t\t\tinput: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: []KVint64{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", -2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = client.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer client.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\tswitch tc.function { // send metric\n\t\t\t\tcase \"total\":\n\t\t\t\t\terr = client.Total(entry.Key, entry.Value)\n\t\t\t\tcase \"gauge\":\n\t\t\t\t\terr = client.Gauge(entry.Key, entry.Value)\n\t\t\t\tcase \"gaugedelta\":\n\t\t\t\t\terr = client.GaugeDelta(entry.Key, entry.Value)\n\t\t\t\tcase \"increment\":\n\t\t\t\t\tif entry.Value < 0 {\n\t\t\t\t\t\terr = client.Decr(entry.Key, int64(math.Abs(float64(entry.Value))))\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = client.Incr(entry.Key, entry.Value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual []KVint64\n\t\t\tfor batch := range ch {\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexTotal.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseInt(vv[2], 10, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(KVint64Sorter(actual))\n\t\t\tsort.Sort(KVint64Sorter(tc.expected))\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: \\nExpected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestClientFloat64(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexTotal := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+(?:\\.\\d+)?)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tfunction string\n\t\tsuffix   string\n\t\tinput    KVfloat64Sorter\n\t\texpected KVfloat64Sorter\n\t}{\n\t\t{\n\t\t\tname:     \"fgauge\",\n\t\t\tfunction: \"fgauge\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.3},\n\t\t\t\t{\"a:b:c\", -2.2},\n\t\t\t\t{\"g.h.i\", 1.2},\n\t\t\t\t{\"zz.%HOST%\", 1.1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.3},\n\t\t\t\t{\"a:b:c\", 0},\n\t\t\t\t{\"a:b:c\", -2.2},\n\t\t\t\t{\"g.h.i\", 1.2},\n\t\t\t\t{\"zz.\" + hostname, 1.1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"fgaugedelta\",\n\t\t\tfunction: \"fgaugedelta\",\n\t\t\tsuffix:   \"g\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", +5.1},\n\t\t\t\t{\"d:e:f\", -2.2},\n\t\t\t\t{\"a:b:c\", -2.1},\n\t\t\t\t{\"g.h.i\", +1.3},\n\t\t\t\t{\"zz.%HOST%\", 1.4}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", +5.1},\n\t\t\t\t{\"d:e:f\", -2.2},\n\t\t\t\t{\"a:b:c\", -2.1},\n\t\t\t\t{\"g.h.i\", +1.3},\n\t\t\t\t{\"zz.\" + hostname, 1.4}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = client.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\t//defer client.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\tswitch tc.function { // send metric\n\t\t\t\tcase \"fgauge\":\n\t\t\t\t\terr = client.FGauge(entry.Key, entry.Value)\n\t\t\t\tcase \"fgaugedelta\":\n\t\t\t\t\terr = client.FGaugeDelta(entry.Key, entry.Value)\n\t\t\t\t}\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tclient.Close()\n\n\t\t\treceived := 0\n\t\t\tvar actual KVfloat64Sorter\n\t\t\tfor batch := range ch {\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexTotal.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseFloat(vv[2], 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tactual.Normalise(2) // keep 2 decimal digits\n\t\t\tsort.Sort(actual)\n\t\t\tsort.Sort(tc.expected)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: Expected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestClientAbsolute(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexAbsolute := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tsuffix   string\n\t\tinput    KVint64Sorter\n\t\texpected KVint64Sorter\n\t}{\n\t\t{\n\t\t\tname:   \"absolute\",\n\t\t\tsuffix: \"a\",\n\t\t\tinput: KVint64Sorter{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"a:b:c\", 8},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.%HOST%\", 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVint64Sorter{\n\t\t\t\t{\"a:b:c\", 5},\n\t\t\t\t{\"a:b:c\", 8},\n\t\t\t\t{\"d:e:f\", 2},\n\t\t\t\t{\"g.h.i\", 1},\n\t\t\t\t{\"zz.\" + hostname, 1}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = client.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer client.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\terr = client.Absolute(entry.Key, entry.Value)\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual KVint64Sorter\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexAbsolute.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseInt(vv[2], 10, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVint64{Key: vv[1][len(prefix):], Value: v})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(actual)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: \\nExpected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc TestClientFAbsolute(t *testing.T) {\n\thostname, err := os.Hostname()\n\tif nil != err {\n\t\tt.Fatal(\"Cannot read host name:\", err)\n\t}\n\n\tregexAbsolute := regexp.MustCompile(`^(.*)\\:([+\\-]?\\d+(?:\\.\\d+)?)\\|(\\w).*$`)\n\tprefix := \"myproject.\"\n\n\ttt := []struct {\n\t\tname     string\n\t\tsuffix   string\n\t\tinput    KVfloat64Sorter\n\t\texpected KVfloat64Sorter\n\t}{\n\t\t{\n\t\t\tname:   \"fabsolute\",\n\t\t\tsuffix: \"a\",\n\t\t\tinput: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.1},\n\t\t\t\t{\"x:b:c\", 5.1},\n\t\t\t\t{\"g.h.i\", 1.1},\n\t\t\t\t{\"zz.%HOST%\", 1.5}, // also test %HOST% replacement\n\t\t\t},\n\t\t\texpected: KVfloat64Sorter{\n\t\t\t\t{\"a:b:c\", 5.2},\n\t\t\t\t{\"d:e:f\", 2.1},\n\t\t\t\t{\"g.h.i\", 1.1},\n\t\t\t\t{\"x:b:c\", 5.1},\n\t\t\t\t{\"zz.\" + hostname, 1.5}, // also test %HOST% replacement\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\tln, udpAddr := newLocalListenerUDP(t)\n\t\t\tdefer ln.Close()\n\n\t\t\tt.Log(\"Starting new UDP listener at\", udpAddr.String())\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\tclient := NewStatsdClient(udpAddr.String(), prefix)\n\n\t\t\tch := make(chan string)\n\n\t\t\tgo doListenUDP(t, ln, ch, len(tc.expected))\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\t\terr = client.CreateSocket()\n\t\t\tif nil != err {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer client.Close()\n\n\t\t\tfor _, entry := range tc.input {\n\t\t\t\terr = client.FAbsolute(entry.Key, entry.Value)\n\t\t\t\tif nil != err {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treceived := 0\n\t\t\tvar actual KVfloat64Sorter\n\t\t\tfor received < len(tc.expected) {\n\t\t\t\tbatch := <-ch\n\t\t\t\tfor _, x := range strings.Split(batch, \"\\n\") {\n\t\t\t\t\tx = strings.TrimSpace(x)\n\t\t\t\t\tif \"\" == x {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\treceived++\n\t\t\t\t\tvv := regexAbsolute.FindStringSubmatch(x)\n\t\t\t\t\t//t.Log(vv, x)\n\t\t\t\t\tif len(vv) < 4 {\n\t\t\t\t\t\tt.Error(\"Expecting more tokens\", len(vv))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif vv[3] != tc.suffix {\n\t\t\t\t\t\tt.Errorf(\"Metric without expected suffix: expected '%s', actual '%s'\", tc.suffix, vv[3])\n\t\t\t\t\t}\n\t\t\t\t\tv, err := strconv.ParseFloat(vv[2], 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tactual = append(actual, KVfloat64{Key: vv[1][len(prefix):], Value: toFixed(v, 2)})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Sort(actual)\n\n\t\t\tif !reflect.DeepEqual(tc.expected, actual) {\n\t\t\t\tt.Errorf(\"did not receive all metrics: \\nExpected: \\n%T %v, \\nActual: \\n%T %v \", tc.expected, tc.expected, actual, actual)\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc newLocalListenerUDP(t *testing.T) (*net.UDPConn, *net.UDPAddr) {\n\taddr := fmt.Sprintf(\":%d\", getFreePort())\n\tudpAddr, err := net.ResolveUDPAddr(\"udp\", addr)\n\tif err != nil {\n\t\tt.Error(\"UDP error:\", err)\n\t\treturn nil, nil\n\t}\n\tln, err := net.ListenUDP(\"udp\", udpAddr)\n\tif err != nil {\n\t\tt.Error(\"UDP Listen error:\", err)\n\t\treturn ln, udpAddr\n\t}\n\tt.Logf(\"Started new local UDP listener @ %s\\n\", udpAddr)\n\treturn ln, udpAddr\n}\n\nfunc doListenUDP(t *testing.T, conn *net.UDPConn, ch chan string, n int) {\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\n\tfor n > 0 {\n\t\t// Handle the connection in a new goroutine.\n\t\t// The loop then returns to accepting, so that\n\t\t// multiple connections may be served concurrently.\n\t\tgo func(c *net.UDPConn, ch chan string, wg *sync.WaitGroup) {\n\t\t\tt.Logf(\"Reading from UDP socket @ %s\\n\", conn.LocalAddr().String())\n\t\t\tbuffer := make([]byte, 1024)\n\t\t\tsize, err := c.Read(buffer)\n\t\t\t// size, address, err := sock.ReadFrom(buffer) <- This starts printing empty and nil values below immediatly\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"Error reading from UDP socket. Buffer: %s, Size: %d, Error: %s\\n\", string(buffer), size, err)\n\t\t\t\t//t.Fatal(err)\n\t\t\t}\n\t\t\tt.Logf(\"Read buffer: \\n------------------\\n%s\\n------------------\\n* Size: %d\\n\", string(buffer), size)\n\t\t\tch <- string(buffer[:size])\n\t\t\twg.Done()\n\t\t}(conn, ch, &wg)\n\t\tn--\n\t}\n\twg.Wait()\n\tclose(ch)\n\tt.Logf(\"Finished listening on UDP socket @ %s\\n\", conn.LocalAddr().String())\n}\n\nfunc doListenTCP(t *testing.T, conn net.Listener, ch chan string, n int) {\n\tfor n > 0 { // read n non-empty lines from TCP socket\n\t\tt.Logf(\"doListenTCP iteration\")\n\t\tclient, err := conn.Accept()\n\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 1024)\n\t\tsize, err := client.Read(buf)\n\t\tif err != nil {\n\t\t\tif err.Error() == \"EOF\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\tt.Logf(\"Read from TCP socket:\\n----------\\n%s\\n----------\\n\", string(buf))\n\t\tfor _, s := range bytes.Split(buf[:size], []byte{'\\n'}) {\n\t\t\tif len(s) > 0 {\n\t\t\t\tn--\n\t\t\t\tch <- string(s)\n\t\t\t}\n\t\t}\n\t}\n\tclose(ch)\n}\n\nfunc newLocalListenerTCP(t *testing.T) (string, net.Listener) {\n\taddr := fmt.Sprintf(\"127.0.0.1:%d\", getFreePort())\n\tln, err := net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn addr, ln\n}\n\nfunc TestTCP(t *testing.T) {\n\taddr, ln := newLocalListenerTCP(t)\n\tdefer ln.Close()\n\n\tt.Log(\"Starting new TCP listener at\", addr)\n\ttime.Sleep(50 * time.Millisecond)\n\n\tprefix := \"myproject.\"\n\tclient := NewStatsdClient(addr, prefix)\n\n\tch := make(chan string)\n\n\ts := map[string]int64{\n\t\t\"a:b:c\": 5,\n\t\t\"d:e:f\": 2,\n\t\t\"x:b:c\": 5,\n\t\t\"g.h.i\": 1,\n\t}\n\n\texpected := make(map[string]int64)\n\tfor k, v := range s {\n\t\texpected[k] = v\n\t}\n\n\t// also test %HOST% replacement\n\ts[\"zz.%HOST%\"] = 1\n\thostname, err := os.Hostname()\n\texpected[\"zz.\"+hostname] = 1\n\tif nil != err {\n\t\tt.Error(\"Cannot read host name:\", err.Error())\n\t}\n\n\tt.Logf(\"Sending stats to TCP Socket\")\n\terr = client.CreateTCPSocket()\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\tdefer client.Close()\n\n\tfor k, v := range s {\n\t\terr = client.Total(k, v)\n\t\tif nil != err {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\ttime.Sleep(60 * time.Millisecond)\n\n\tgo doListenTCP(t, ln, ch, len(s))\n\ttime.Sleep(50 * time.Millisecond)\n\n\tactual := make(map[string]int64)\n\n\tre := regexp.MustCompile(`^(.*)\\:(\\d+)\\|(\\w).*$`)\n\n\tfor i := len(s); i > 0; i-- {\n\t\t//t.Logf(\"ITERATION %d\\n\", i)\n\t\tx, open := <-ch\n\t\tif !open {\n\t\t\t//t.Logf(\"CLOSED _____\")\n\t\t\tbreak\n\t\t}\n\t\tx = strings.TrimSpace(x)\n\t\tif \"\" == x {\n\t\t\t//t.Logf(\"EMPTY STRING *****\")\n\t\t\tbreak\n\t\t}\n\t\t//fmt.Println(x)\n\t\tif !strings.HasPrefix(x, prefix) {\n\t\t\tt.Errorf(\"Metric without expected prefix: expected '%s', actual '%s'\", prefix, x)\n\t\t\tbreak\n\t\t}\n\t\tvv := re.FindStringSubmatch(x)\n\t\tif vv[3] != \"t\" {\n\t\t\tt.Errorf(\"Metric without expected suffix: expected 't', actual '%s'\", vv[3])\n\t\t}\n\t\tv, err := strconv.ParseInt(vv[2], 10, 64)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tactual[vv[1][len(prefix):]] = v\n\t}\n\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \\n\", expected, expected, actual, actual)\n\t}\n}\n\nfunc TestSendEvents(t *testing.T) {\n\tc := NewStatsdClient(\"127.0.0.1:1201\", \"test\")\n\tc.conn = &MockNetConn{} // mock connection\n\n\t// override with a small size\n\tUDPPayloadSize = 40\n\n\te1 := &event.Increment{Name: \"test1\", Value: 123}\n\te2 := &event.Increment{Name: \"test2\", Value: 432}\n\te3 := &event.Increment{Name: \"test3\", Value: 111}\n\te4 := &event.Gauge{Name: \"test4\", Value: 12435}\n\n\tevents := map[string]event.Event{\n\t\t\"test1\": e1,\n\t\t\"test2\": e2,\n\t\t\"test3\": e3,\n\t\t\"test4\": e4,\n\t}\n\n\terr := c.SendEvents(events)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\tb1 := make([]byte, UDPPayloadSize*3)\n\tn, err2 := c.conn.Read(b1)\n\tif nil != err2 {\n\t\tt.Error(err2)\n\t}\n\tcleanPayload := strings.Replace(strings.TrimSpace(string(b1[:n])), \"\\n\\n\", \"\\n\", -1)\n\tnStats := len(strings.Split(cleanPayload, \"\\n\"))\n\n\tif nStats != len(events) {\n\t\tt.Errorf(\"Was expecting %d events, got %d:  %s\", len(events), nStats, string(b1))\n\t}\n}\n\n// getFreePort Ask the kernel for a free open port that is ready to use\nfunc getFreePort() int {\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tl, err := net.ListenTCP(\"tcp\", addr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer l.Close()\n\treturn l.Addr().(*net.TCPAddr).Port\n}\n"
  },
  {
    "path": "event/absolute.go",
    "content": "package event\n\nimport \"fmt\"\n\n// Absolute is a metric that is not averaged/aggregated.\n// We keep each value distinct and then we flush them all individually.\ntype Absolute struct {\n\tName   string\n\tValues []int64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *Absolute) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Values = append(e.Values, e2.Payload().([]int64)...)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e Absolute) Payload() interface{} {\n\treturn e.Values\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e Absolute) Stats() []string {\n\tret := make([]string, 0, len(e.Values))\n\tfor _, v := range e.Values {\n\t\tret = append(ret, fmt.Sprintf(\"%s:%d|a\", e.Name, v))\n\t}\n\treturn ret\n}\n\n// Key returns the name of this metric\nfunc (e Absolute) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *Absolute) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e Absolute) Type() int {\n\treturn EventAbsolute\n}\n\n// TypeString returns a name for this type of metric\nfunc (e Absolute) TypeString() string {\n\treturn \"Absolute\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e Absolute) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Values: %v}\", e.TypeString(), e.Name, e.Values)\n}\n"
  },
  {
    "path": "event/absolute_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestAbsoluteUpdate(t *testing.T) {\n\te1 := &Absolute{Name: \"test\", Values: []int64{15}}\n\te2 := &Absolute{Name: \"test\", Values: []int64{-10}}\n\te3 := &Absolute{Name: \"test\", Values: []int64{8}}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:15|a\", \"test:-10|a\", \"test:8|a\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/fabsolute.go",
    "content": "package event\n\nimport \"fmt\"\n\n// FAbsolute is a metric that is not averaged/aggregated.\n// We keep each value distinct and then we flush them all individually.\ntype FAbsolute struct {\n\tName   string\n\tValues []float64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *FAbsolute) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Values = append(e.Values, e2.Payload().([]float64)...)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e FAbsolute) Payload() interface{} {\n\treturn e.Values\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e FAbsolute) Stats() []string {\n\tret := make([]string, 0, len(e.Values))\n\tfor _, v := range e.Values {\n\t\tret = append(ret, fmt.Sprintf(\"%s:%g|a\", e.Name, v))\n\t}\n\treturn ret\n}\n\n// Key returns the name of this metric\nfunc (e FAbsolute) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *FAbsolute) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e FAbsolute) Type() int {\n\treturn EventFAbsolute\n}\n\n// TypeString returns a name for this type of metric\nfunc (e FAbsolute) TypeString() string {\n\treturn \"FAbsolute\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e FAbsolute) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Values: %v}\", e.TypeString(), e.Name, e.Values)\n}\n"
  },
  {
    "path": "event/fabsolute_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFAbsoluteUpdate(t *testing.T) {\n\te1 := &FAbsolute{Name: \"test\", Values: []float64{15.3}}\n\te2 := &FAbsolute{Name: \"test\", Values: []float64{-10.1}}\n\te3 := &FAbsolute{Name: \"test\", Values: []float64{8.3}}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:15.3|a\", \"test:-10.1|a\", \"test:8.3|a\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/fgauge.go",
    "content": "package event\n\nimport \"fmt\"\n\n// FGauge - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again\ntype FGauge struct {\n\tName  string\n\tValue float64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *FGauge) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value = e2.Payload().(float64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e FGauge) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e FGauge) Stats() []string {\n\tif e.Value < 0 {\n\t\t// because a leading '+' or '-' in the value of a gauge denotes a delta, to send\n\t\t// a negative gauge value we first set the gauge absolutely to 0, then send the\n\t\t// negative value as a delta from 0 (that's just how the spec works :-)\n\t\treturn []string{\n\t\t\tfmt.Sprintf(\"%s:%d|g\", e.Name, 0),\n\t\t\tfmt.Sprintf(\"%s:%g|g\", e.Name, e.Value),\n\t\t}\n\t}\n\treturn []string{fmt.Sprintf(\"%s:%g|g\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e FGauge) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *FGauge) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e FGauge) Type() int {\n\treturn EventFGauge\n}\n\n// TypeString returns a name for this type of metric\nfunc (e FGauge) TypeString() string {\n\treturn \"FGauge\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e FGauge) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %g}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/fgauge_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFGaugeUpdate(t *testing.T) {\n\te1 := &FGauge{Name: \"test\", Value: float64(15.1)}\n\te2 := &FGauge{Name: \"test\", Value: float64(-10.1)}\n\te3 := &FGauge{Name: \"test\", Value: float64(8.4)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:8.4|g\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n\nfunc TestFGaugeUpdateNegative(t *testing.T) {\n\te1 := &FGauge{Name: \"test\", Value: float64(-10.1)}\n\te2 := &FGauge{Name: \"test\", Value: float64(-3.4)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:0|g\", \"test:-3.4|g\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/fgaugedelta.go",
    "content": "package event\n\nimport \"fmt\"\n\n// FGaugeDelta - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again\ntype FGaugeDelta struct {\n\tName  string\n\tValue float64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *FGaugeDelta) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value += e2.Payload().(float64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e FGaugeDelta) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e FGaugeDelta) Stats() []string {\n\treturn []string{fmt.Sprintf(\"%s:%+g|g\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e FGaugeDelta) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *FGaugeDelta) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e FGaugeDelta) Type() int {\n\treturn EventFGaugeDelta\n}\n\n// TypeString returns a name for this type of metric\nfunc (e FGaugeDelta) TypeString() string {\n\treturn \"FGaugeDelta\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e FGaugeDelta) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %g}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/fgaugedelta_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFGaugeDeltaUpdate(t *testing.T) {\n\te1 := &FGaugeDelta{Name: \"test\", Value: float64(15.1)}\n\te2 := &FGaugeDelta{Name: \"test\", Value: float64(-10.0)}\n\te3 := &FGaugeDelta{Name: \"test\", Value: float64(15.1)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:+20.2|g\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n\nfunc TestFGaugeDeltaUpdateNegative(t *testing.T) {\n\te1 := &FGaugeDelta{Name: \"test\", Value: float64(-15.1)}\n\te2 := &FGaugeDelta{Name: \"test\", Value: float64(10.0)}\n\te3 := &FGaugeDelta{Name: \"test\", Value: float64(-15.1)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:-20.2|g\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/gauge.go",
    "content": "package event\n\nimport \"fmt\"\n\n// Gauge - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again\ntype Gauge struct {\n\tName  string\n\tValue int64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *Gauge) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value = e2.Payload().(int64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e Gauge) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e Gauge) Stats() []string {\n\tif e.Value < 0 {\n\t\t// because a leading '+' or '-' in the value of a gauge denotes a delta, to send\n\t\t// a negative gauge value we first set the gauge absolutely to 0, then send the\n\t\t// negative value as a delta from 0 (that's just how the spec works :-)\n\t\treturn []string{\n\t\t\tfmt.Sprintf(\"%s:%d|g\", e.Name, 0),\n\t\t\tfmt.Sprintf(\"%s:%d|g\", e.Name, e.Value),\n\t\t}\n\t}\n\treturn []string{fmt.Sprintf(\"%s:%d|g\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e Gauge) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *Gauge) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e Gauge) Type() int {\n\treturn EventGauge\n}\n\n// TypeString returns a name for this type of metric\nfunc (e Gauge) TypeString() string {\n\treturn \"Gauge\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e Gauge) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %d}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/gauge_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGaugeUpdate(t *testing.T) {\n\te1 := &Gauge{Name: \"test\", Value: int64(15)}\n\te2 := &Gauge{Name: \"test\", Value: int64(-10)}\n\te3 := &Gauge{Name: \"test\", Value: int64(8)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:8|g\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n\nfunc TestGaugeUpdateNegative(t *testing.T) {\n\te1 := &Gauge{Name: \"test\", Value: int64(-10)}\n\te2 := &Gauge{Name: \"test\", Value: int64(-3)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:0|g\", \"test:-3|g\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/gaugedelta.go",
    "content": "package event\n\nimport \"fmt\"\n\n// GaugeDelta - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again\ntype GaugeDelta struct {\n\tName  string\n\tValue int64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *GaugeDelta) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value += e2.Payload().(int64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e GaugeDelta) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e GaugeDelta) Stats() []string {\n\treturn []string{fmt.Sprintf(\"%s:%+d|g\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e GaugeDelta) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *GaugeDelta) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e GaugeDelta) Type() int {\n\treturn EventGaugeDelta\n}\n\n// TypeString returns a name for this type of metric\nfunc (e GaugeDelta) TypeString() string {\n\treturn \"GaugeDelta\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e GaugeDelta) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %d}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/gaugedelta_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGaugeDeltaUpdate(t *testing.T) {\n\te1 := &GaugeDelta{Name: \"test\", Value: int64(15)}\n\te2 := &GaugeDelta{Name: \"test\", Value: int64(-10)}\n\te3 := &GaugeDelta{Name: \"test\", Value: int64(15)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:+20|g\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n\nfunc TestGaugeDeltaUpdateNegative(t *testing.T) {\n\te1 := &GaugeDelta{Name: \"test\", Value: int64(-15)}\n\te2 := &GaugeDelta{Name: \"test\", Value: int64(10)}\n\te3 := &GaugeDelta{Name: \"test\", Value: int64(-15)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:-20|g\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/increment.go",
    "content": "package event\n\nimport \"fmt\"\n\n// Increment represents a metric whose value is averaged over a minute\ntype Increment struct {\n\tName  string\n\tValue int64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *Increment) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value += e2.Payload().(int64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e Increment) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e Increment) Stats() []string {\n\treturn []string{fmt.Sprintf(\"%s:%d|c\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e Increment) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *Increment) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e Increment) Type() int {\n\treturn EventIncr\n}\n\n// TypeString returns a name for this type of metric\nfunc (e Increment) TypeString() string {\n\treturn \"Increment\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e Increment) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %d}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/increment_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestIncrementUpdate(t *testing.T) {\n\te1 := &Increment{Name: \"test\", Value: int64(15)}\n\te2 := &Increment{Name: \"test\", Value: int64(-10)}\n\te3 := &Increment{Name: \"test\", Value: int64(8)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:13|c\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/interface.go",
    "content": "package event\n\n// constant event type identifiers\nconst (\n\tEventIncr = iota\n\tEventTiming\n\tEventAbsolute\n\tEventTotal\n\tEventGauge\n\tEventGaugeDelta\n\tEventFGauge\n\tEventFGaugeDelta\n\tEventFAbsolute\n\tEventPrecisionTiming\n)\n\n// Event is an interface to a generic StatsD event, used by the buffered client collator\ntype Event interface {\n\tStats() []string\n\tType() int\n\tTypeString() string\n\tPayload() interface{}\n\tUpdate(e2 Event) error\n\tString() string\n\tKey() string\n\tSetKey(string)\n}\n\n// compile-time assertion to verify default events implement the Event interface\nfunc _() {\n\tvar _ Event = (*Absolute)(nil)        // assert *Absolute implements Event\n\tvar _ Event = (*FAbsolute)(nil)       // assert *FAbsolute implements Event\n\tvar _ Event = (*Gauge)(nil)           // assert *Gauge implements Event\n\tvar _ Event = (*FGauge)(nil)          // assert *FGauge implements Event\n\tvar _ Event = (*GaugeDelta)(nil)      // assert *GaugeDelta implements Event\n\tvar _ Event = (*FGaugeDelta)(nil)     // assert *FGaugeDelta implements Event\n\tvar _ Event = (*Increment)(nil)       // assert *Increment implements Event\n\tvar _ Event = (*PrecisionTiming)(nil) // assert *PrecisionTiming implements Event\n\tvar _ Event = (*Timing)(nil)          // assert *Timing implements Event\n\tvar _ Event = (*Total)(nil)           // assert *Total implements Event\n}\n"
  },
  {
    "path": "event/precisiontiming.go",
    "content": "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 interval\ntype PrecisionTiming struct {\n\tName  string\n\tMin   time.Duration\n\tMax   time.Duration\n\tValue time.Duration\n\tCount int64\n}\n\n// NewPrecisionTiming is a factory for a Timing event, setting the Count to 1 to prevent div_by_0 errors\nfunc NewPrecisionTiming(k string, delta time.Duration) *PrecisionTiming {\n\treturn &PrecisionTiming{Name: k, Min: delta, Max: delta, Value: delta, Count: 1}\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *PrecisionTiming) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\tp := e2.Payload().(PrecisionTiming)\n\te.Count += p.Count\n\te.Value += p.Value\n\te.Min = time.Duration(minInt64(int64(e.Min), int64(p.Min)))\n\te.Max = time.Duration(maxInt64(int64(e.Max), int64(p.Min)))\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e PrecisionTiming) Payload() interface{} {\n\treturn e\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e PrecisionTiming) Stats() []string {\n\treturn []string{\n\t\tfmt.Sprintf(\"%s.count:%d|c\", e.Name, e.Count),\n\t\tfmt.Sprintf(\"%s.avg:%.6f|ms\", e.Name, float64(int64(e.Value)/e.Count)/1000000), // make sure e.Count != 0\n\t\tfmt.Sprintf(\"%s.min:%.6f|ms\", e.Name, e.durationToMs(e.Min)),\n\t\tfmt.Sprintf(\"%s.max:%.6f|ms\", e.Name, e.durationToMs(e.Max)),\n\t}\n}\n\n// durationToMs converts time.Duration into the corresponding value in milliseconds\nfunc (e PrecisionTiming) durationToMs(x time.Duration) float64 {\n\treturn float64(x) / float64(time.Millisecond)\n}\n\n// Key returns the name of this metric\nfunc (e PrecisionTiming) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *PrecisionTiming) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e PrecisionTiming) Type() int {\n\treturn EventPrecisionTiming\n}\n\n// TypeString returns a name for this type of metric\nfunc (e PrecisionTiming) TypeString() string {\n\treturn \"PrecisionTiming\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e PrecisionTiming) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %+v}\", e.TypeString(), e.Name, e.Payload())\n}\n"
  },
  {
    "path": "event/precisiontiming_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestPrecisionTimingUpdate(t *testing.T) {\n\te1 := NewPrecisionTiming(\"test\", 5*time.Microsecond)\n\te2 := NewPrecisionTiming(\"test\", 3*time.Microsecond)\n\te3 := NewPrecisionTiming(\"test\", 7*time.Microsecond)\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test.count:3|c\", \"test.avg:0.005000|ms\", \"test.min:0.003000|ms\", \"test.max:0.007000|ms\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/timing.go",
    "content": "package event\n\nimport \"fmt\"\n\n// Timing keeps min/max/avg information about a timer over a certain interval\ntype Timing struct {\n\tName  string\n\tMin   int64\n\tMax   int64\n\tValue int64\n\tCount int64\n}\n\n// NewTiming is a factory for a Timing event, setting the Count to 1 to prevent div_by_0 errors\nfunc NewTiming(k string, delta int64) *Timing {\n\treturn &Timing{Name: k, Min: delta, Max: delta, Value: delta, Count: 1}\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *Timing) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\tp := e2.Payload().(map[string]int64)\n\te.Count += p[\"cnt\"]\n\te.Value += p[\"val\"]\n\te.Min = minInt64(e.Min, p[\"min\"])\n\te.Max = maxInt64(e.Max, p[\"max\"])\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e Timing) Payload() interface{} {\n\treturn map[string]int64{\n\t\t\"min\": e.Min,\n\t\t\"max\": e.Max,\n\t\t\"val\": e.Value,\n\t\t\"cnt\": e.Count,\n\t}\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e Timing) Stats() []string {\n\treturn []string{\n\t\tfmt.Sprintf(\"%s.count:%d|c\", e.Name, e.Count),\n\t\tfmt.Sprintf(\"%s.avg:%d|ms\", e.Name, int64(e.Value/e.Count)), // make sure e.Count != 0\n\t\tfmt.Sprintf(\"%s.min:%d|ms\", e.Name, e.Min),\n\t\tfmt.Sprintf(\"%s.max:%d|ms\", e.Name, e.Max),\n\t}\n}\n\n// Key returns the name of this metric\nfunc (e Timing) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *Timing) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e Timing) Type() int {\n\treturn EventTiming\n}\n\n// TypeString returns a name for this type of metric\nfunc (e Timing) TypeString() string {\n\treturn \"Timing\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e Timing) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %+v}\", e.TypeString(), e.Name, e.Payload())\n}\n\nfunc minInt64(v1, v2 int64) int64 {\n\tif v1 <= v2 {\n\t\treturn v1\n\t}\n\treturn v2\n}\nfunc maxInt64(v1, v2 int64) int64 {\n\tif v1 >= v2 {\n\t\treturn v1\n\t}\n\treturn v2\n}\n"
  },
  {
    "path": "event/timing_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTimingUpdate(t *testing.T) {\n\te1 := NewTiming(\"test\", 5)\n\te2 := NewTiming(\"test\", 3)\n\te3 := NewTiming(\"test\", 7)\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test.count:3|c\", \"test.avg:5|ms\", \"test.min:3|ms\", \"test.max:7|ms\"}\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "event/total.go",
    "content": "package event\n\nimport \"fmt\"\n\n// Total represents a metric that is continously increasing, e.g. read operations since boot\ntype Total struct {\n\tName  string\n\tValue int64\n}\n\n// Update the event with metrics coming from a new one of the same type and with the same key\nfunc (e *Total) Update(e2 Event) error {\n\tif e.Type() != e2.Type() {\n\t\treturn fmt.Errorf(\"statsd event type conflict: %s vs %s \", e.String(), e2.String())\n\t}\n\te.Value += e2.Payload().(int64)\n\treturn nil\n}\n\n// Payload returns the aggregated value for this event\nfunc (e Total) Payload() interface{} {\n\treturn e.Value\n}\n\n// Stats returns an array of StatsD events as they travel over UDP\nfunc (e Total) Stats() []string {\n\treturn []string{fmt.Sprintf(\"%s:%d|t\", e.Name, e.Value)}\n}\n\n// Key returns the name of this metric\nfunc (e Total) Key() string {\n\treturn e.Name\n}\n\n// SetKey sets the name of this metric\nfunc (e *Total) SetKey(key string) {\n\te.Name = key\n}\n\n// Type returns an integer identifier for this type of metric\nfunc (e Total) Type() int {\n\treturn EventTotal\n}\n\n// TypeString returns a name for this type of metric\nfunc (e Total) TypeString() string {\n\treturn \"Total\"\n}\n\n// String returns a debug-friendly representation of this metric\nfunc (e Total) String() string {\n\treturn fmt.Sprintf(\"{Type: %s, Key: %s, Value: %d}\", e.TypeString(), e.Name, e.Value)\n}\n"
  },
  {
    "path": "event/total_test.go",
    "content": "package event\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestTotalUpdate(t *testing.T) {\n\te1 := &Total{Name: \"test\", Value: int64(15)}\n\te2 := &Total{Name: \"test\", Value: int64(-10)}\n\te3 := &Total{Name: \"test\", Value: int64(8)}\n\terr := e1.Update(e2)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\terr = e1.Update(e3)\n\tif nil != err {\n\t\tt.Error(err)\n\t}\n\n\texpected := []string{\"test:13|t\"} // only the last value is flushed\n\tactual := e1.Stats()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"did not receive all metrics: Expected: %T %v, Actual: %T %v \", expected, expected, actual, actual)\n\t}\n}\n"
  },
  {
    "path": "interface.go",
    "content": "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 (buffered/unbuffered)\ntype Statsd interface {\n\tCreateSocket() error\n\tCreateTCPSocket() error\n\tClose() error\n\tIncr(stat string, count int64) error\n\tDecr(stat string, count int64) error\n\tTiming(stat string, delta int64) error\n\tPrecisionTiming(stat string, delta time.Duration) error\n\tGauge(stat string, value int64) error\n\tGaugeDelta(stat string, value int64) error\n\tAbsolute(stat string, value int64) error\n\tTotal(stat string, value int64) error\n\n\tFGauge(stat string, value float64) error\n\tFGaugeDelta(stat string, value float64) error\n\tFAbsolute(stat string, value float64) error\n\n\tSendEvents(events map[string]event.Event) error\n}\n"
  },
  {
    "path": "mock/mockableclient.go",
    "content": "package mock\n\n//\n// A mockable client allowing arbitrary functions to be called statsd.Statsd methods.\n//\n// This is particularly helpful in unit test scenarios where it is desired to simulate calls\n// without actually writing to a network or filesystem.\n//\n// A default implementation is provided that records all calls and can be used for verification\n// in unit tests.\n//\n// The default implementations of these methods are no-ops, so without any further configuration,\n// &MockStatsdClient{} is equivalent to statsd.NoopClient. But utility methods are also provided that\n// allow recording calls for the purposes of verification during unit testing.\n//\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/quipo/statsd/event\"\n)\n\ntype statelessStatsdFunction func() error\ntype intMetricStatsdFunction func(string, int64) error\ntype floatMetricStatsdFunction func(string, float64) error\ntype durationMetricStatsdFunction func(string, time.Duration) error\ntype eventsStatsdFunction func(events map[string]event.Event) error\n\n// MockStatsdClient at its simplest provides a layer of indirection so that\n// arbitrary functions can be used as the targets of calls to the Statsd interface\n//\n// In its basic state, it will act like a noop client.\n//\n// There are also helper functions that will set functions for specific\n// calls to record those events to caller-provided slices.\n// This is particularly helpful for unit testing that needs to verify\n// that certain metrics have been recorded by the system under test\n\ntype MockStatsdClient struct {\n\tCreateSocketFn    statelessStatsdFunction\n\tCreateTCPSocketFn statelessStatsdFunction\n\tCloseFn           statelessStatsdFunction\n\tIncrFn            intMetricStatsdFunction\n\tDecrFn            intMetricStatsdFunction\n\tTimingFn          intMetricStatsdFunction\n\tPrecisionTimingFn durationMetricStatsdFunction\n\tGaugeFn           intMetricStatsdFunction\n\tGaugeDeltaFn      intMetricStatsdFunction\n\tAbsoluteFn        intMetricStatsdFunction\n\tTotalFn           intMetricStatsdFunction\n\n\tFGaugeFn      floatMetricStatsdFunction\n\tFGaugeDeltaFn floatMetricStatsdFunction\n\tFAbsoluteFn   floatMetricStatsdFunction\n\n\tSendEventsFn eventsStatsdFunction\n}\n\n// Implement statsd interface\n\nfunc (msc *MockStatsdClient) CreateSocket() error {\n\tif msc.CreateSocketFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.CreateSocketFn()\n}\n\nfunc (msc *MockStatsdClient) CreateTCPSocket() error {\n\tif msc.CreateTCPSocketFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.CreateTCPSocketFn()\n}\n\nfunc (msc *MockStatsdClient) Close() error {\n\tif msc.CloseFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.CloseFn()\n}\n\nfunc (msc *MockStatsdClient) Incr(stat string, count int64) error {\n\tif msc.IncrFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.IncrFn(stat, count)\n}\n\nfunc (msc *MockStatsdClient) Decr(stat string, count int64) error {\n\tif msc.DecrFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.DecrFn(stat, count)\n}\n\nfunc (msc *MockStatsdClient) Timing(stat string, delta int64) error {\n\tif msc.TimingFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.TimingFn(stat, delta)\n}\n\nfunc (msc *MockStatsdClient) PrecisionTiming(stat string, delta time.Duration) error {\n\tif msc.PrecisionTimingFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.PrecisionTimingFn(stat, delta)\n}\n\nfunc (msc *MockStatsdClient) Gauge(stat string, value int64) error {\n\tif msc.GaugeFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.GaugeFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) GaugeDelta(stat string, value int64) error {\n\tif msc.GaugeDeltaFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.GaugeDeltaFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) Absolute(stat string, value int64) error {\n\tif msc.AbsoluteFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.AbsoluteFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) Total(stat string, value int64) error {\n\tif msc.TotalFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.TotalFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) FGauge(stat string, value float64) error {\n\tif msc.FGaugeFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.FGaugeFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) FGaugeDelta(stat string, value float64) error {\n\tif msc.FGaugeDeltaFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.FGaugeDeltaFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) FAbsolute(stat string, value float64) error {\n\tif msc.FAbsoluteFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.FAbsoluteFn(stat, value)\n}\n\nfunc (msc *MockStatsdClient) SendEvents(events map[string]event.Event) error {\n\tif msc.SendEventsFn == nil {\n\t\treturn nil\n\t}\n\treturn msc.SendEventsFn(events)\n}\n\n// Mocking helpers that record seen events for verification during unit testing\n\ntype Int64Event struct {\n\tMetricName string\n\tEventValue int64\n}\n\ntype Float64Event struct {\n\tMetricName string\n\tEventValue float64\n}\n\ntype DurationEvent struct {\n\tMetricName string\n\tEventValue time.Duration\n}\n\n// UnvaluedEvents are useful for recording things like calls to Close() or CreateSocket()\ntype UnvaluedEvent struct {\n}\n\n// Fluent-style constructors for recording events to caller-provided slices\n// This means that during unit tests, one can do something like\n//\n// incrEvents := []statsd.Int64Event\n// decrEvents := []statsd.Int64Event\n// statsdClient = &MockStatsdClient{}.RecordIncrEventsTo(&incrEvents).RecordDecrEventsTo(&decrEvents)\n// ... Execute code under test that records metrics\n// ... Verify that incrEvents and decrEvents have seen the expected events\n\nfunc (msc *MockStatsdClient) RecordCreateSocketEventsTo(createSocketEvents *[]UnvaluedEvent) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.CreateSocketFn = func() error {\n\t\trecordUnvaluedEvent(eventLock, createSocketEvents)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordCreateTCPSocketEventsTo(createTcpSocketEvents *[]UnvaluedEvent) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.CreateTCPSocketFn = func() error {\n\t\trecordUnvaluedEvent(eventLock, createTcpSocketEvents)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordCloseEventsTo(closeEvents *[]UnvaluedEvent) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.CloseFn = func() error {\n\t\trecordUnvaluedEvent(eventLock, closeEvents)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordIncrEventsTo(incrEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.IncrFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, incrEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordDecrEventsTo(decrEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.DecrFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, decrEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordTimingEventsTo(timingEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.TimingFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, timingEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordPrecisionTimingEventsTo(timingEvents *[]DurationEvent) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.PrecisionTimingFn = func(metricName string, eventValue time.Duration) error {\n\t\trecordDurationEvent(eventLock, timingEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordGaugeEventsTo(gaugeEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.GaugeFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, gaugeEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordGaugeDeltaEventsTo(gaugeDeltaEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.GaugeDeltaFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, gaugeDeltaEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordAbsoluteEventsTo(absoluteEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.AbsoluteFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, absoluteEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordTotalEventsTo(totalEvents *[]Int64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.TotalFn = func(metricName string, eventValue int64) error {\n\t\trecordInt64Event(eventLock, totalEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordFGaugeEventsTo(fgaugeEvents *[]Float64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.FGaugeFn = func(metricName string, eventValue float64) error {\n\t\trecordFloat64Event(eventLock, fgaugeEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordFGaugeDeltaEventsTo(fgaugeDeltaEvents *[]Float64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.FGaugeDeltaFn = func(metricName string, eventValue float64) error {\n\t\trecordFloat64Event(eventLock, fgaugeDeltaEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc (msc *MockStatsdClient) RecordFAbsoluteEventsTo(fabsoluteEvents *[]Float64Event) *MockStatsdClient {\n\teventLock := &sync.Mutex{}\n\tmsc.FAbsoluteFn = func(metricName string, eventValue float64) error {\n\t\trecordFloat64Event(eventLock, fabsoluteEvents, metricName, eventValue)\n\t\treturn nil\n\t}\n\treturn msc\n}\n\nfunc recordDurationEvent(eventLock sync.Locker, events *[]DurationEvent, metricName string, eventValue time.Duration) {\n\tnewEvent := DurationEvent{\n\t\tMetricName: metricName,\n\t\tEventValue: eventValue,\n\t}\n\teventLock.Lock()\n\tdefer eventLock.Unlock()\n\t*events = append(*events, newEvent)\n}\n\nfunc recordFloat64Event(eventLock sync.Locker, events *[]Float64Event, metricName string, eventValue float64) {\n\tnewEvent := Float64Event{\n\t\tMetricName: metricName,\n\t\tEventValue: eventValue,\n\t}\n\teventLock.Lock()\n\tdefer eventLock.Unlock()\n\t*events = append(*events, newEvent)\n}\n\nfunc recordInt64Event(eventLock sync.Locker, events *[]Int64Event, metricName string, eventValue int64) {\n\tnewEvent := Int64Event{\n\t\tMetricName: metricName,\n\t\tEventValue: eventValue,\n\t}\n\teventLock.Lock()\n\tdefer eventLock.Unlock()\n\t*events = append(*events, newEvent)\n}\n\nfunc recordUnvaluedEvent(eventLock sync.Locker, events *[]UnvaluedEvent) {\n\teventLock.Lock()\n\tdefer eventLock.Unlock()\n\t*events = append(*events, UnvaluedEvent{})\n}\n"
  },
  {
    "path": "mock/mockableclient_test.go",
    "content": "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\tmockClient := MockStatsdClient{}\n\terr := mockClient.CreateSocket()\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.CreateTCPSocket()\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Close()\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Incr(\"incr\", 1)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Decr(\"decr\", 2)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Timing(\"timing\", 3)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.PrecisionTiming(\"precisionTiming\", 4)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Gauge(\"gauge\", 5)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.GaugeDelta(\"gaugeDelta\", 6)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Absolute(\"absolute\", 7)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.Total(\"total\", 8)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\n\terr = mockClient.FGauge(\"fgauge\", 10.0)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.FGaugeDelta(\"fgaugeDelta\", 11.0)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\terr = mockClient.FAbsolute(\"fabsolute\", 12.0)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\n\terr = mockClient.SendEvents(make(map[string]event.Event))\n\tif err != nil {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordCreateSocketEventsTo(t *testing.T) {\n\tvar createSocketEvents []UnvaluedEvent\n\tmockClient := (&MockStatsdClient{}).RecordCreateSocketEventsTo(&createSocketEvents)\n\terr := mockClient.CreateSocket()\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock CreateSocket\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []UnvaluedEvent{UnvaluedEvent{}}\n\tif !reflect.DeepEqual(createSocketEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, createSocketEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordCreateTCPSocketEventsTo(t *testing.T) {\n\tvar createTCPSocketEvents []UnvaluedEvent\n\tmockClient := (&MockStatsdClient{}).RecordCreateTCPSocketEventsTo(&createTCPSocketEvents)\n\terr := mockClient.CreateTCPSocket()\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock CreateTCPSocket\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []UnvaluedEvent{UnvaluedEvent{}}\n\tif !reflect.DeepEqual(createTCPSocketEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, createTCPSocketEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordCloseEventsTo(t *testing.T) {\n\tvar closeEvents []UnvaluedEvent\n\tmockClient := (&MockStatsdClient{}).RecordCloseEventsTo(&closeEvents)\n\terr := mockClient.Close()\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Close\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []UnvaluedEvent{UnvaluedEvent{}}\n\tif !reflect.DeepEqual(closeEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, closeEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordIncrEventsTo(t *testing.T) {\n\tvar incrEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordIncrEventsTo(&incrEvents)\n\terr := mockClient.Incr(\"incr\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Incr\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"incr\", EventValue: 1}}\n\tif !reflect.DeepEqual(incrEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, incrEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_Decr(t *testing.T) {\n\tvar decrEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordDecrEventsTo(&decrEvents)\n\terr := mockClient.Decr(\"decr\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Decr\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"decr\", EventValue: 1}}\n\tif !reflect.DeepEqual(decrEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, decrEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_Timing(t *testing.T) {\n\tvar timingEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordIncrEventsTo(&timingEvents)\n\terr := mockClient.Incr(\"timing\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Timing\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"timing\", EventValue: 1}}\n\tif !reflect.DeepEqual(timingEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, timingEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordPrecisionTimingEventsTo(t *testing.T) {\n\tvar durationEvents []DurationEvent\n\tmockClient := (&MockStatsdClient{}).RecordPrecisionTimingEventsTo(&durationEvents)\n\terr := mockClient.PrecisionTiming(\"precisionTiming\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock PrecisionTiming\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []DurationEvent{DurationEvent{MetricName: \"precisionTiming\", EventValue: 1}}\n\tif !reflect.DeepEqual(durationEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, durationEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordGaugeEventsTo(t *testing.T) {\n\tvar gaugeEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordGaugeEventsTo(&gaugeEvents)\n\terr := mockClient.Gauge(\"gauge\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Gauge\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"gauge\", EventValue: 1}}\n\tif !reflect.DeepEqual(gaugeEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, gaugeEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordFGaugeDeltaEventsTo(t *testing.T) {\n\tvar gaugeDeltaEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordGaugeDeltaEventsTo(&gaugeDeltaEvents)\n\terr := mockClient.GaugeDelta(\"gaugeDelta\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock GaugeDelta\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"gaugeDelta\", EventValue: 1}}\n\tif !reflect.DeepEqual(gaugeDeltaEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, gaugeDeltaEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordAbsoluteEventsTo(t *testing.T) {\n\tvar absoluteEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordAbsoluteEventsTo(&absoluteEvents)\n\terr := mockClient.Absolute(\"absolute\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Absolute\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"absolute\", EventValue: 1}}\n\tif !reflect.DeepEqual(absoluteEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, absoluteEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordTotalEventsTo(t *testing.T) {\n\tvar totalEvents []Int64Event\n\tmockClient := (&MockStatsdClient{}).RecordTotalEventsTo(&totalEvents)\n\terr := mockClient.Total(\"total\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock Total\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Int64Event{Int64Event{MetricName: \"total\", EventValue: 1}}\n\tif !reflect.DeepEqual(totalEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, totalEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordFGaugeEventsTo(t *testing.T) {\n\tvar fgaugeEvents []Float64Event\n\tmockClient := (&MockStatsdClient{}).RecordFGaugeEventsTo(&fgaugeEvents)\n\terr := mockClient.FGauge(\"fgauge\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock FGauge\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Float64Event{Float64Event{MetricName: \"fgauge\", EventValue: 1}}\n\tif !reflect.DeepEqual(fgaugeEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, fgaugeEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordFGaugeDeltaEventsTo2(t *testing.T) {\n\tvar fgaugeDeltaEvents []Float64Event\n\tmockClient := (&MockStatsdClient{}).RecordFGaugeDeltaEventsTo(&fgaugeDeltaEvents)\n\terr := mockClient.FGaugeDelta(\"fgaugeDelta\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock FGaugeDelta\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Float64Event{Float64Event{MetricName: \"fgaugeDelta\", EventValue: 1}}\n\tif !reflect.DeepEqual(fgaugeDeltaEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, fgaugeDeltaEvents)\n\t\tt.Fail()\n\t}\n}\n\nfunc TestMockStatsdClient_RecordFAbsoluteEventsTo(t *testing.T) {\n\tvar fabsoluteEvents []Float64Event\n\tmockClient := (&MockStatsdClient{}).RecordFAbsoluteEventsTo(&fabsoluteEvents)\n\terr := mockClient.FAbsolute(\"fabsolute\", 1)\n\tif err != nil {\n\t\tt.Logf(\"Got non-nil err from mock FAbsolute\")\n\t\tt.Fail()\n\t}\n\texpectedEvents := []Float64Event{Float64Event{MetricName: \"fabsolute\", EventValue: 1}}\n\tif !reflect.DeepEqual(fabsoluteEvents, expectedEvents) {\n\t\tt.Logf(\"Expected %s, saw %s\", expectedEvents, fabsoluteEvents)\n\t\tt.Fail()\n\t}\n}\n\n//func TestRecordingBuilders(t *testing.T) {\n//\tvar createTcpSocketEvents []UnvaluedEvent\n//\tvar closeEvents []UnvaluedEvent\n//\tvar incrEvents []Int64Event\n//\tvar decrEvents []Int64Event\n//\tvar timingEvents []Int64Event\n//\tvar precidionTimingEvents []DurationEvent\n//\tvar gaugeEvents []Int64Event\n//\tvar gaugeDeltaEvents []Int64Event\n//\tvar absoluteEvents []Int64Event\n//\tvar totalEvents []Int64Event\n//\tvar fgaugeEvents []Float64Event\n//\tvar fgaugeDeltaEvents []Float64Event\n//\tvar fabsoluteEvents []Float64Event\n//\n//\tmockClient := &MockStatsdClient{}.\n//\t\tRecordCreateSocketEventsTo(&createSocketEvents).\n//\t\tRecordCreateTCPSocketEventsTo(&createTcpSocketEvents).\n//\t\tRecordCloseEventsTo(&closeEvents).\n//\t\tRecordIncrEventsTo(&incrEvents).\n//\t\tRecordDecrEventsTo(&decrEvents).\n//\t\tRecordTimingEventsTo(&timingEvents).\n//\t\tRecordPrecisionTimingEventsTo(&precidionTimingEvents).\n//\t\tRecordGaugeEventsTo(&gaugeEvents).\n//\t\tRecordGaugeDeltaEventsTo(&gaugeDeltaEvents).\n//\t\tRecordAbsoluteEventsTo(&absoluteEvents).\n//\t\tRecordTotalEventsTo(&totalEvents).\n//\t\tRecordFGaugeEventsTo(&fgaugeEvents).\n//\t\tRecordFGaugeDeltaEventsTo(&fgaugeDeltaEvents).\n//\t\tRecordFAbsoluteEventsTo(&fabsoluteEvents)\n//\n//\terr := mockClient.CreateSocket()\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//\terr = mockClient.CreateTCPSocket()\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//\terr = mockClient.Close()\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//\terr = mockClient.Incr(\"incr\", 1)\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//\terr = mockClient.Decr(\"decr\", 1)\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//\terr = mockClient.Timing(\"timing\", 1)\n//\tif err != nil {\n//\t\tt.Fail()\n//\t}\n//}\n"
  },
  {
    "path": "noopclient.go",
    "content": "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// NoopClient implements a \"no-op\" statsd in case there is no statsd server\ntype NoopClient struct{}\n\n// CreateSocket does nothing\nfunc (s NoopClient) CreateSocket() error {\n\treturn nil\n}\n\n// CreateTCPSocket does nothing\nfunc (s NoopClient) CreateTCPSocket() error {\n\treturn nil\n}\n\n// Close does nothing\nfunc (s NoopClient) Close() error {\n\treturn nil\n}\n\n// Incr does nothing\nfunc (s NoopClient) Incr(stat string, count int64) error {\n\treturn nil\n}\n\n// Decr does nothing\nfunc (s NoopClient) Decr(stat string, count int64) error {\n\treturn nil\n}\n\n// Timing does nothing\nfunc (s NoopClient) Timing(stat string, count int64) error {\n\treturn nil\n}\n\n// PrecisionTiming does nothing\nfunc (s NoopClient) PrecisionTiming(stat string, delta time.Duration) error {\n\treturn nil\n}\n\n// Gauge does nothing\nfunc (s NoopClient) Gauge(stat string, value int64) error {\n\treturn nil\n}\n\n// GaugeDelta does nothing\nfunc (s NoopClient) GaugeDelta(stat string, value int64) error {\n\treturn nil\n}\n\n// Absolute does nothing\nfunc (s NoopClient) Absolute(stat string, value int64) error {\n\treturn nil\n}\n\n// Total does nothing\nfunc (s NoopClient) Total(stat string, value int64) error {\n\treturn nil\n}\n\n// FGauge does nothing\nfunc (s NoopClient) FGauge(stat string, value float64) error {\n\treturn nil\n}\n\n// FGaugeDelta does nothing\nfunc (s NoopClient) FGaugeDelta(stat string, value float64) error {\n\treturn nil\n}\n\n// FAbsolute does nothing\nfunc (s NoopClient) FAbsolute(stat string, value float64) error {\n\treturn nil\n}\n\n// SendEvents does nothing\nfunc (s NoopClient) SendEvents(events map[string]event.Event) error {\n\treturn nil\n}\n"
  },
  {
    "path": "stdoutclient.go",
    "content": "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 implements a \"no-op\" statsd in case there is no statsd server\ntype StdoutClient struct {\n\tFD     *os.File\n\tprefix string\n\tLogger Logger\n}\n\n// NewStdoutClient - Factory\nfunc NewStdoutClient(filename string, prefix string) *StdoutClient {\n\tvar err error\n\t// allow %HOST% in the prefix string\n\tprefix = strings.Replace(prefix, \"%HOST%\", Hostname, 1)\n\tvar fh *os.File\n\tif filename == \"\" {\n\t\tfh = os.Stdout\n\t} else {\n\t\tfh, err = os.OpenFile(filename, os.O_WRONLY, 0644)\n\t\tif nil != err {\n\t\t\tfmt.Printf(\"Cannot open file '%s' for stats output: %s\\n\", filename, err.Error())\n\t\t}\n\t}\n\treturn &StdoutClient{\n\t\tFD:     fh,\n\t\tprefix: prefix,\n\t\tLogger: log.New(os.Stdout, \"[StdoutClient] \", log.Ldate|log.Ltime),\n\t}\n}\n\n// CreateSocket does nothing\nfunc (s *StdoutClient) CreateSocket() error {\n\tif s.FD == nil {\n\t\ts.FD = os.Stdout\n\t}\n\treturn nil\n}\n\n// CreateTCPSocket does nothing\nfunc (s *StdoutClient) CreateTCPSocket() error {\n\tif s.FD == nil {\n\t\ts.FD = os.Stdout\n\t}\n\treturn nil\n}\n\n// Close does nothing\nfunc (s *StdoutClient) Close() error {\n\treturn nil\n}\n\n// Incr - Increment a counter metric. Often used to note a particular event\nfunc (s *StdoutClient) Incr(stat string, count int64) error {\n\tif 0 != count {\n\t\treturn s.send(stat, \"%d|c\", count)\n\t}\n\treturn nil\n}\n\n// Decr - Decrement a counter metric. Often used to note a particular event\nfunc (s *StdoutClient) Decr(stat string, count int64) error {\n\tif 0 != count {\n\t\treturn s.send(stat, \"%d|c\", -count)\n\t}\n\treturn nil\n}\n\n// Timing - Track a duration event\n// the time delta must be given in milliseconds\nfunc (s *StdoutClient) Timing(stat string, delta int64) error {\n\treturn s.send(stat, \"%d|ms\", delta)\n}\n\n// PrecisionTiming - Track a duration event\n// the time delta has to be a duration\nfunc (s *StdoutClient) PrecisionTiming(stat string, delta time.Duration) error {\n\treturn s.send(stat, \"%.6f|ms\", float64(delta)/float64(time.Millisecond))\n}\n\n// Gauge - Gauges are a constant data type. They are not subject to averaging,\n// and they don’t change unless you change them. That is, once you set a gauge value,\n// it will be a flat line on the graph until you change it again. If you specify\n// delta to be true, that specifies that the gauge should be updated, not set. Due to the\n// underlying protocol, you can't explicitly set a gauge to a negative number without\n// first setting it to zero.\nfunc (s *StdoutClient) Gauge(stat string, value int64) error {\n\tif value < 0 {\n\t\terr := s.send(stat, \"%d|g\", 0)\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t\treturn s.send(stat, \"%d|g\", value)\n\t}\n\treturn s.send(stat, \"%d|g\", value)\n}\n\n// GaugeDelta -- Send a change for a gauge\nfunc (s *StdoutClient) GaugeDelta(stat string, value int64) error {\n\t// Gauge Deltas are always sent with a leading '+' or '-'. The '-' takes care of itself but the '+' must added by hand\n\tif value < 0 {\n\t\treturn s.send(stat, \"%d|g\", value)\n\t}\n\treturn s.send(stat, \"+%d|g\", value)\n}\n\n// FGauge -- Send a floating point value for a gauge\nfunc (s *StdoutClient) FGauge(stat string, value float64) error {\n\tif value < 0 {\n\t\terr := s.send(stat, \"%d|g\", 0)\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t\treturn s.send(stat, \"%g|g\", value)\n\t}\n\treturn s.send(stat, \"%g|g\", value)\n}\n\n// FGaugeDelta -- Send a floating point change for a gauge\nfunc (s *StdoutClient) FGaugeDelta(stat string, value float64) error {\n\tif value < 0 {\n\t\treturn s.send(stat, \"%g|g\", value)\n\t}\n\treturn s.send(stat, \"+%g|g\", value)\n}\n\n// Absolute - Send absolute-valued metric (not averaged/aggregated)\nfunc (s *StdoutClient) Absolute(stat string, value int64) error {\n\treturn s.send(stat, \"%d|a\", value)\n}\n\n// FAbsolute - Send absolute-valued floating point metric (not averaged/aggregated)\nfunc (s *StdoutClient) FAbsolute(stat string, value float64) error {\n\treturn s.send(stat, \"%g|a\", value)\n}\n\n// Total - Send a metric that is continously increasing, e.g. read operations since boot\nfunc (s *StdoutClient) Total(stat string, value int64) error {\n\treturn s.send(stat, \"%d|t\", value)\n}\n\n// write a UDP packet with the statsd event\nfunc (s *StdoutClient) send(stat string, format string, value interface{}) error {\n\tstat = strings.Replace(stat, \"%HOST%\", Hostname, 1)\n\t// if sending tcp append a newline\n\tformat = fmt.Sprintf(\"%s%s:%s\\n\", s.prefix, stat, format)\n\t_, err := fmt.Fprintf(s.FD, format, value)\n\treturn err\n}\n\n// SendEvent - Sends stats from an event object\nfunc (s *StdoutClient) SendEvent(e event.Event) error {\n\tfor _, stat := range e.Stats() {\n\t\t//fmt.Printf(\"SENDING EVENT %s%s\\n\", s.prefix, strings.Replace(stat, \"%HOST%\", Hostname, 1))\n\t\t_, err := fmt.Fprintf(s.FD, \"%s%s\", s.prefix, strings.Replace(stat, \"%HOST%\", Hostname, 1))\n\t\tif nil != err {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// SendEvents - Sends stats from all the event objects.\n// Tries to bundle many together into one fmt.Fprintf based on UDPPayloadSize.\nfunc (s *StdoutClient) SendEvents(events map[string]event.Event) error {\n\tvar n int\n\tvar stats = make([]string, 0)\n\n\tfor _, e := range events {\n\t\tfor _, stat := range e.Stats() {\n\n\t\t\tstat = fmt.Sprintf(\"%s%s\", s.prefix, strings.Replace(stat, \"%HOST%\", Hostname, 1))\n\t\t\t_n := n + len(stat) + 1\n\n\t\t\tif _n > UDPPayloadSize {\n\t\t\t\t// with this last event, the UDP payload would be too big\n\t\t\t\tif _, err := fmt.Fprintf(s.FD, strings.Join(stats, \"\\n\")); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// reset payload after flushing, and add the last event\n\t\t\t\tstats = []string{stat}\n\t\t\t\tn = len(stat)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// can fit more into the current payload\n\t\t\tn = _n\n\t\t\tstats = append(stats, stat)\n\t\t}\n\t}\n\n\tif len(stats) != 0 {\n\t\tif _, err := fmt.Fprintf(s.FD, strings.Join(stats, \"\\n\")); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  }
]