Utilizing new client and overhauling Accumulator interface

Fixes #280
Fixes #281
Fixes #289
This commit is contained in:
Cameron Sparr 2015-10-16 16:13:32 -06:00
parent 6263bc2d1b
commit c26ce9c4fe
27 changed files with 498 additions and 550 deletions

View File

@ -2,6 +2,13 @@
### Release Notes ### Release Notes
- The -test flag will now only output 2 collections for plugins that need it - The -test flag will now only output 2 collections for plugins that need it
- There is a new agent configuration option: `flush_interval`. This option tells
Telegraf how often to flush data to InfluxDB and other output sinks. For example,
users can set `interval = "2s"` and `flush_interval = "60s"` for Telegraf to
collect data every 2 seconds, and flush every 60 seconds.
- `precision` and `utc` are no longer valid agent config values. `precision` has
moved to the `influxdb` output config, where it will continue to default to "s"
- debug and test output will now print the raw line-protocol string
### Features ### Features
- [#205](https://github.com/influxdb/telegraf/issues/205): Include per-db redis keyspace info - [#205](https://github.com/influxdb/telegraf/issues/205): Include per-db redis keyspace info
@ -18,6 +25,8 @@ of metrics collected and from how many plugins.
- [#262](https://github.com/influxdb/telegraf/pull/262): zookeeper plugin, thanks @jrxFive! - [#262](https://github.com/influxdb/telegraf/pull/262): zookeeper plugin, thanks @jrxFive!
- [#237](https://github.com/influxdb/telegraf/pull/237): statsd service plugin, thanks @sparrc - [#237](https://github.com/influxdb/telegraf/pull/237): statsd service plugin, thanks @sparrc
- [#273](https://github.com/influxdb/telegraf/pull/273): puppet agent plugin, thats @jrxFive! - [#273](https://github.com/influxdb/telegraf/pull/273): puppet agent plugin, thats @jrxFive!
- [#280](https://github.com/influxdb/telegraf/issues/280): Use InfluxDB client v2.
- [#281](https://github.com/influxdb/telegraf/issues/281): Eliminate need to deep copy Batch Points.
### Bugfixes ### Bugfixes
- [#228](https://github.com/influxdb/telegraf/pull/228): New version of package will replace old one. Thanks @ekini! - [#228](https://github.com/influxdb/telegraf/pull/228): New version of package will replace old one. Thanks @ekini!
@ -25,6 +34,8 @@ of metrics collected and from how many plugins.
- [#261](https://github.com/influxdb/telegraf/issues/260): RabbitMQ panics if wrong credentials given. Thanks @ekini! - [#261](https://github.com/influxdb/telegraf/issues/260): RabbitMQ panics if wrong credentials given. Thanks @ekini!
- [#245](https://github.com/influxdb/telegraf/issues/245): Document Exec plugin example. Thanks @ekini! - [#245](https://github.com/influxdb/telegraf/issues/245): Document Exec plugin example. Thanks @ekini!
- [#264](https://github.com/influxdb/telegraf/issues/264): logrotate config file fixes. Thanks @linsomniac! - [#264](https://github.com/influxdb/telegraf/issues/264): logrotate config file fixes. Thanks @linsomniac!
- [#290](https://github.com/influxdb/telegraf/issues/290): Fix some plugins sending their values as strings.
- [#289](https://github.com/influxdb/telegraf/issues/289): Fix accumulator panic on nil tags.
## v0.1.9 [2015-09-22] ## v0.1.9 [2015-09-22]

View File

@ -36,11 +36,14 @@ type Plugin interface {
} }
type Accumulator interface { type Accumulator interface {
Add(measurement string, value interface{}, tags map[string]string) Add(measurement string,
AddFieldsWithTime(measurement string, value interface{},
values map[string]interface{},
tags map[string]string, tags map[string]string,
timestamp time.Time) timestamp ...time.Time)
AddFields(measurement string,
fields map[string]interface{},
tags map[string]string,
timestamp ...time.Time)
} }
``` ```
@ -81,8 +84,8 @@ func Gather(acc plugins.Accumulator) error {
"pid": fmt.Sprintf("%d", process.Pid), "pid": fmt.Sprintf("%d", process.Pid),
} }
acc.Add("cpu", process.CPUTime, tags) acc.Add("cpu", process.CPUTime, tags, time.Now())
acc.Add("memory", process.MemoryBytes, tags) acc.Add("memory", process.MemoryBytes, tags, time.Now())
} }
} }
``` ```
@ -179,7 +182,7 @@ type Output interface {
Close() error Close() error
Description() string Description() string
SampleConfig() string SampleConfig() string
Write(client.BatchPoints) error Write(points []*client.Point) error
} }
``` ```
@ -214,8 +217,8 @@ func (s *Simple) Close() error {
return nil return nil
} }
func (s *Simple) Write(bp client.BatchPoints) error { func (s *Simple) Write(points []*client.Point) error {
for _, pt := range bp { for _, pt := range points {
// write `pt` to the output sink here // write `pt` to the output sink here
} }
return nil return nil

View File

@ -37,8 +37,11 @@ ifeq ($(UNAME), Linux)
ADVERTISED_HOST=localhost docker-compose --file scripts/docker-compose.yml up -d ADVERTISED_HOST=localhost docker-compose --file scripts/docker-compose.yml up -d
endif endif
test: prepare docker-compose test: test-cleanup prepare docker-compose
$(GOBIN)/godep go test ./... # Sleeping for kafka leadership election, TSDB setup, etc.
sleep 30
# Setup SUCCESS, running tests
godep go test ./...
test-short: prepare test-short: prepare
$(GOBIN)/godep go test -short ./... $(GOBIN)/godep go test -short ./...

View File

@ -134,6 +134,7 @@ measurements at a 10s interval and will collect totalcpu & percpu data.
[outputs.influxdb] [outputs.influxdb]
url = "http://192.168.59.103:8086" # required. url = "http://192.168.59.103:8086" # required.
database = "telegraf" # required. database = "telegraf" # required.
precision = "s"
# PLUGINS # PLUGINS
[cpu] [cpu]
@ -196,7 +197,7 @@ Telegraf currently has support for collecting metrics from
* disk * disk
* swap * swap
## Service Plugins ## Supported Service Plugins
Telegraf can collect metrics via the following services Telegraf can collect metrics via the following services

View File

@ -2,188 +2,138 @@ package telegraf
import ( import (
"fmt" "fmt"
"sort"
"strings"
"sync" "sync"
"time" "time"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
) )
// BatchPoints is used to send a batch of data in a single write from telegraf type Accumulator interface {
// to influx Add(measurement string, value interface{},
type BatchPoints struct { tags map[string]string, t ...time.Time)
AddFields(measurement string, fields map[string]interface{},
tags map[string]string, t ...time.Time)
SetDefaultTags(tags map[string]string)
AddDefaultTag(key, value string)
Prefix() string
SetPrefix(prefix string)
Debug() bool
SetDebug(enabled bool)
}
func NewAccumulator(
plugin *ConfiguredPlugin,
points chan *client.Point,
) Accumulator {
acc := accumulator{}
acc.points = points
acc.plugin = plugin
return &acc
}
type accumulator struct {
sync.Mutex sync.Mutex
client.BatchPoints points chan *client.Point
Debug bool defaultTags map[string]string
Prefix string debug bool
Config *ConfiguredPlugin plugin *ConfiguredPlugin
prefix string
} }
// deepcopy returns a deep copy of the BatchPoints object. This is primarily so func (ac *accumulator) Add(
// we can do multithreaded output flushing (see Agent.flush)
func (bp *BatchPoints) deepcopy() *BatchPoints {
bp.Lock()
defer bp.Unlock()
var bpc BatchPoints
bpc.Time = bp.Time
bpc.Precision = bp.Precision
bpc.Tags = make(map[string]string)
for k, v := range bp.Tags {
bpc.Tags[k] = v
}
var pts []client.Point
for _, pt := range bp.Points {
var ptc client.Point
ptc.Measurement = pt.Measurement
ptc.Time = pt.Time
ptc.Precision = pt.Precision
ptc.Raw = pt.Raw
ptc.Tags = make(map[string]string)
ptc.Fields = make(map[string]interface{})
for k, v := range pt.Tags {
ptc.Tags[k] = v
}
for k, v := range pt.Fields {
ptc.Fields[k] = v
}
pts = append(pts, ptc)
}
bpc.Points = pts
return &bpc
}
// Add adds a measurement
func (bp *BatchPoints) Add(
measurement string, measurement string,
val interface{}, value interface{},
tags map[string]string, tags map[string]string,
t ...time.Time,
) { ) {
fields := make(map[string]interface{}) fields := make(map[string]interface{})
fields["value"] = val fields["value"] = value
bp.AddFields(measurement, fields, tags) ac.AddFields(measurement, fields, tags, t...)
} }
// AddFieldsWithTime adds a measurement with a provided timestamp func (ac *accumulator) AddFields(
func (bp *BatchPoints) AddFieldsWithTime(
measurement string, measurement string,
fields map[string]interface{}, fields map[string]interface{},
tags map[string]string, tags map[string]string,
timestamp time.Time, t ...time.Time,
) { ) {
// TODO this function should add the fields with the timestamp, but that will
// need to wait for the InfluxDB point precision/unit to be fixed
bp.AddFields(measurement, fields, tags)
// bp.Lock()
// defer bp.Unlock()
// measurement = bp.Prefix + measurement if tags == nil {
tags = make(map[string]string)
}
// if bp.Config != nil { // InfluxDB client/points does not support writing uint64
// if !bp.Config.ShouldPass(measurement, tags) { // TODO fix when it does
// return // https://github.com/influxdb/influxdb/pull/4508
// }
// }
// if bp.Debug {
// var tg []string
// for k, v := range tags {
// tg = append(tg, fmt.Sprintf("%s=\"%s\"", k, v))
// }
// var vals []string
// for k, v := range fields {
// vals = append(vals, fmt.Sprintf("%s=%v", k, v))
// }
// sort.Strings(tg)
// sort.Strings(vals)
// fmt.Printf("> [%s] %s %s\n", strings.Join(tg, " "), measurement, strings.Join(vals, " "))
// }
// bp.Points = append(bp.Points, client.Point{
// Measurement: measurement,
// Tags: tags,
// Fields: fields,
// Time: timestamp,
// })
}
// AddFields will eventually replace the Add function, once we move to having a
// single plugin as a single measurement with multiple fields
func (bp *BatchPoints) AddFields(
measurement string,
fields map[string]interface{},
tags map[string]string,
) {
bp.Lock()
defer bp.Unlock()
// InfluxDB does not support writing uint64
for k, v := range fields { for k, v := range fields {
switch val := v.(type) { switch val := v.(type) {
case uint64: case uint64:
if val < uint64(9223372036854775808) { if val < uint64(9223372036854775808) {
fields[k] = int64(val) fields[k] = int64(val)
} else {
fields[k] = int64(9223372036854775807)
} }
} }
} }
measurement = bp.Prefix + measurement var timestamp time.Time
if len(t) > 0 {
timestamp = t[0]
} else {
timestamp = time.Now()
}
if bp.Config != nil { if ac.plugin != nil {
if !bp.Config.ShouldPass(measurement, tags) { if !ac.plugin.ShouldPass(measurement, tags) {
return return
} }
} }
// Apply BatchPoints tags to tags passed in, giving precedence to those for k, v := range ac.defaultTags {
// passed in. This is so that plugins have the ability to override global if _, ok := tags[k]; !ok {
// tags.
for k, v := range bp.Tags {
_, ok := tags[k]
if !ok {
tags[k] = v tags[k] = v
} }
} }
if bp.Debug { if ac.prefix != "" {
var tg []string measurement = ac.prefix + measurement
for k, v := range tags {
tg = append(tg, fmt.Sprintf("%s=\"%s\"", k, v))
}
var vals []string
for k, v := range fields {
vals = append(vals, fmt.Sprintf("%s=%v", k, v))
}
sort.Strings(tg)
sort.Strings(vals)
fmt.Printf("> [%s] %s %s\n", strings.Join(tg, " "), measurement, strings.Join(vals, " "))
} }
bp.Points = append(bp.Points, client.Point{ pt := client.NewPoint(measurement, tags, fields, timestamp)
Measurement: measurement, if ac.debug {
Tags: tags, fmt.Println("> " + pt.String())
Fields: fields, }
}) ac.points <- pt
}
func (ac *accumulator) SetDefaultTags(tags map[string]string) {
ac.defaultTags = tags
}
func (ac *accumulator) AddDefaultTag(key, value string) {
ac.defaultTags[key] = value
}
func (ac *accumulator) Prefix() string {
return ac.prefix
}
func (ac *accumulator) SetPrefix(prefix string) {
ac.prefix = prefix
}
func (ac *accumulator) Debug() bool {
return ac.debug
}
func (ac *accumulator) SetDebug(debug bool) {
ac.debug = debug
} }

