155 lines
3.1 KiB
Go
155 lines
3.1 KiB
Go
|
package expvar
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/influxdb/telegraf/plugins"
|
||
|
)
|
||
|
|
||
|
var sampleConfig = `
|
||
|
# Specify services via an array of tables
|
||
|
[[plugins.expvar.services]]
|
||
|
# Name for the service being polled
|
||
|
name = "influxdb"
|
||
|
|
||
|
# Multiple URLs from which to read expvars
|
||
|
urls = [
|
||
|
"http://localhost:8086/debug/vars"
|
||
|
]
|
||
|
`
|
||
|
|
||
|
type Expvar struct {
|
||
|
Services []Service
|
||
|
}
|
||
|
|
||
|
type Service struct {
|
||
|
Name string
|
||
|
URLs []string `toml:"urls"`
|
||
|
}
|
||
|
|
||
|
func (*Expvar) Description() string {
|
||
|
return "Read InfluxDB-style expvar metrics from one or more HTTP endpoints"
|
||
|
}
|
||
|
|
||
|
func (*Expvar) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (e *Expvar) Gather(acc plugins.Accumulator) error {
|
||
|
var wg sync.WaitGroup
|
||
|
|
||
|
totalURLs := 0
|
||
|
for _, service := range e.Services {
|
||
|
totalURLs += len(service.URLs)
|
||
|
}
|
||
|
errorChannel := make(chan error, totalURLs)
|
||
|
|
||
|
for _, service := range e.Services {
|
||
|
for _, u := range service.URLs {
|
||
|
wg.Add(1)
|
||
|
go func(service Service, url string) {
|
||
|
defer wg.Done()
|
||
|
if err := e.gatherURL(acc, service, url); err != nil {
|
||
|
errorChannel <- err
|
||
|
}
|
||
|
}(service, u)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
wg.Wait()
|
||
|
close(errorChannel)
|
||
|
|
||
|
// 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"))
|
||
|
}
|
||
|
|
||
|
type point struct {
|
||
|
Name string `json:"name"`
|
||
|
Tags map[string]string `json:"tags"`
|
||
|
Values map[string]interface{} `json:"values"`
|
||
|
}
|
||
|
|
||
|
// Gathers data from a particular URL
|
||
|
// Parameters:
|
||
|
// acc : The telegraf Accumulator to use
|
||
|
// service: the service being queried
|
||
|
// url : endpoint to send request to
|
||
|
//
|
||
|
// Returns:
|
||
|
// error: Any error that may have occurred
|
||
|
func (e *Expvar) gatherURL(
|
||
|
acc plugins.Accumulator,
|
||
|
service Service,
|
||
|
url string,
|
||
|
) error {
|
||
|
resp, err := http.Get(url)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
// Can't predict what all is going to be in the response, so decode the top keys one at a time.
|
||
|
dec := json.NewDecoder(resp.Body)
|
||
|
|
||
|
// Parse beginning of object
|
||
|
if t, err := dec.Token(); err != nil {
|
||
|
return err
|
||
|
} else if t != json.Delim('{') {
|
||
|
return errors.New("expvars must be a JSON object")
|
||
|
}
|
||
|
|
||
|
// Loop through rest of object
|
||
|
for {
|
||
|
// Nothing left in this object, we're done
|
||
|
if !dec.More() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// Read in a string key. We actually don't care about the top-level keys
|
||
|
_, err := dec.Token()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Attempt to parse a whole object into a point.
|
||
|
// It might be a non-object, like a string or array.
|
||
|
// If we fail to decode it into a point, ignore it and move on.
|
||
|
var p point
|
||
|
if err := dec.Decode(&p); err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if p.Name == "" || p.Tags == nil || p.Values == nil || len(p.Values) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
p.Tags["expvar_url"] = url
|
||
|
|
||
|
acc.AddFields(
|
||
|
service.Name+"_"+p.Name,
|
||
|
p.Values,
|
||
|
p.Tags,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
plugins.Add("expvar", func() plugins.Plugin {
|
||
|
return &Expvar{}
|
||
|
})
|
||
|
}
|