├── LICENSE ├── README.md ├── go.mod ├── go.sum └── influxdb.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Vincent Rischmann 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-metrics-influxdb 2 | =================== 3 | 4 | This is a reporter for the [go-metrics](https://github.com/rcrowley/go-metrics) library which will post the metrics to [InfluxDB](https://influxdb.com/). 5 | 6 | This version adds a measurement for the metrics, moves the histogram bucket names into tags, similar to the behavior of hitograms in telegraf, and aligns all metrics in a batch on the same timestamp. 7 | 8 | Additionally, metrics can be aligned to the beginning of a bucket as defined by the interval. 9 | 10 | Setting align to true will cause the timestamp to be truncated down to the nearest even integral of the reporting interval. 11 | 12 | For example, if the interval is 30 seconds, tiemstamps will be aligned on :00 and :30 for every reporting interval. 13 | 14 | This also maps to a similar option in Telegraf. 15 | 16 | Note 17 | ---- 18 | 19 | This is only compatible with InfluxDB 0.9+. 20 | 21 | Usage 22 | ----- 23 | 24 | ```go 25 | import "github.com/jregovic/go-metrics-influxdb" 26 | 27 | go influxdb.InfluxDBWithTags( 28 | metrics.DefaultRegistry, // metrics registry 29 | time.Second * 10, // interval 30 | metricsHost, // the InfluxDB url 31 | database, // your InfluxDB database 32 | measurement, // your measurement 33 | metricsuser, // your InfluxDB user 34 | metricspass, // your InfluxDB password 35 | tags, // your tag set as map[string]string 36 | aligntimestamps // align the timestamps 37 | ) 38 | ``` 39 | 40 | License 41 | ------- 42 | 43 | go-metrics-influxdb is licensed under the MIT license. See the LICENSE file for details. 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vrischmann/go-metrics-influxdb 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d 7 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= 2 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 3 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= 4 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 5 | -------------------------------------------------------------------------------- /influxdb.go: -------------------------------------------------------------------------------- 1 | package influxdb 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | uurl "net/url" 7 | "time" 8 | 9 | client "github.com/influxdata/influxdb1-client" 10 | "github.com/rcrowley/go-metrics" 11 | ) 12 | 13 | type reporter struct { 14 | reg metrics.Registry 15 | interval time.Duration 16 | align bool 17 | url uurl.URL 18 | database string 19 | 20 | measurement string 21 | username string 22 | password string 23 | tags map[string]string 24 | 25 | client *client.Client 26 | } 27 | 28 | // InfluxDB starts a InfluxDB reporter which will post the metrics from the given registry at each d interval. 29 | func InfluxDB(r metrics.Registry, d time.Duration, url, database, measurement, username, password string, align bool) { 30 | InfluxDBWithTags(r, d, url, database, measurement, username, password, map[string]string{}, align) 31 | } 32 | 33 | // InfluxDBWithTags starts a InfluxDB reporter which will post the metrics from the given registry at each d interval with the specified tags 34 | func InfluxDBWithTags(r metrics.Registry, d time.Duration, url, database, measurement, username, password string, tags map[string]string, align bool) { 35 | u, err := uurl.Parse(url) 36 | if err != nil { 37 | log.Printf("unable to parse InfluxDB url %s. err=%v", url, err) 38 | return 39 | } 40 | 41 | rep := &reporter{ 42 | reg: r, 43 | interval: d, 44 | url: *u, 45 | database: database, 46 | measurement: measurement, 47 | username: username, 48 | password: password, 49 | tags: tags, 50 | align: align, 51 | } 52 | if err := rep.makeClient(); err != nil { 53 | log.Printf("unable to make InfluxDB client. err=%v", err) 54 | return 55 | } 56 | 57 | rep.run() 58 | } 59 | 60 | func (r *reporter) makeClient() (err error) { 61 | r.client, err = client.NewClient(client.Config{ 62 | URL: r.url, 63 | Username: r.username, 64 | Password: r.password, 65 | }) 66 | 67 | return 68 | } 69 | 70 | func (r *reporter) run() { 71 | intervalTicker := time.Tick(r.interval) 72 | pingTicker := time.Tick(time.Second * 5) 73 | 74 | for { 75 | select { 76 | case <-intervalTicker: 77 | if err := r.send(); err != nil { 78 | log.Printf("unable to send metrics to InfluxDB. err=%v", err) 79 | } 80 | case <-pingTicker: 81 | _, _, err := r.client.Ping() 82 | if err != nil { 83 | log.Printf("got error while sending a ping to InfluxDB, trying to recreate client. err=%v", err) 84 | 85 | if err = r.makeClient(); err != nil { 86 | log.Printf("unable to make InfluxDB client. err=%v", err) 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | func (r *reporter) send() error { 94 | var pts []client.Point 95 | 96 | now := time.Now() 97 | if r.align { 98 | now = now.Truncate(r.interval) 99 | } 100 | r.reg.Each(func(name string, i interface{}) { 101 | 102 | switch metric := i.(type) { 103 | case metrics.Counter: 104 | ms := metric.Snapshot() 105 | pts = append(pts, client.Point{ 106 | Measurement: r.measurement, 107 | Tags: r.tags, 108 | Fields: map[string]interface{}{ 109 | fmt.Sprintf("%s.count", name): ms.Count(), 110 | }, 111 | Time: now, 112 | }) 113 | case metrics.Gauge: 114 | ms := metric.Snapshot() 115 | pts = append(pts, client.Point{ 116 | Measurement: r.measurement, 117 | Tags: r.tags, 118 | Fields: map[string]interface{}{ 119 | fmt.Sprintf("%s.gauge", name): ms.Value(), 120 | }, 121 | Time: now, 122 | }) 123 | case metrics.GaugeFloat64: 124 | ms := metric.Snapshot() 125 | pts = append(pts, client.Point{ 126 | Measurement: r.measurement, 127 | Tags: r.tags, 128 | Fields: map[string]interface{}{ 129 | fmt.Sprintf("%s.gauge", name): ms.Value(), 130 | }, 131 | Time: now, 132 | }) 133 | case metrics.Histogram: 134 | ms := metric.Snapshot() 135 | ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) 136 | fields := map[string]float64{ 137 | "count": float64(ms.Count()), 138 | "max": float64(ms.Max()), 139 | "mean": ms.Mean(), 140 | "min": float64(ms.Min()), 141 | "stddev": ms.StdDev(), 142 | "variance": ms.Variance(), 143 | "p50": ps[0], 144 | "p75": ps[1], 145 | "p95": ps[2], 146 | "p99": ps[3], 147 | "p999": ps[4], 148 | "p9999": ps[5], 149 | } 150 | for k, v := range fields { 151 | pts = append(pts, client.Point{ 152 | Measurement: r.measurement, 153 | Tags: bucketTags(k, r.tags), 154 | Fields: map[string]interface{}{ 155 | fmt.Sprintf("%s.histogram", name): v, 156 | }, 157 | Time: now, 158 | }) 159 | 160 | } 161 | case metrics.Meter: 162 | ms := metric.Snapshot() 163 | fields := map[string]float64{ 164 | "count": float64(ms.Count()), 165 | "m1": ms.Rate1(), 166 | "m5": ms.Rate5(), 167 | "m15": ms.Rate15(), 168 | "mean": ms.RateMean(), 169 | } 170 | for k, v := range fields { 171 | pts = append(pts, client.Point{ 172 | Measurement: r.measurement, 173 | Tags: bucketTags(k, r.tags), 174 | Fields: map[string]interface{}{ 175 | fmt.Sprintf("%s.meter", name): v, 176 | }, 177 | Time: now, 178 | }) 179 | } 180 | 181 | case metrics.Timer: 182 | ms := metric.Snapshot() 183 | ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) 184 | fields := map[string]float64{ 185 | "count": float64(ms.Count()), 186 | "max": float64(ms.Max()), 187 | "mean": ms.Mean(), 188 | "min": float64(ms.Min()), 189 | "stddev": ms.StdDev(), 190 | "variance": ms.Variance(), 191 | "p50": ps[0], 192 | "p75": ps[1], 193 | "p95": ps[2], 194 | "p99": ps[3], 195 | "p999": ps[4], 196 | "p9999": ps[5], 197 | "m1": ms.Rate1(), 198 | "m5": ms.Rate5(), 199 | "m15": ms.Rate15(), 200 | "meanrate": ms.RateMean(), 201 | } 202 | for k, v := range fields { 203 | pts = append(pts, client.Point{ 204 | Measurement: r.measurement, 205 | Tags: bucketTags(k, r.tags), 206 | Fields: map[string]interface{}{ 207 | fmt.Sprintf("%s.timer", name): v, 208 | }, 209 | Time: now, 210 | }) 211 | } 212 | } 213 | }) 214 | 215 | bps := client.BatchPoints{ 216 | Points: pts, 217 | Database: r.database, 218 | } 219 | 220 | _, err := r.client.Write(bps) 221 | return err 222 | } 223 | 224 | func bucketTags(bucket string, tags map[string]string) map[string]string { 225 | m := map[string]string{} 226 | for tk, tv := range tags { 227 | m[tk] = tv 228 | } 229 | m["bucket"] = bucket 230 | return m 231 | } 232 | --------------------------------------------------------------------------------