225
agent.go
View File

@ -11,6 +11,8 @@ import (
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
"github.com/influxdb/telegraf/plugins" "github.com/influxdb/telegraf/plugins"
"github.com/influxdb/influxdb/client/v2"
) )
type runningOutput struct { type runningOutput struct {
@ -30,6 +32,13 @@ type Agent struct {
// Interval at which to gather information // Interval at which to gather information
Interval Duration Interval Duration
// Interval at which to flush data
FlushInterval Duration
// TODO(cam): Remove UTC and Precision parameters, they are no longer
// valid for the agent config. Leaving them here for now for backwards-
// compatability
// Option for outputting data in UTC // Option for outputting data in UTC
UTC bool `toml:"utc"` UTC bool `toml:"utc"`
@ -50,10 +59,11 @@ type Agent struct {
// NewAgent returns an Agent struct based off the given Config // NewAgent returns an Agent struct based off the given Config
func NewAgent(config *Config) (*Agent, error) { func NewAgent(config *Config) (*Agent, error) {
agent := &Agent{ agent := &Agent{
Config: config, Config: config,
Interval: Duration{10 * time.Second}, Interval: Duration{10 * time.Second},
UTC: true, FlushInterval: Duration{10 * time.Second},
Precision: "s", UTC: true,
Precision: "s",
} }
// Apply the toml table to the agent config, overriding defaults // Apply the toml table to the agent config, overriding defaults
@ -170,11 +180,9 @@ func (a *Agent) LoadPlugins(filters []string) ([]string, error) {
return names, nil return names, nil
} }
// crankParallel runs the plugins that are using the same reporting interval // gatherParallel runs the plugins that are using the same reporting interval
// as the telegraf agent. // as the telegraf agent.
func (a *Agent) crankParallel() error { func (a *Agent) gatherParallel(pointChan chan *client.Point) error {
points := make(chan *BatchPoints, len(a.plugins))
var wg sync.WaitGroup var wg sync.WaitGroup
start := time.Now() start := time.Now()
@ -189,100 +197,51 @@ func (a *Agent) crankParallel() error {
go func(plugin *runningPlugin) { go func(plugin *runningPlugin) {
defer wg.Done() defer wg.Done()
var bp BatchPoints acc := NewAccumulator(plugin.config, pointChan)
bp.Debug = a.Debug acc.SetDebug(a.Debug)
bp.Prefix = plugin.name + "_" acc.SetPrefix(plugin.name + "_")
bp.Config = plugin.config acc.SetDefaultTags(a.Config.Tags)
bp.Precision = a.Precision
bp.Tags = a.Config.Tags
if err := plugin.plugin.Gather(&bp); err != nil { if err := plugin.plugin.Gather(acc); err != nil {
log.Printf("Error in plugin [%s]: %s", plugin.name, err) log.Printf("Error in plugin [%s]: %s", plugin.name, err)
} }
points <- &bp
}(plugin) }(plugin)
} }
wg.Wait() wg.Wait()
close(points)
var bp BatchPoints
bp.Time = time.Now()
if a.UTC {
bp.Time = bp.Time.UTC()
}
bp.Precision = a.Precision
for sub := range points {
bp.Points = append(bp.Points, sub.Points...)
}
elapsed := time.Since(start) elapsed := time.Since(start)
log.Printf("Cranking default (%s) interval, gathered %d metrics from %d plugins in %s\n", log.Printf("Default (%s) interval, gathered metrics from %d plugins in %s\n",
a.Interval, len(bp.Points), counter, elapsed) a.Interval, counter, elapsed)
return a.flush(&bp) return nil
} }
// crank is mostly for test purposes. // gatherSeparate runs the plugins that have been configured with their own
func (a *Agent) crank() error {
var bp BatchPoints
bp.Debug = a.Debug
bp.Precision = a.Precision
for _, plugin := range a.plugins {
bp.Prefix = plugin.name + "_"
bp.Config = plugin.config
err := plugin.plugin.Gather(&bp)
if err != nil {
return err
}
}
bp.Tags = a.Config.Tags
bp.Time = time.Now()
if a.UTC {
bp.Time = bp.Time.UTC()
}
return a.flush(&bp)
}
// crankSeparate runs the plugins that have been configured with their own
// reporting interval. // reporting interval.
func (a *Agent) crankSeparate(shutdown chan struct{}, plugin *runningPlugin) error { func (a *Agent) gatherSeparate(
shutdown chan struct{},
plugin *runningPlugin,
pointChan chan *client.Point,
) error {
ticker := time.NewTicker(plugin.config.Interval) ticker := time.NewTicker(plugin.config.Interval)
for { for {
var bp BatchPoints
var outerr error var outerr error
start := time.Now() start := time.Now()
bp.Debug = a.Debug acc := NewAccumulator(plugin.config, pointChan)
acc.SetDebug(a.Debug)
acc.SetPrefix(plugin.name + "_")
acc.SetDefaultTags(a.Config.Tags)
bp.Prefix = plugin.name + "_" if err := plugin.plugin.Gather(acc); err != nil {
bp.Config = plugin.config
bp.Precision = a.Precision
bp.Tags = a.Config.Tags
if err := plugin.plugin.Gather(&bp); err != nil {
log.Printf("Error in plugin [%s]: %s", plugin.name, err) log.Printf("Error in plugin [%s]: %s", plugin.name, err)
outerr = errors.New("Error encountered processing plugins & outputs")
}
bp.Time = time.Now()
if a.UTC {
bp.Time = bp.Time.UTC()
} }
elapsed := time.Since(start) elapsed := time.Since(start)
log.Printf("Cranking separate (%s) interval, gathered %d metrics from %s in %s\n", log.Printf("Separate (%s) interval, gathered metrics from %s in %s\n",
plugin.config.Interval, len(bp.Points), plugin.name, elapsed) plugin.config.Interval, plugin.name, elapsed)
if err := a.flush(&bp); err != nil {
outerr = errors.New("Error encountered processing plugins & outputs")
}
if outerr != nil { if outerr != nil {
return outerr return outerr
@ -297,20 +256,55 @@ func (a *Agent) crankSeparate(shutdown chan struct{}, plugin *runningPlugin) err
} }
} }
func (a *Agent) flush(bp *BatchPoints) error { // Test verifies that we can 'Gather' from all plugins with their configured
// Config struct
func (a *Agent) Test() error {
shutdown := make(chan struct{})
defer close(shutdown)
pointChan := make(chan *client.Point)
go a.flusher(shutdown, pointChan)
for _, plugin := range a.plugins {
acc := NewAccumulator(plugin.config, pointChan)
acc.SetDebug(true)
acc.SetPrefix(plugin.name + "_")
fmt.Printf("* Plugin: %s, Collection 1\n", plugin.name)
if plugin.config.Interval != 0 {
fmt.Printf("* Internal: %s\n", plugin.config.Interval)
}
if err := plugin.plugin.Gather(acc); err != nil {
return err
}
// Special instructions for some plugins. cpu, for example, needs to be
// run twice in order to return cpu usage percentages.
switch plugin.name {
case "cpu":
time.Sleep(500 * time.Millisecond)
fmt.Printf("* Plugin: %s, Collection 2\n", plugin.name)
if err := plugin.plugin.Gather(acc); err != nil {
return err
}
}
}
return nil
}
func (a *Agent) flush(points []*client.Point) error {
var wg sync.WaitGroup var wg sync.WaitGroup
var outerr error var outerr error
for _, o := range a.outputs { for _, o := range a.outputs {
wg.Add(1) wg.Add(1)
// Copy BatchPoints
bpc := bp.deepcopy()
go func(ro *runningOutput) { go func(ro *runningOutput) {
defer wg.Done() defer wg.Done()
// Log all output errors: // Log all output errors:
if err := ro.output.Write(bpc.BatchPoints); err != nil { if err := ro.output.Write(points); err != nil {
log.Printf("Error in output [%s]: %s", ro.name, err) log.Printf("Error in output [%s]: %s", ro.name, err)
outerr = errors.New("Error encountered flushing outputs") outerr = errors.New("Error encountered flushing outputs")
} }
@ -321,45 +315,44 @@ func (a *Agent) flush(bp *BatchPoints) error {
return outerr return outerr
} }
// Test verifies that we can 'Gather' from all plugins with their configured // flusher monitors the points input channel and flushes on the minimum interval
// Config struct func (a *Agent) flusher(shutdown chan struct{}, pointChan chan *client.Point) error {
func (a *Agent) Test() error { ticker := time.NewTicker(a.FlushInterval.Duration)
var acc BatchPoints points := make([]*client.Point, 0)
for {
acc.Debug = true select {
case <-shutdown:
for _, plugin := range a.plugins { return nil
acc.Prefix = plugin.name + "_" case <-ticker.C:
acc.Config = plugin.config start := time.Now()
if err := a.flush(points); err != nil {
fmt.Printf("* Plugin: %s, Collection 1\n", plugin.name) log.Printf(err.Error())
if plugin.config.Interval != 0 {
fmt.Printf("* Internal: %s\n", plugin.config.Interval)
}
if err := plugin.plugin.Gather(&acc); err != nil {
return err
}
// Special instructions for some plugins. cpu, for example, needs to be
// run twice in order to return cpu usage percentages.
switch plugin.name {
case "cpu":
time.Sleep(500 * time.Millisecond)
fmt.Printf("* Plugin: %s, Collection 2\n", plugin.name)
if err := plugin.plugin.Gather(&acc); err != nil {
return err
} }
elapsed := time.Since(start)
log.Printf("Flushed %d metrics in %s\n", len(points), elapsed)
points = make([]*client.Point, 0)
case pt := <-pointChan:
points = append(points, pt)
} }
} }
return nil
} }
// Run runs the agent daemon, gathering every Interval // Run runs the agent daemon, gathering every Interval
func (a *Agent) Run(shutdown chan struct{}) error { func (a *Agent) Run(shutdown chan struct{}) error {
var wg sync.WaitGroup var wg sync.WaitGroup
// channel shared between all plugin threads for accumulating points
pointChan := make(chan *client.Point, 1000)
wg.Add(1)
go func() {
defer wg.Done()
if err := a.flusher(shutdown, pointChan); err != nil {
log.Printf("Flusher routine failed, exiting: %s\n", err.Error())
close(shutdown)
}
}()
for _, plugin := range a.plugins { for _, plugin := range a.plugins {
// Start service of any ServicePlugins // Start service of any ServicePlugins
@ -374,12 +367,12 @@ func (a *Agent) Run(shutdown chan struct{}) error {
} }
// Special handling for plugins that have their own collection interval // Special handling for plugins that have their own collection interval
// configured. Default intervals are handled below with crankParallel // configured. Default intervals are handled below with gatherParallel
if plugin.config.Interval != 0 { if plugin.config.Interval != 0 {
wg.Add(1) wg.Add(1)
go func(plugin *runningPlugin) { go func(plugin *runningPlugin) {
defer wg.Done() defer wg.Done()
if err := a.crankSeparate(shutdown, plugin); err != nil { if err := a.gatherSeparate(shutdown, plugin, pointChan); err != nil {
log.Printf(err.Error()) log.Printf(err.Error())
} }
}(plugin) }(plugin)
@ -391,7 +384,7 @@ func (a *Agent) Run(shutdown chan struct{}) error {
ticker := time.NewTicker(a.Interval.Duration) ticker := time.NewTicker(a.Interval.Duration)
for { for {
if err := a.crankParallel(); err != nil { if err := a.gatherParallel(pointChan); err != nil {
log.Printf(err.Error()) log.Printf(err.Error())
} }

View File

@ -74,7 +74,7 @@ func TestAgent_DrivesMetrics(t *testing.T) {
plugin.On("Add", "foo", 1.2, nil).Return(nil) plugin.On("Add", "foo", 1.2, nil).Return(nil)
plugin.On("Add", "bar", 888, nil).Return(nil) plugin.On("Add", "bar", 888, nil).Return(nil)
err := a.crank() err := a.gather()
require.NoError(t, err) require.NoError(t, err)
} }
@ -112,7 +112,7 @@ func TestAgent_AppliesTags(t *testing.T) {
plugin.On("Read").Return(msgs, nil) plugin.On("Read").Return(msgs, nil)
metrics.On("Receive", m2).Return(nil) metrics.On("Receive", m2).Return(nil)
err := a.crank() err := a.gather()
require.NoError(t, err) require.NoError(t, err)
} }
*/ */

View File

@ -355,11 +355,8 @@ var header = `# Telegraf configuration
[agent] [agent]
# Default data collection interval for all plugins # Default data collection interval for all plugins
interval = "10s" interval = "10s"
# If utc = false, uses local time (utc is highly recommended) # Default data flushing interval for all outputs
utc = true flush_interval = "10s"
# Precision of writes, valid values are n, u, ms, s, m, and h
# note: using second precision greatly helps InfluxDB compression
precision = "s"
# run telegraf in debug mode # run telegraf in debug mode
debug = false debug = false
# Override default hostname, if empty use os.Hostname() # Override default hostname, if empty use os.Hostname()

View File

@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
"github.com/streadway/amqp" "github.com/streadway/amqp"
) )
@ -82,39 +82,21 @@ func (q *AMQP) Description() string {
return "Configuration for the AMQP server to send metrics to" return "Configuration for the AMQP server to send metrics to"
} }
func (q *AMQP) Write(bp client.BatchPoints) error { func (q *AMQP) Write(points []*client.Point) error {
q.Lock() q.Lock()
defer q.Unlock() defer q.Unlock()
if len(bp.Points) == 0 { if len(points) == 0 {
return nil return nil
} }
var zero_time time.Time for _, p := range points {
for _, p := range bp.Points {
// Combine tags from Point and BatchPoints and grab the resulting // Combine tags from Point and BatchPoints and grab the resulting
// line-protocol output string to write to AMQP // line-protocol output string to write to AMQP
var value, key string var value, key string
if p.Raw != "" { value = p.String()
value = p.Raw
} else {
for k, v := range bp.Tags {
if p.Tags == nil {
p.Tags = make(map[string]string, len(bp.Tags))
}
p.Tags[k] = v
}
if p.Time == zero_time {
if bp.Time == zero_time {
p.Time = time.Now()
} else {
p.Time = bp.Time
}
}
value = p.MarshalString()
}
if q.RoutingTag != "" { if q.RoutingTag != "" {
if h, ok := p.Tags[q.RoutingTag]; ok { if h, ok := p.Tags()[q.RoutingTag]; ok {
key = h key = h
} }
} }

View File

@ -23,6 +23,6 @@ func TestConnectAndWrite(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Verify that we can successfully write data to the amqp broker // Verify that we can successfully write data to the amqp broker
err = q.Write(testutil.MockBatchPoints()) err = q.Write(testutil.MockBatchPoints().Points())
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -8,7 +8,7 @@ import (
"net/url" "net/url"
"sort" "sort"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
t "github.com/influxdb/telegraf" t "github.com/influxdb/telegraf"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
) )
@ -59,19 +59,19 @@ func (d *Datadog) Connect() error {
return nil return nil
} }
func (d *Datadog) Write(bp client.BatchPoints) error { func (d *Datadog) Write(points []*client.Point) error {
if len(bp.Points) == 0 { if len(points) == 0 {
return nil return nil
} }
ts := TimeSeries{ ts := TimeSeries{
Series: make([]*Metric, len(bp.Points)), Series: make([]*Metric, len(points)),
} }
for index, pt := range bp.Points { for index, pt := range points {
metric := &Metric{ metric := &Metric{
Metric: pt.Measurement, Metric: pt.Name(),
Tags: buildTags(bp.Tags, pt.Tags), Tags: buildTags(pt.Tags()),
} }
if p, err := buildPoint(bp, pt); err == nil { if p, err := buildPoint(pt); err == nil {
metric.Points[0] = p metric.Points[0] = p
} }
ts.Series[index] = metric ts.Series[index] = metric
@ -114,13 +114,18 @@ func (d *Datadog) authenticatedUrl() string {
return fmt.Sprintf("%s?%s", d.apiUrl, q.Encode()) return fmt.Sprintf("%s?%s", d.apiUrl, q.Encode())
} }
func buildTags(bpTags map[string]string, ptTags map[string]string) []string { func buildPoint(pt *client.Point) (Point, error) {
tags := make([]string, (len(bpTags) + len(ptTags))) var p Point
index := 0 if err := p.setValue(pt.Fields()["value"]); err != nil {
for k, v := range bpTags { return p, fmt.Errorf("unable to extract value from Fields, %s", err.Error())
tags[index] = fmt.Sprintf("%s:%s", k, v)
index += 1
} }
p[0] = float64(pt.Time().Unix())
return p, nil
}
func buildTags(ptTags map[string]string) []string {
tags := make([]string, len(ptTags))
index := 0
for k, v := range ptTags { for k, v := range ptTags {
tags[index] = fmt.Sprintf("%s:%s", k, v) tags[index] = fmt.Sprintf("%s:%s", k, v)
index += 1 index += 1
@ -129,19 +134,6 @@ func buildTags(bpTags map[string]string, ptTags map[string]string) []string {
return tags return tags
} }
func buildPoint(bp client.BatchPoints, pt client.Point) (Point, error) {
var p Point
if err := p.setValue(pt.Fields["value"]); err != nil {
return p, fmt.Errorf("unable to extract value from Fields, %s", err.Error())
}
if pt.Time.IsZero() {
p[0] = float64(bp.Time.Unix())
} else {
p[0] = float64(pt.Time.Unix())
}
return p, nil
}
func (p *Point) setValue(v interface{}) error { func (p *Point) setValue(v interface{}) error {
switch d := v.(type) { switch d := v.(type) {
case int: case int:

View File

@ -11,7 +11,7 @@ import (
"github.com/influxdb/telegraf/testutil" "github.com/influxdb/telegraf/testutil"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -38,7 +38,7 @@ func TestUriOverride(t *testing.T) {
d.Apikey = "123456" d.Apikey = "123456"
err := d.Connect() err := d.Connect()
require.NoError(t, err) require.NoError(t, err)
err = d.Write(testutil.MockBatchPoints()) err = d.Write(testutil.MockBatchPoints().Points())
require.NoError(t, err) require.NoError(t, err)
} }
@ -57,7 +57,7 @@ func TestBadStatusCode(t *testing.T) {
d.Apikey = "123456" d.Apikey = "123456"
err := d.Connect() err := d.Connect()
require.NoError(t, err) require.NoError(t, err)
err = d.Write(testutil.MockBatchPoints()) err = d.Write(testutil.MockBatchPoints().Points())
if err == nil { if err == nil {
t.Errorf("error expected but none returned") t.Errorf("error expected but none returned")
} else { } else {
@ -74,28 +74,24 @@ func TestAuthenticatedUrl(t *testing.T) {
func TestBuildTags(t *testing.T) { func TestBuildTags(t *testing.T) {
var tagtests = []struct { var tagtests = []struct {
bpIn map[string]string
ptIn map[string]string ptIn map[string]string
outTags []string outTags []string
}{ }{
{ {
map[string]string{"one": "two"}, map[string]string{"one": "two", "three": "four"},
map[string]string{"three": "four"},
[]string{"one:two", "three:four"}, []string{"one:two", "three:four"},
}, },
{ {
map[string]string{"aaa": "bbb"}, map[string]string{"aaa": "bbb"},
map[string]string{},
[]string{"aaa:bbb"}, []string{"aaa:bbb"},
}, },
{ {
map[string]string{},
map[string]string{}, map[string]string{},
[]string{}, []string{},
}, },
} }
for _, tt := range tagtests { for _, tt := range tagtests {
tags := buildTags(tt.bpIn, tt.ptIn) tags := buildTags(tt.ptIn)
if !reflect.DeepEqual(tags, tt.outTags) { if !reflect.DeepEqual(tags, tt.outTags) {
t.Errorf("\nexpected %+v\ngot %+v\n", tt.outTags, tags) t.Errorf("\nexpected %+v\ngot %+v\n", tt.outTags, tags)
} }
@ -103,92 +99,114 @@ func TestBuildTags(t *testing.T) {
} }
func TestBuildPoint(t *testing.T) { func TestBuildPoint(t *testing.T) {
tags := make(map[string]string)
var tagtests = []struct { var tagtests = []struct {
bpIn client.BatchPoints ptIn *client.Point
ptIn client.Point
outPt Point outPt Point
err error err error
}{ }{
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test1",
tags,
map[string]interface{}{"value": 0.0},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
0.0,
}, },
client.Point{
Fields: map[string]interface{}{"value": 0.0},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 0.0},
nil, nil,
}, },
{ {
client.BatchPoints{}, client.NewPoint(
client.Point{ "test2",
Fields: map[string]interface{}{"value": 1.0}, tags,
Time: time.Date(2010, time.December, 10, 23, 0, 0, 0, time.UTC), map[string]interface{}{"value": 1.0},
time.Date(2010, time.December, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2010, time.December, 10, 23, 0, 0, 0, time.UTC).Unix()),
1.0,
}, },
Point{float64(time.Date(2010, time.December, 10, 23, 0, 0, 0, time.UTC).Unix()), 1.0},
nil, nil,
}, },
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test3",
tags,
map[string]interface{}{"value": 10},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
10.0,
}, },
client.Point{
Fields: map[string]interface{}{"value": 10},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 10.0},
nil, nil,
}, },
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test4",
tags,
map[string]interface{}{"value": int32(112345)},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
112345.0,
}, },
client.Point{
Fields: map[string]interface{}{"value": int32(112345)},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 112345.0},
nil, nil,
}, },
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test5",
tags,
map[string]interface{}{"value": int64(112345)},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
112345.0,
}, },
client.Point{
Fields: map[string]interface{}{"value": int64(112345)},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 112345.0},
nil, nil,
}, },
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test6",
tags,
map[string]interface{}{"value": float32(11234.5)},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
11234.5,
}, },
client.Point{
Fields: map[string]interface{}{"value": float32(11234.5)},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 11234.5},
nil, nil,
}, },
{ {
client.BatchPoints{ client.NewPoint(
Time: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), "test7",
tags,
map[string]interface{}{"value": "11234.5"},
time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
),
Point{
float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()),
11234.5,
}, },
client.Point{
Fields: map[string]interface{}{"value": "11234.5"},
},
Point{float64(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).Unix()), 11234.5},
fmt.Errorf("unable to extract value from Fields, undeterminable type"), fmt.Errorf("unable to extract value from Fields, undeterminable type"),
}, },
} }
for _, tt := range tagtests { for _, tt := range tagtests {
pt, err := buildPoint(tt.bpIn, tt.ptIn) pt, err := buildPoint(tt.ptIn)
if err != nil && tt.err == nil { if err != nil && tt.err == nil {
t.Errorf("unexpected error, %+v\n", err) t.Errorf("%s: unexpected error, %+v\n", tt.ptIn.Name(), err)
} }
if tt.err != nil && err == nil { if tt.err != nil && err == nil {
t.Errorf("expected an error (%s) but none returned", tt.err.Error()) t.Errorf("%s: expected an error (%s) but none returned", tt.ptIn.Name(), tt.err.Error())
} }
if !reflect.DeepEqual(pt, tt.outPt) && tt.err == nil { if !reflect.DeepEqual(pt, tt.outPt) && tt.err == nil {
t.Errorf("\nexpected %+v\ngot %+v\n", tt.outPt, pt) t.Errorf("%s: \nexpected %+v\ngot %+v\n", tt.ptIn.Name(), tt.outPt, pt)
} }
} }
} }

View File

@ -8,7 +8,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
t "github.com/influxdb/telegraf" t "github.com/influxdb/telegraf"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
) )
@ -21,9 +21,10 @@ type InfluxDB struct {
Password string Password string
Database string Database string
UserAgent string UserAgent string
Precision string
Timeout t.Duration Timeout t.Duration
conns []*client.Client conns []client.Client
} }
var sampleConfig = ` var sampleConfig = `
@ -32,6 +33,7 @@ var sampleConfig = `
urls = ["http://localhost:8086"] # required urls = ["http://localhost:8086"] # required
# The target database for metrics (telegraf will create it if not exists) # The target database for metrics (telegraf will create it if not exists)
database = "telegraf" # required database = "telegraf" # required
precision = "s"
# Connection timeout (for the connection with InfluxDB), formatted as a string. # Connection timeout (for the connection with InfluxDB), formatted as a string.
# Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". # Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
@ -63,18 +65,15 @@ func (i *InfluxDB) Connect() error {
urls = append(urls, u) urls = append(urls, u)
} }
var conns []*client.Client var conns []client.Client
for _, parsed_url := range urls { for _, parsed_url := range urls {
c, err := client.NewClient(client.Config{ c := client.NewClient(client.Config{
URL: *parsed_url, URL: parsed_url,
Username: i.Username, Username: i.Username,
Password: i.Password, Password: i.Password,
UserAgent: i.UserAgent, UserAgent: i.UserAgent,
Timeout: i.Timeout.Duration, Timeout: i.Timeout.Duration,
}) })
if err != nil {
return err
}
conns = append(conns, c) conns = append(conns, c)
} }
@ -113,15 +112,22 @@ func (i *InfluxDB) Description() string {
// Choose a random server in the cluster to write to until a successful write // Choose a random server in the cluster to write to until a successful write
// occurs, logging each unsuccessful. If all servers fail, return error. // occurs, logging each unsuccessful. If all servers fail, return error.
func (i *InfluxDB) Write(bp client.BatchPoints) error { func (i *InfluxDB) Write(points []*client.Point) error {
bp.Database = i.Database bp, _ := client.NewBatchPoints(client.BatchPointsConfig{
Database: i.Database,
Precision: i.Precision,
})
for _, point := range points {
bp.AddPoint(point)
}
// This will get set to nil if a successful write occurs // This will get set to nil if a successful write occurs
err := errors.New("Could not write to any InfluxDB server in cluster") err := errors.New("Could not write to any InfluxDB server in cluster")
p := rand.Perm(len(i.conns)) p := rand.Perm(len(i.conns))
for _, n := range p { for _, n := range p {
if _, e := i.conns[n].Write(bp); e != nil { if e := i.conns[n].Write(bp); e != nil {
log.Println("ERROR: " + e.Error()) log.Println("ERROR: " + e.Error())
} else { } else {
err = nil err = nil

View File

@ -3,10 +3,9 @@ package kafka
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/Shopify/sarama" "github.com/Shopify/sarama"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
) )
@ -52,40 +51,21 @@ func (k *Kafka) Description() string {
return "Configuration for the Kafka server to send metrics to" return "Configuration for the Kafka server to send metrics to"
} }
func (k *Kafka) Write(bp client.BatchPoints) error { func (k *Kafka) Write(points []*client.Point) error {
if len(bp.Points) == 0 { if len(points) == 0 {
return nil return nil
} }
var zero_time time.Time for _, p := range points {
for _, p := range bp.Points {
// Combine tags from Point and BatchPoints and grab the resulting // Combine tags from Point and BatchPoints and grab the resulting
// line-protocol output string to write to Kafka // line-protocol output string to write to Kafka
var value string value := p.String()
if p.Raw != "" {
value = p.Raw
} else {
for k, v := range bp.Tags {
if p.Tags == nil {
p.Tags = make(map[string]string, len(bp.Tags))
}
p.Tags[k] = v
}
if p.Time == zero_time {
if bp.Time == zero_time {
p.Time = time.Now()
} else {
p.Time = bp.Time
}
}
value = p.MarshalString()
}
m := &sarama.ProducerMessage{ m := &sarama.ProducerMessage{
Topic: k.Topic, Topic: k.Topic,
Value: sarama.StringEncoder(value), Value: sarama.StringEncoder(value),
} }
if h, ok := p.Tags[k.RoutingTag]; ok { if h, ok := p.Tags()[k.RoutingTag]; ok {
m.Key = sarama.StringEncoder(h) m.Key = sarama.StringEncoder(h)
} }

View File

@ -23,6 +23,6 @@ func TestConnectAndWrite(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Verify that we can successfully write data to the kafka broker // Verify that we can successfully write data to the kafka broker
err = k.Write(testutil.MockBatchPoints()) err = k.Write(testutil.MockBatchPoints().Points())
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -10,7 +10,7 @@ import (
"sync" "sync"
paho "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" paho "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
t "github.com/influxdb/telegraf" t "github.com/influxdb/telegraf"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
) )
@ -78,35 +78,31 @@ func (m *MQTT) Description() string {
return "Configuration for MQTT server to send metrics to" return "Configuration for MQTT server to send metrics to"
} }
func (m *MQTT) Write(bp client.BatchPoints) error { func (m *MQTT) Write(points []*client.Point) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if len(bp.Points) == 0 { if len(points) == 0 {
return nil return nil
} }
hostname, ok := bp.Tags["host"] hostname, ok := points[0].Tags()["host"]
if !ok { if !ok {
hostname = "" hostname = ""
} }
for _, p := range bp.Points { for _, p := range points {
var t []string var t []string
if m.TopicPrefix != "" { if m.TopicPrefix != "" {
t = append(t, m.TopicPrefix) t = append(t, m.TopicPrefix)
} }
tm := strings.Split(p.Measurement, "_") tm := strings.Split(p.Name(), "_")
if len(tm) < 2 { if len(tm) < 2 {
tm = []string{p.Measurement, "stat"} tm = []string{p.Name(), "stat"}
} }
t = append(t, "host", hostname, tm[0], tm[1]) t = append(t, "host", hostname, tm[0], tm[1])
topic := strings.Join(t, "/") topic := strings.Join(t, "/")
var value string value := p.String()
if p.Raw != "" {
value = p.Raw
} else {
value = getValue(p.Fields["value"])
}
err := m.publish(topic, value) err := m.publish(topic, value)
if err != nil { if err != nil {
return fmt.Errorf("Could not write to MQTT server, %s", err) return fmt.Errorf("Could not write to MQTT server, %s", err)
@ -116,23 +112,6 @@ func (m *MQTT) Write(bp client.BatchPoints) error {
return nil return nil
} }
func getValue(v interface{}) string {
var ret string
switch v.(type) {
default:
ret = fmt.Sprintf("%v", v)
case bool:
ret = fmt.Sprintf("%t", v)
case float32, float64:
ret = fmt.Sprintf("%f", v)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
ret = fmt.Sprintf("%d", v)
case string, []byte:
ret = fmt.Sprintf("%s", v)
}
return ret
}
func (m *MQTT) publish(topic, body string) error { func (m *MQTT) publish(topic, body string) error {
token := m.Client.Publish(topic, 0, false, body) token := m.Client.Publish(topic, 0, false, body)
token.Wait() token.Wait()

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
"github.com/influxdb/telegraf/outputs" "github.com/influxdb/telegraf/outputs"
) )
@ -51,15 +51,15 @@ func (o *OpenTSDB) Connect() error {
return fmt.Errorf("OpenTSDB: TCP address cannot be resolved") return fmt.Errorf("OpenTSDB: TCP address cannot be resolved")
} }
connection, err := net.DialTCP("tcp", nil, tcpAddr) connection, err := net.DialTCP("tcp", nil, tcpAddr)
defer connection.Close()
if err != nil { if err != nil {
return fmt.Errorf("OpenTSDB: Telnet connect fail") return fmt.Errorf("OpenTSDB: Telnet connect fail")
} }
defer connection.Close()
return nil return nil
} }
func (o *OpenTSDB) Write(bp client.BatchPoints) error { func (o *OpenTSDB) Write(points []*client.Point) error {
if len(bp.Points) == 0 { if len(points) == 0 {
return nil return nil
} }
var timeNow = time.Now() var timeNow = time.Now()
@ -70,19 +70,20 @@ func (o *OpenTSDB) Write(bp client.BatchPoints) error {
if err != nil { if err != nil {
return fmt.Errorf("OpenTSDB: Telnet connect fail") return fmt.Errorf("OpenTSDB: Telnet connect fail")
} }
for _, pt := range bp.Points { for _, pt := range points {
metric := &MetricLine{ metric := &MetricLine{
Metric: fmt.Sprintf("%s%s", o.Prefix, pt.Measurement), Metric: fmt.Sprintf("%s%s", o.Prefix, pt.Name()),
Timestamp: timeNow.Unix(), Timestamp: timeNow.Unix(),
} }
metricValue, buildError := buildValue(bp, pt)
metricValue, buildError := buildValue(pt)
if buildError != nil { if buildError != nil {
fmt.Printf("OpenTSDB: %s\n", buildError.Error()) fmt.Printf("OpenTSDB: %s\n", buildError.Error())
continue continue
} }
metric.Value = metricValue metric.Value = metricValue
tagsSlice := buildTags(bp.Tags, pt.Tags) tagsSlice := buildTags(pt.Tags())
metric.Tags = fmt.Sprint(strings.Join(tagsSlice, " ")) metric.Tags = fmt.Sprint(strings.Join(tagsSlice, " "))
messageLine := fmt.Sprintf("put %s %v %s %s\n", metric.Metric, metric.Timestamp, metric.Value, metric.Tags) messageLine := fmt.Sprintf("put %s %v %s %s\n", metric.Metric, metric.Timestamp, metric.Value, metric.Tags)
@ -99,13 +100,9 @@ func (o *OpenTSDB) Write(bp client.BatchPoints) error {
return nil return nil
} }
func buildTags(bpTags map[string]string, ptTags map[string]string) []string { func buildTags(ptTags map[string]string) []string {
tags := make([]string, (len(bpTags) + len(ptTags))) tags := make([]string, len(ptTags))
index := 0 index := 0
for k, v := range bpTags {
tags[index] = fmt.Sprintf("%s=%s", k, v)
index += 1
}
for k, v := range ptTags { for k, v := range ptTags {
tags[index] = fmt.Sprintf("%s=%s", k, v) tags[index] = fmt.Sprintf("%s=%s", k, v)
index += 1 index += 1
@ -114,9 +111,9 @@ func buildTags(bpTags map[string]string, ptTags map[string]string) []string {
return tags return tags
} }
func buildValue(bp client.BatchPoints, pt client.Point) (string, error) { func buildValue(pt *client.Point) (string, error) {
var retv string var retv string
var v = pt.Fields["value"] var v = pt.Fields()["value"]
switch p := v.(type) { switch p := v.(type) {
case int64: case int64:
retv = IntToString(int64(p)) retv = IntToString(int64(p))

View File

@ -3,47 +3,42 @@ package opentsdb
import ( import (
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
"github.com/influxdb/telegraf/testutil" "github.com/influxdb/telegraf/testutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestBuildTagsTelnet(t *testing.T) { func TestBuildTagsTelnet(t *testing.T) {
var tagtests = []struct { var tagtests = []struct {
bpIn map[string]string
ptIn map[string]string ptIn map[string]string
outTags []string outTags []string
}{ }{
{ {
map[string]string{"one": "two"}, map[string]string{"one": "two", "three": "four"},
map[string]string{"three": "four"},
[]string{"one=two", "three=four"}, []string{"one=two", "three=four"},
}, },
{ {
map[string]string{"aaa": "bbb"}, map[string]string{"aaa": "bbb"},
map[string]string{},
[]string{"aaa=bbb"}, []string{"aaa=bbb"},
}, },
{ {
map[string]string{"one": "two"}, map[string]string{"one": "two", "aaa": "bbb"},
map[string]string{"aaa": "bbb"},
[]string{"aaa=bbb", "one=two"}, []string{"aaa=bbb", "one=two"},
}, },
{ {
map[string]string{},
map[string]string{}, map[string]string{},
[]string{}, []string{},
}, },
} }
for _, tt := range tagtests { for _, tt := range tagtests {
tags := buildTags(tt.bpIn, tt.ptIn) tags := buildTags(tt.ptIn)
if !reflect.DeepEqual(tags, tt.outTags) { if !reflect.DeepEqual(tags, tt.outTags) {
t.Errorf("\nexpected %+v\ngot %+v\n", tt.outTags, tags) t.Errorf("\nexpected %+v\ngot %+v\n", tt.outTags, tags)
} }
} }
} }
func TestWrite(t *testing.T) { func TestWrite(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("Skipping integration test in short mode") t.Skip("Skipping integration test in short mode")
@ -60,36 +55,24 @@ func TestWrite(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Verify that we can successfully write data to OpenTSDB // Verify that we can successfully write data to OpenTSDB
err = o.Write(testutil.MockBatchPoints()) err = o.Write(testutil.MockBatchPoints().Points())
require.NoError(t, err) require.NoError(t, err)
// Verify postive and negative test cases of writing data // Verify postive and negative test cases of writing data
var bp client.BatchPoints bp := testutil.MockBatchPoints()
bp.Time = time.Now() tags := make(map[string]string)
bp.Tags = map[string]string{"testkey": "testvalue"} bp.AddPoint(client.NewPoint("justametric.float", tags,
bp.Points = []client.Point{ map[string]interface{}{"value": float64(1.0)}))
{ bp.AddPoint(client.NewPoint("justametric.int", tags,
Measurement: "justametric.float", map[string]interface{}{"value": int64(123456789)}))
Fields: map[string]interface{}{"value": float64(1.0)}, bp.AddPoint(client.NewPoint("justametric.uint", tags,
}, map[string]interface{}{"value": uint64(123456789012345)}))
{ bp.AddPoint(client.NewPoint("justametric.string", tags,
Measurement: "justametric.int", map[string]interface{}{"value": "Lorem Ipsum"}))
Fields: map[string]interface{}{"value": int64(123456789)}, bp.AddPoint(client.NewPoint("justametric.anotherfloat", tags,
}, map[string]interface{}{"value": float64(42.0)}))
{
Measurement: "justametric.uint", err = o.Write(bp.Points())
Fields: map[string]interface{}{"value": uint64(123456789012345)},
},
{
Measurement: "justametric.string",
Fields: map[string]interface{}{"value": "Lorem Ipsum"},
},
{
Measurement: "justametric.anotherfloat",
Fields: map[string]interface{}{"value": float64(42.0)},
},
}
err = o.Write(bp)
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -1,7 +1,7 @@
package outputs package outputs
import ( import (
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
) )
type Output interface { type Output interface {
@ -9,7 +9,7 @@ type Output interface {
Close() error Close() error
Description() string Description() string
SampleConfig() string SampleConfig() string
Write(client.BatchPoints) error Write(points []*client.Point) error
} }
type Creator func() Output type Creator func() Output

View File

@ -3,11 +3,13 @@ package exec
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"github.com/gonuts/go-shellquote" "github.com/gonuts/go-shellquote"
"github.com/influxdb/telegraf/plugins" "github.com/influxdb/telegraf/plugins"
"math" "math"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"time" "time"
) )
@ -88,19 +90,32 @@ func (e *Exec) Description() string {
func (e *Exec) Gather(acc plugins.Accumulator) error { func (e *Exec) Gather(acc plugins.Accumulator) error {
var wg sync.WaitGroup var wg sync.WaitGroup
var outerr error errorChannel := make(chan error, len(e.Commands))
for _, c := range e.Commands { for _, c := range e.Commands {
wg.Add(1) wg.Add(1)
go func(c *Command, acc plugins.Accumulator) { go func(c *Command, acc plugins.Accumulator) {
defer wg.Done() defer wg.Done()
outerr = e.gatherCommand(c, acc) err := e.gatherCommand(c, acc)
if err != nil {
errorChannel <- err
}
}(c, acc) }(c, acc)
} }
wg.Wait() wg.Wait()
close(errorChannel)
return outerr // Get all errors and return them as one giant error
errorStrings := []string{}
for err := range errorChannel {
errorStrings = append(errorStrings, err.Error())
}
if len(errorStrings) == 0 {
return nil
}
return errors.New(strings.Join(errorStrings, "\n"))
} }
func (e *Exec) gatherCommand(c *Command, acc plugins.Accumulator) error { func (e *Exec) gatherCommand(c *Command, acc plugins.Accumulator) error {

View File

@ -93,7 +93,7 @@ func emitMetrics(k *Kafka, acc plugins.Accumulator, metricConsumer <-chan []byte
} }
for _, point := range points { for _, point := range points {
acc.AddFieldsWithTime(point.Name(), point.Fields(), point.Tags(), point.Time()) acc.AddFields(point.Name(), point.Fields(), point.Tags(), point.Time())
} }
case <-timeout: case <-timeout:
return nil return nil

View File

@ -50,5 +50,5 @@ func TestReadsMetricsFromKafka(t *testing.T) {
"direction": "in", "direction": "in",
"region": "us-west", "region": "us-west",
}, point.Tags) }, point.Tags)
assert.Equal(t, time.Unix(0, 1422568543702900257), point.Time) assert.Equal(t, time.Unix(0, 1422568543702900257).Unix(), point.Time.Unix())
} }

View File

@ -89,7 +89,7 @@ func (d *MongodbData) addStat(acc plugins.Accumulator, statLine reflect.Value, s
} }
func (d *MongodbData) add(acc plugins.Accumulator, key string, val interface{}) { func (d *MongodbData) add(acc plugins.Accumulator, key string, val interface{}) {
acc.AddFieldsWithTime( acc.AddFields(
key, key,
map[string]interface{}{ map[string]interface{}{
"value": val, "value": val,

View File

@ -6,17 +6,15 @@ type Accumulator interface {
// Create a point with a value, decorating it with tags // Create a point with a value, decorating it with tags
// NOTE: tags is expected to be owned by the caller, don't mutate // NOTE: tags is expected to be owned by the caller, don't mutate
// it after passing to Add. // it after passing to Add.
Add(measurement string, value interface{}, tags map[string]string) Add(measurement string,
value interface{},
// Create a point with a set of values, decorating it with tags
// NOTE: tags and values are expected to be owned by the caller, don't mutate
// them after passing to AddFieldsWithTime.
AddFieldsWithTime(
measurement string,
values map[string]interface{},
tags map[string]string, tags map[string]string,
timestamp time.Time, t ...time.Time)
)
AddFields(measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time)
} }
type Plugin interface { type Plugin interface {

View File

@ -22,19 +22,20 @@ func TestZookeeperGeneratesMetrics(t *testing.T) {
err := z.Gather(&acc) err := z.Gather(&acc)
require.NoError(t, err) require.NoError(t, err)
intMetrics := []string{"zookeeper_avg_latency", intMetrics := []string{
"zookeeper_max_latency", "avg_latency",
"zookeeper_min_latency", "max_latency",
"zookeeper_packets_received", "min_latency",
"zookeeper_packets_sent", "packets_received",
"zookeeper_outstanding_requests", "packets_sent",
"zookeeper_znode_count", "outstanding_requests",
"zookeeper_watch_count", "znode_count",
"zookeeper_ephemerals_count", "watch_count",
"zookeeper_approximate_data_size", "ephemerals_count",
"zookeeper_pending_syncs", "approximate_data_size",
"zookeeper_open_file_descriptor_count", "open_file_descriptor_count",
"zookeeper_max_file_descriptor_count"} "max_file_descriptor_count",
}
for _, metric := range intMetrics { for _, metric := range intMetrics {
assert.True(t, acc.HasIntValue(metric), metric) assert.True(t, acc.HasIntValue(metric), metric)

View File

@ -22,7 +22,12 @@ type Accumulator struct {
} }
// Add adds a measurement point to the accumulator // Add adds a measurement point to the accumulator
func (a *Accumulator) Add(measurement string, value interface{}, tags map[string]string) { func (a *Accumulator) Add(
measurement string,
value interface{},
tags map[string]string,
t ...time.Time,
) {
a.Lock() a.Lock()
defer a.Unlock() defer a.Unlock()
if tags == nil { if tags == nil {
@ -38,24 +43,58 @@ func (a *Accumulator) Add(measurement string, value interface{}, tags map[string
) )
} }
// AddFieldsWithTime adds a measurement point with a specified timestamp. // AddFields adds a measurement point with a specified timestamp.
func (a *Accumulator) AddFieldsWithTime( func (a *Accumulator) AddFields(
measurement string, measurement string,
values map[string]interface{}, values map[string]interface{},
tags map[string]string, tags map[string]string,
timestamp time.Time, timestamp ...time.Time,
) { ) {
a.Lock()
defer a.Unlock()
var t time.Time
if len(timestamp) > 0 {
t = timestamp[0]
} else {
t = time.Now()
}
a.Points = append( a.Points = append(
a.Points, a.Points,
&Point{ &Point{
Measurement: measurement, Measurement: measurement,
Values: values, Values: values,
Tags: tags, Tags: tags,
Time: timestamp, Time: t,
}, },
) )
} }
func (a *Accumulator) SetDefaultTags(tags map[string]string) {
// stub for implementing Accumulator interface.
}
func (a *Accumulator) AddDefaultTag(key, value string) {
// stub for implementing Accumulator interface.
}
func (a *Accumulator) Prefix() string {
// stub for implementing Accumulator interface.
return ""
}
func (a *Accumulator) SetPrefix(prefix string) {
// stub for implementing Accumulator interface.
}
func (a *Accumulator) Debug() bool {
// stub for implementing Accumulator interface.
return true
}
func (a *Accumulator) SetDebug(debug bool) {
// stub for implementing Accumulator interface.
}
// Get gets the specified measurement point from the accumulator // Get gets the specified measurement point from the accumulator
func (a *Accumulator) Get(measurement string) (*Point, bool) { func (a *Accumulator) Get(measurement string) (*Point, bool) {
for _, p := range a.Points { for _, p := range a.Points {

View File

@ -4,9 +4,8 @@ import (
"net" "net"
"net/url" "net/url"
"os" "os"
"time"
"github.com/influxdb/influxdb/client" "github.com/influxdb/influxdb/client/v2"
) )
var localhost = "localhost" var localhost = "localhost"
@ -34,13 +33,14 @@ func GetLocalHost() string {
// MockBatchPoints returns a mock BatchPoints object for using in unit tests // MockBatchPoints returns a mock BatchPoints object for using in unit tests
// of telegraf output sinks. // of telegraf output sinks.
func MockBatchPoints() client.BatchPoints { func MockBatchPoints() client.BatchPoints {
var bp client.BatchPoints // Create a new point batch
bp.Time = time.Now() bp, _ := client.NewBatchPoints(client.BatchPointsConfig{})
bp.Tags = map[string]string{"tag1": "value1"}
bp.Points = []client.Point{ // Create a point and add to batch
{ tags := map[string]string{"tag1": "value1"}
Fields: map[string]interface{}{"value": 1.0}, fields := map[string]interface{}{"value": 1.0}
}, pt := client.NewPoint("test_point", tags, fields)
} bp.AddPoint(pt)
return bp return bp
} }