Add Prometheus parser and Kubernetes input plugin
This commit is contained in:
parent
6a601ceb97
commit
a5288fc280
|
@ -16,6 +16,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
|
_ "github.com/influxdata/telegraf/plugins/inputs/influxdb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
|
_ "github.com/influxdata/telegraf/plugins/inputs/jolokia"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
|
_ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/kubernetes"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
|
_ "github.com/influxdata/telegraf/plugins/inputs/leofs"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
_ "github.com/influxdata/telegraf/plugins/inputs/lustre2"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
|
_ "github.com/influxdata/telegraf/plugins/inputs/mailchimp"
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Kubernetes Input Plugin
|
||||||
|
|
||||||
|
The Kubernetes plugin can gather metrics from Kubernetes services
|
||||||
|
- APIServer
|
||||||
|
- Scheduler
|
||||||
|
- Controller Manager
|
||||||
|
- Kubelet
|
||||||
|
|
||||||
|
### Configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Get metrics from Kubernetes services
|
||||||
|
[[inputs.kubernetes.apiserver]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:8080"
|
||||||
|
endpoint = "/metrics"
|
||||||
|
timeout = 5.0
|
||||||
|
# includes only metrics which match one of the
|
||||||
|
# following regexp
|
||||||
|
includes = ["apiserver_.*"]
|
||||||
|
|
||||||
|
[[inputs.kubernetes.scheduler]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:10251"
|
||||||
|
endpoint = "/metrics"
|
||||||
|
timeout = 1.0
|
||||||
|
# DO NOT include metrics which match one of the
|
||||||
|
# following regexp
|
||||||
|
excludes = ["scheduler_.*"]
|
||||||
|
|
||||||
|
[[inputs.kubernetes.controllermanager]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:10252"
|
||||||
|
|
||||||
|
[[inputs.kubernetes.kubelet]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:4194"
|
||||||
|
# You should increase metric_buffer_limit
|
||||||
|
# Because of number of kubelet metrics
|
||||||
|
# otherwise you can limit metrics with
|
||||||
|
# the following 'excludes' argument
|
||||||
|
excludes = ["container_.*"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Measurements & Fields:
|
||||||
|
|
||||||
|
This input plugin get measurements and fields from Kubernetes services.
|
||||||
|
If new metrics appear on K8S services, this plugin will grab it without
|
||||||
|
modification.
|
||||||
|
|
||||||
|
- http_request_duration_microseconds"
|
||||||
|
- 0.5
|
||||||
|
- 0.9
|
||||||
|
- 0.99
|
||||||
|
- count
|
||||||
|
- sum
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
|
||||||
|
This input plugin get metrics tags from Kubernetes services.
|
||||||
|
If new tags appear on K8S services, this plugin will grab it without
|
||||||
|
modification.
|
||||||
|
|
||||||
|
- kubeservice
|
||||||
|
- serverURL
|
||||||
|
- handler
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
- .......
|
||||||
|
|
||||||
|
### Example Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./telegraf -config telegraf.conf -input-filter example -test
|
||||||
|
process_cpu_seconds_total,kubeservice=kubelet,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics counter=452366.52 1455436043283475033
|
||||||
|
ssh_tunnel_open_fail_count,kubeservice=kubelet,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics counter=0 1455436043284057879
|
||||||
|
get_token_count,kubeservice=kubelet,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics counter=0 1455436043285030409
|
||||||
|
http_requests_total,code=200,handler=prometheus,kubeservice=kubelet,method=get,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics counter=116 1455436043285364118
|
||||||
|
kubelet_generate_pod_status_latency_microseconds,kubeservice=kubelet,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics 0.5=94441,0.9=186928,0.99=236698,count=0,sum=128409528225 1455436043285715318
|
||||||
|
process_max_fds,kubeservice=kubelet,serverURL=http://mtl-blade19-02.nuance.com:4194/metrics gauge=1000000 1455436043286061204
|
||||||
|
```
|
|
@ -0,0 +1,318 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
// Plugin inspired from
|
||||||
|
// https://github.com/prometheus/prom2json/blob/master/main.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
"github.com/influxdata/telegraf/plugins/parsers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Kubernetes struct {
|
||||||
|
Apiserver []Apiserver
|
||||||
|
Scheduler []Scheduler
|
||||||
|
Controllermanager []Controllermanager
|
||||||
|
Kubelet []Kubelet
|
||||||
|
client HTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type KubeService struct {
|
||||||
|
Url string
|
||||||
|
Endpoint string
|
||||||
|
Timeout float64
|
||||||
|
Excludes []string
|
||||||
|
Includes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Apiserver struct {
|
||||||
|
KubeService
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scheduler struct {
|
||||||
|
KubeService
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controllermanager struct {
|
||||||
|
KubeService
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kubelet struct {
|
||||||
|
KubeService
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPClient interface {
|
||||||
|
// Returns the result of an http request
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// req: HTTP request object
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// http.Response: HTTP respons object
|
||||||
|
// error : Any error that may have occurred
|
||||||
|
MakeRequest(req *http.Request, timeout float64) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RealHTTPClient struct {
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RealHTTPClient) MakeRequest(req *http.Request, timeout float64) (*http.Response, error) {
|
||||||
|
c.client.Timeout = time.Duration(timeout) * time.Second
|
||||||
|
return c.client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
# Get metrics from Kubernetes services
|
||||||
|
[[inputs.kubernetes.apiserver]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:8080"
|
||||||
|
endpoint = "/metrics"
|
||||||
|
timeout = 5.0
|
||||||
|
# includes only metrics which match one of the
|
||||||
|
# following regexp
|
||||||
|
includes = ["apiserver_.*"]
|
||||||
|
|
||||||
|
[[inputs.kubernetes.scheduler]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:10251"
|
||||||
|
endpoint = "/metrics"
|
||||||
|
timeout = 1.0
|
||||||
|
# DO NOT include metrics which match one of the
|
||||||
|
# following regexp
|
||||||
|
excludes = ["scheduler_.*"]
|
||||||
|
|
||||||
|
[[inputs.kubernetes.controllermanager]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:10252"
|
||||||
|
|
||||||
|
[[inputs.kubernetes.kubelet]]
|
||||||
|
url = "http://mtl-nvcbladea-15.nuance.com:4194"
|
||||||
|
# You should increase metric_buffer_limit
|
||||||
|
# Because of number of kubelet metrics
|
||||||
|
# otherwise you can limit metrics with
|
||||||
|
# the following 'excludes' argument
|
||||||
|
excludes = ["container_.*"]
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
func (k *Kubernetes) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubernetes) Description() string {
|
||||||
|
return "Read metrics from Kubernetes services"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gathers data for all servers.
|
||||||
|
func (k *Kubernetes) Gather(acc telegraf.Accumulator) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
errorChannel := make(chan error,
|
||||||
|
len(k.Apiserver)+len(k.Scheduler)+
|
||||||
|
len(k.Controllermanager)+len(k.Kubelet))
|
||||||
|
|
||||||
|
// Routine to gather metrics and add it to acc
|
||||||
|
GatherMetric := func(url string, endpoint string, serviceType string,
|
||||||
|
excludes []string, includes []string, timeout float64) {
|
||||||
|
defer wg.Done()
|
||||||
|
if timeout == 0. {
|
||||||
|
timeout = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint == "" {
|
||||||
|
endpoint = "/metrics"
|
||||||
|
} else if string(endpoint[0]) != "/" {
|
||||||
|
endpoint = "/" + endpoint
|
||||||
|
}
|
||||||
|
url = url + endpoint
|
||||||
|
if err := k.gatherServer(acc, url, serviceType, excludes, includes, timeout); err != nil {
|
||||||
|
errorChannel <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apiservers
|
||||||
|
for _, service := range k.Apiserver {
|
||||||
|
wg.Add(1)
|
||||||
|
serviceType := "apiserver"
|
||||||
|
go GatherMetric(service.Url, service.Endpoint, serviceType, service.Excludes, service.Includes, service.Timeout)
|
||||||
|
}
|
||||||
|
// Schedulers
|
||||||
|
for _, service := range k.Scheduler {
|
||||||
|
wg.Add(1)
|
||||||
|
serviceType := "scheduler"
|
||||||
|
go GatherMetric(service.Url, service.Endpoint, serviceType, service.Excludes, service.Includes, service.Timeout)
|
||||||
|
}
|
||||||
|
// Controllermanager
|
||||||
|
for _, service := range k.Controllermanager {
|
||||||
|
wg.Add(1)
|
||||||
|
serviceType := "controllermanager"
|
||||||
|
go GatherMetric(service.Url, service.Endpoint, serviceType, service.Excludes, service.Includes, service.Timeout)
|
||||||
|
}
|
||||||
|
// Kubelet
|
||||||
|
for _, service := range k.Kubelet {
|
||||||
|
wg.Add(1)
|
||||||
|
serviceType := "kubelet"
|
||||||
|
go GatherMetric(service.Url, service.Endpoint, serviceType, service.Excludes, service.Includes, service.Timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gathers data from a particular server
|
||||||
|
// Parameters:
|
||||||
|
// acc : The telegraf Accumulator to use
|
||||||
|
// serverURL : endpoint to send request to
|
||||||
|
// serviceType : service type (apiserver, kubelet, ...)
|
||||||
|
// timeout : http timeout
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// error: Any error that may have occurred
|
||||||
|
func (k *Kubernetes) gatherServer(
|
||||||
|
acc telegraf.Accumulator,
|
||||||
|
serviceURL string,
|
||||||
|
serviceType string,
|
||||||
|
excludes []string,
|
||||||
|
includes []string,
|
||||||
|
timeout float64,
|
||||||
|
) error {
|
||||||
|
// Get raw data from Kube service
|
||||||
|
collectDate := time.Now()
|
||||||
|
raw_data, err := k.sendRequest(serviceURL, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Prepare Prometheus parser config
|
||||||
|
config := parsers.Config{
|
||||||
|
DataFormat: "prometheus",
|
||||||
|
}
|
||||||
|
// Create Prometheus parser
|
||||||
|
promparser, err := parsers.NewParser(&config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Set default tags
|
||||||
|
tags := map[string]string{
|
||||||
|
"kubeservice": serviceType,
|
||||||
|
"serverURL": serviceURL,
|
||||||
|
}
|
||||||
|
promparser.SetDefaultTags(tags)
|
||||||
|
|
||||||
|
// Parseing
|
||||||
|
metrics, err := promparser.Parse(raw_data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Add (or not) collected metrics
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if len(includes) > 0 {
|
||||||
|
// includes regexp
|
||||||
|
IncludeMetric:
|
||||||
|
for _, include := range includes {
|
||||||
|
r, err := regexp.Compile(include)
|
||||||
|
if err == nil {
|
||||||
|
if r.MatchString(metric.Name()) {
|
||||||
|
acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), collectDate)
|
||||||
|
break IncludeMetric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(excludes) > 0 {
|
||||||
|
// excludes regexp
|
||||||
|
includeMetric := true
|
||||||
|
ExcludeMetric:
|
||||||
|
for _, exclude := range excludes {
|
||||||
|
r, err := regexp.Compile(exclude)
|
||||||
|
if err == nil {
|
||||||
|
if r.MatchString(metric.Name()) {
|
||||||
|
includeMetric = false
|
||||||
|
break ExcludeMetric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if includeMetric {
|
||||||
|
acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), collectDate)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no includes/excludes regexp
|
||||||
|
acc.AddFields(metric.Name(), metric.Fields(), metric.Tags(), metric.Time())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends an HTTP request to the server using the Kubernetes object's HTTPClient
|
||||||
|
// Parameters:
|
||||||
|
// serverURL: endpoint to send request to
|
||||||
|
// timeout: request timeout
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// []byte: body of the response
|
||||||
|
// error : Any error that may have occurred
|
||||||
|
func (k *Kubernetes) sendRequest(serverURL string, timeout float64) ([]byte, error) {
|
||||||
|
// Prepare URL
|
||||||
|
requestURL, err := url.Parse(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid server URL \"%s\"", serverURL)
|
||||||
|
}
|
||||||
|
params := url.Values{}
|
||||||
|
requestURL.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("GET", requestURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request
|
||||||
|
resp, err := k.client.MakeRequest(req, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process response
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
|
||||||
|
requestURL.String(),
|
||||||
|
resp.StatusCode,
|
||||||
|
http.StatusText(resp.StatusCode),
|
||||||
|
http.StatusOK,
|
||||||
|
http.StatusText(http.StatusOK))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("kubernetes", func() telegraf.Input {
|
||||||
|
return &Kubernetes{client: RealHTTPClient{client: &http.Client{}}}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const validData = `# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
|
||||||
|
# TYPE cadvisor_version_info gauge
|
||||||
|
cadvisor_version_info{cadvisorRevision="",cadvisorVersion="",dockerVersion="1.8.2",kernelVersion="3.10.0-229.20.1.el7.x86_64",osVersion="CentOS Linux 7 (Core)"} 1
|
||||||
|
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
|
||||||
|
# TYPE go_gc_duration_seconds summary
|
||||||
|
go_gc_duration_seconds{quantile="0"} 0.013534896000000001
|
||||||
|
go_gc_duration_seconds{quantile="0.25"} 0.02469263
|
||||||
|
go_gc_duration_seconds{quantile="0.5"} 0.033727822000000005
|
||||||
|
go_gc_duration_seconds{quantile="0.75"} 0.03840335
|
||||||
|
go_gc_duration_seconds{quantile="1"} 0.049956604
|
||||||
|
go_gc_duration_seconds_sum 1970.341293002
|
||||||
|
go_gc_duration_seconds_count 65952
|
||||||
|
# HELP http_request_duration_microseconds The HTTP request latencies in microseconds.
|
||||||
|
# TYPE http_request_duration_microseconds summary
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 552048.506
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds_sum{handler="prometheus"} 1.8909097205e+07
|
||||||
|
http_request_duration_microseconds_count{handler="prometheus"} 9
|
||||||
|
# HELP get_token_fail_count Counter of failed Token() requests to the alternate token source
|
||||||
|
# TYPE get_token_fail_count counter
|
||||||
|
get_token_fail_count 0
|
||||||
|
`
|
||||||
|
|
||||||
|
const invalidData = "I don't think this is valid data"
|
||||||
|
|
||||||
|
const empty = ""
|
||||||
|
|
||||||
|
type mockHTTPClient struct {
|
||||||
|
responseBody string
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock implementation of MakeRequest. Usually returns an http.Response with
|
||||||
|
// hard-coded responseBody and statusCode. However, if the request uses a
|
||||||
|
// nonstandard method, it uses status code 405 (method not allowed)
|
||||||
|
func (c mockHTTPClient) MakeRequest(req *http.Request, timeout float64) (*http.Response, error) {
|
||||||
|
resp := http.Response{}
|
||||||
|
resp.StatusCode = c.statusCode
|
||||||
|
|
||||||
|
// basic error checking on request method
|
||||||
|
allowedMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"}
|
||||||
|
methodValid := false
|
||||||
|
for _, method := range allowedMethods {
|
||||||
|
if req.Method == method {
|
||||||
|
methodValid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !methodValid {
|
||||||
|
resp.StatusCode = 405 // Method not allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Body = ioutil.NopCloser(strings.NewReader(c.responseBody))
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a pointer to an Kubernetes object that uses a mock HTTP client.
|
||||||
|
// Parameters:
|
||||||
|
// response : Body of the response that the mock HTTP client should return
|
||||||
|
// statusCode: HTTP status code the mock HTTP client should return
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// *Kubernetes: Pointer to an Kubernetes object that uses the generated mock HTTP client
|
||||||
|
func genMockKubernetes(response string, statusCode int) Kubernetes {
|
||||||
|
return Kubernetes{
|
||||||
|
client: mockHTTPClient{responseBody: response, statusCode: statusCode},
|
||||||
|
Apiserver: []Apiserver{
|
||||||
|
Apiserver{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:8080",
|
||||||
|
Endpoint: "/metrics",
|
||||||
|
Timeout: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scheduler: []Scheduler{
|
||||||
|
Scheduler{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:10251",
|
||||||
|
Excludes: []string{"http_request_duration_.*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Controllermanager: []Controllermanager{
|
||||||
|
Controllermanager{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:10252",
|
||||||
|
Endpoint: "metrics",
|
||||||
|
Includes: []string{"http_request_duration_microseconds"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Kubelet: []Kubelet{
|
||||||
|
Kubelet{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:4194",
|
||||||
|
Endpoint: "metrics",
|
||||||
|
Includes: []string{"http_request_duration_microseconds"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a pointer to an Kubernetes object that uses a mock HTTP client.
|
||||||
|
// Parameters:
|
||||||
|
// response : Body of the response that the mock HTTP client should return
|
||||||
|
// statusCode: HTTP status code the mock HTTP client should return
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// *Kubernetes: Pointer to an Kubernetes object that uses the generated mock HTTP client
|
||||||
|
func genMockKubernetes2(response string, statusCode int) Kubernetes {
|
||||||
|
return Kubernetes{
|
||||||
|
client: mockHTTPClient{responseBody: response, statusCode: statusCode},
|
||||||
|
Apiserver: []Apiserver{
|
||||||
|
Apiserver{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:8080",
|
||||||
|
Endpoint: "/metrics",
|
||||||
|
Timeout: 1.0,
|
||||||
|
Includes: []string{"http_request_duration_microseconds"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the proper values are ignored or collected
|
||||||
|
func TestOK(t *testing.T) {
|
||||||
|
kubernetes := genMockKubernetes(validData, 200)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := kubernetes.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 33, acc.NFields())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that the proper values are ignored or collected
|
||||||
|
func TestOK2(t *testing.T) {
|
||||||
|
kubernetes := genMockKubernetes2(validData, 200)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := kubernetes.Gather(&acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 5, acc.NFields())
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"kubeservice": "apiserver",
|
||||||
|
"serverURL": "http://127.0.0.1:8080/metrics",
|
||||||
|
"handler": "prometheus",
|
||||||
|
}
|
||||||
|
mname := "http_request_duration_microseconds"
|
||||||
|
expectedFields := map[string]interface{}{
|
||||||
|
"0.5": 552048.506,
|
||||||
|
"0.9": 5.876804288e+06,
|
||||||
|
"0.99": 5.876804288e+06,
|
||||||
|
"count": 0.0,
|
||||||
|
"sum": 1.8909097205e+07}
|
||||||
|
acc.AssertContainsTaggedFields(t, mname, expectedFields, tags)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test response to HTTP 500
|
||||||
|
func TestKubernetes500(t *testing.T) {
|
||||||
|
kubernetes := genMockKubernetes(validData, 500)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := kubernetes.Gather(&acc)
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, 0, acc.NFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test response to malformed Data
|
||||||
|
func TestKubernetesBadData(t *testing.T) {
|
||||||
|
kubernetes := genMockKubernetes(invalidData, 200)
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := kubernetes.Gather(&acc)
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Equal(t, 0, acc.NFields())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test response to empty string as response objectgT
|
||||||
|
func TestKubernetesEmptyResponse(t *testing.T) {
|
||||||
|
kubernetes := Kubernetes{client: RealHTTPClient{client: &http.Client{}}}
|
||||||
|
kubernetes.Apiserver = []Apiserver{
|
||||||
|
Apiserver{
|
||||||
|
KubeService{
|
||||||
|
Url: "http://127.0.0.1:59999",
|
||||||
|
Endpoint: "/metrics",
|
||||||
|
Timeout: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := kubernetes.Gather(&acc)
|
||||||
|
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Equal(t, 0, acc.NFields())
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
// Parser inspired from
|
||||||
|
// https://github.com/prometheus/prom2json/blob/master/main.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrometheusParser is an object for Parsing incoming metrics.
|
||||||
|
type PrometheusParser struct {
|
||||||
|
// DefaultTags will be added to every parsed metric
|
||||||
|
DefaultTags map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a slice of Metrics from a text representation of a
|
||||||
|
// metrics
|
||||||
|
func (p *PrometheusParser) Parse(buf []byte) ([]telegraf.Metric, error) {
|
||||||
|
var metrics []telegraf.Metric
|
||||||
|
var parser expfmt.TextParser
|
||||||
|
// parse even if the buffer begins with a newline
|
||||||
|
buf = bytes.TrimPrefix(buf, []byte("\n"))
|
||||||
|
// Read raw data
|
||||||
|
buffer := bytes.NewBuffer(buf)
|
||||||
|
reader := bufio.NewReader(buffer)
|
||||||
|
metricFamilies, err := parser.TextToMetricFamilies(reader)
|
||||||
|
// read metrics
|
||||||
|
for metricName, mf := range metricFamilies {
|
||||||
|
for _, m := range mf.Metric {
|
||||||
|
// reading tags
|
||||||
|
tags := makeLabels(m)
|
||||||
|
for key, value := range p.DefaultTags {
|
||||||
|
tags[key] = value
|
||||||
|
}
|
||||||
|
// reading fields
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
if mf.GetType() == dto.MetricType_SUMMARY {
|
||||||
|
// summary metric
|
||||||
|
fields = makeQuantiles(m)
|
||||||
|
fields["count"] = float64(m.GetHistogram().GetSampleCount())
|
||||||
|
fields["sum"] = float64(m.GetSummary().GetSampleSum())
|
||||||
|
} else if mf.GetType() == dto.MetricType_HISTOGRAM {
|
||||||
|
// historgram metric
|
||||||
|
fields = makeBuckets(m)
|
||||||
|
fields["count"] = float64(m.GetHistogram().GetSampleCount())
|
||||||
|
fields["sum"] = float64(m.GetSummary().GetSampleSum())
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// standard metric
|
||||||
|
fields = getNameAndValue(m)
|
||||||
|
}
|
||||||
|
// converting to telegraf metric
|
||||||
|
if len(fields) > 0 {
|
||||||
|
metric, err := telegraf.NewMetric(metricName, tags, fields)
|
||||||
|
if err == nil {
|
||||||
|
metrics = append(metrics, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse one line
|
||||||
|
func (p *PrometheusParser) ParseLine(line string) (telegraf.Metric, error) {
|
||||||
|
metrics, err := p.Parse([]byte(line + "\n"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(metrics) < 1 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Can not parse the line: %s, for data format: prometheus", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default tags
|
||||||
|
func (p *PrometheusParser) SetDefaultTags(tags map[string]string) {
|
||||||
|
p.DefaultTags = tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Quantiles from summary metric
|
||||||
|
func makeQuantiles(m *dto.Metric) map[string]interface{} {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for _, q := range m.GetSummary().Quantile {
|
||||||
|
if !math.IsNaN(q.GetValue()) {
|
||||||
|
fields[fmt.Sprint(q.GetQuantile())] = float64(q.GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Buckets from histogram metric
|
||||||
|
func makeBuckets(m *dto.Metric) map[string]interface{} {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
for _, b := range m.GetHistogram().Bucket {
|
||||||
|
fields[fmt.Sprint(b.GetUpperBound())] = float64(b.GetCumulativeCount())
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get labels from metric
|
||||||
|
func makeLabels(m *dto.Metric) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
for _, lp := range m.Label {
|
||||||
|
result[lp.GetName()] = lp.GetValue()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name and value from metric
|
||||||
|
func getNameAndValue(m *dto.Metric) map[string]interface{} {
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
if m.Gauge != nil {
|
||||||
|
if !math.IsNaN(m.GetGauge().GetValue()) {
|
||||||
|
fields["gauge"] = float64(m.GetGauge().GetValue())
|
||||||
|
}
|
||||||
|
} else if m.Counter != nil {
|
||||||
|
if !math.IsNaN(m.GetGauge().GetValue()) {
|
||||||
|
fields["counter"] = float64(m.GetCounter().GetValue())
|
||||||
|
}
|
||||||
|
} else if m.Untyped != nil {
|
||||||
|
if !math.IsNaN(m.GetGauge().GetValue()) {
|
||||||
|
fields["value"] = float64(m.GetUntyped().GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
|
@ -0,0 +1,315 @@
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exptime = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validPrometheus = "cpu_load_short,cpu=cpu0 value=10 1257894000000000000"
|
||||||
|
validPrometheusNewline = "\ncpu_load_short,cpu=cpu0 value=10 1257894000000000000\n"
|
||||||
|
invalidPrometheus = "I don't think this is line protocol"
|
||||||
|
invalidPrometheus2 = "{\"a\": 5, \"b\": {\"c\": 6}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
const validUniqueGauge = `# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
|
||||||
|
# TYPE cadvisor_version_info gauge
|
||||||
|
cadvisor_version_info{cadvisorRevision="",cadvisorVersion="",dockerVersion="1.8.2",kernelVersion="3.10.0-229.20.1.el7.x86_64",osVersion="CentOS Linux 7 (Core)"} 1
|
||||||
|
`
|
||||||
|
|
||||||
|
const validUniqueCounter = `# HELP get_token_fail_count Counter of failed Token() requests to the alternate token source
|
||||||
|
# TYPE get_token_fail_count counter
|
||||||
|
get_token_fail_count 0
|
||||||
|
`
|
||||||
|
|
||||||
|
const validUniqueLine = `# HELP get_token_fail_count Counter of failed Token() requests to the alternate token source
|
||||||
|
`
|
||||||
|
|
||||||
|
const validUniqueSummary = `# HELP http_request_duration_microseconds The HTTP request latencies in microseconds.
|
||||||
|
# TYPE http_request_duration_microseconds summary
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 552048.506
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds_sum{handler="prometheus"} 1.8909097205e+07
|
||||||
|
http_request_duration_microseconds_count{handler="prometheus"} 9
|
||||||
|
`
|
||||||
|
|
||||||
|
const validUniqueHistogram = `# HELP apiserver_request_latencies Response latency distribution in microseconds for each verb, resource and client.
|
||||||
|
# TYPE apiserver_request_latencies histogram
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="125000"} 1994
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="250000"} 1997
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="500000"} 2000
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="1e+06"} 2005
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="2e+06"} 2012
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="4e+06"} 2017
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="8e+06"} 2024
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="+Inf"} 2025
|
||||||
|
apiserver_request_latencies_sum{resource="bindings",verb="POST"} 1.02726334e+08
|
||||||
|
apiserver_request_latencies_count{resource="bindings",verb="POST"} 2025
|
||||||
|
`
|
||||||
|
|
||||||
|
const validData = `# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
|
||||||
|
# TYPE cadvisor_version_info gauge
|
||||||
|
cadvisor_version_info{cadvisorRevision="",cadvisorVersion="",dockerVersion="1.8.2",kernelVersion="3.10.0-229.20.1.el7.x86_64",osVersion="CentOS Linux 7 (Core)"} 1
|
||||||
|
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
|
||||||
|
# TYPE go_gc_duration_seconds summary
|
||||||
|
go_gc_duration_seconds{quantile="0"} 0.013534896000000001
|
||||||
|
go_gc_duration_seconds{quantile="0.25"} 0.02469263
|
||||||
|
go_gc_duration_seconds{quantile="0.5"} 0.033727822000000005
|
||||||
|
go_gc_duration_seconds{quantile="0.75"} 0.03840335
|
||||||
|
go_gc_duration_seconds{quantile="1"} 0.049956604
|
||||||
|
go_gc_duration_seconds_sum 1970.341293002
|
||||||
|
go_gc_duration_seconds_count 65952
|
||||||
|
# HELP http_request_duration_microseconds The HTTP request latencies in microseconds.
|
||||||
|
# TYPE http_request_duration_microseconds summary
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.5"} 552048.506
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.9"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds{handler="prometheus",quantile="0.99"} 5.876804288e+06
|
||||||
|
http_request_duration_microseconds_sum{handler="prometheus"} 1.8909097205e+07
|
||||||
|
http_request_duration_microseconds_count{handler="prometheus"} 9
|
||||||
|
# HELP get_token_fail_count Counter of failed Token() requests to the alternate token source
|
||||||
|
# TYPE get_token_fail_count counter
|
||||||
|
get_token_fail_count 0
|
||||||
|
# HELP apiserver_request_latencies Response latency distribution in microseconds for each verb, resource and client.
|
||||||
|
# TYPE apiserver_request_latencies histogram
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="125000"} 1994
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="250000"} 1997
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="500000"} 2000
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="1e+06"} 2005
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="2e+06"} 2012
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="4e+06"} 2017
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="8e+06"} 2024
|
||||||
|
apiserver_request_latencies_bucket{resource="bindings",verb="POST",le="+Inf"} 2025
|
||||||
|
apiserver_request_latencies_sum{resource="bindings",verb="POST"} 1.02726334e+08
|
||||||
|
apiserver_request_latencies_count{resource="bindings",verb="POST"} 2025
|
||||||
|
`
|
||||||
|
|
||||||
|
const prometheusMulti = `
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
`
|
||||||
|
|
||||||
|
const prometheusMultiSomeInvalid = `
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,cpu=cpu3, host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
cpu,cpu=cpu4 , usage_idle=99,usage_busy=1
|
||||||
|
cpu,host=foo,datacenter=us-east usage_idle=99,usage_busy=1
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestParseValidPrometheus(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
|
||||||
|
// Gauge value
|
||||||
|
metrics, err := parser.Parse([]byte(validUniqueGauge))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, metrics, 1)
|
||||||
|
assert.Equal(t, "cadvisor_version_info", metrics[0].Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"gauge": float64(1),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"osVersion": "CentOS Linux 7 (Core)",
|
||||||
|
"dockerVersion": "1.8.2",
|
||||||
|
"kernelVersion": "3.10.0-229.20.1.el7.x86_64",
|
||||||
|
}, metrics[0].Tags())
|
||||||
|
|
||||||
|
// Counter value
|
||||||
|
parser.SetDefaultTags(map[string]string{"mytag": "mytagvalue"})
|
||||||
|
metrics, err = parser.Parse([]byte(validUniqueCounter))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, metrics, 1)
|
||||||
|
assert.Equal(t, "get_token_fail_count", metrics[0].Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"counter": float64(0),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
assert.Equal(t, map[string]string{"mytag": "mytagvalue"}, metrics[0].Tags())
|
||||||
|
|
||||||
|
// Summary data
|
||||||
|
parser.SetDefaultTags(map[string]string{})
|
||||||
|
metrics, err = parser.Parse([]byte(validUniqueSummary))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, metrics, 1)
|
||||||
|
assert.Equal(t, "http_request_duration_microseconds", metrics[0].Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"0.5": 552048.506,
|
||||||
|
"0.9": 5.876804288e+06,
|
||||||
|
"0.99": 5.876804288e+06,
|
||||||
|
"count": 0.0,
|
||||||
|
"sum": 1.8909097205e+07,
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
assert.Equal(t, map[string]string{"handler": "prometheus"}, metrics[0].Tags())
|
||||||
|
|
||||||
|
// histogram data
|
||||||
|
metrics, err = parser.Parse([]byte(validUniqueHistogram))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, metrics, 1)
|
||||||
|
assert.Equal(t, "apiserver_request_latencies", metrics[0].Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"500000": 2000.0,
|
||||||
|
"count": 2025.0,
|
||||||
|
"sum": 0.0,
|
||||||
|
"250000": 1997.0,
|
||||||
|
"2e+06": 2012.0,
|
||||||
|
"4e+06": 2017.0,
|
||||||
|
"8e+06": 2024.0,
|
||||||
|
"+Inf": 2025.0,
|
||||||
|
"125000": 1994.0,
|
||||||
|
"1e+06": 2005.0,
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
assert.Equal(t,
|
||||||
|
map[string]string{"verb": "POST", "resource": "bindings"},
|
||||||
|
metrics[0].Tags())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLineInvalidPrometheus(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
metric, err := parser.ParseLine(validUniqueLine)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, metric)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
func TestParseLineValidPrometheus(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
|
||||||
|
metric, err := parser.ParseLine(validPrometheus)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "cpu_load_short", metric.Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"value": float64(10),
|
||||||
|
}, metric.Fields())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"cpu": "cpu0",
|
||||||
|
}, metric.Tags())
|
||||||
|
assert.Equal(t, exptime, metric.Time())
|
||||||
|
|
||||||
|
metric, err = parser.ParseLine(validPrometheusNewline)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "cpu_load_short", metric.Name())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"value": float64(10),
|
||||||
|
}, metric.Fields())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"cpu": "cpu0",
|
||||||
|
}, metric.Tags())
|
||||||
|
assert.Equal(t, exptime, metric.Time())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultipleValid(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
|
||||||
|
metrics, err := parser.Parse([]byte(prometheusMulti))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, metrics, 7)
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
assert.Equal(t, "cpu", metric.Name())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"datacenter": "us-east",
|
||||||
|
"host": "foo",
|
||||||
|
}, metrics[0].Tags())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSomeValid(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
|
||||||
|
metrics, err := parser.Parse([]byte(prometheusMultiSomeInvalid))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, metrics, 4)
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
assert.Equal(t, "cpu", metric.Name())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"datacenter": "us-east",
|
||||||
|
"host": "foo",
|
||||||
|
}, metrics[0].Tags())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that default tags are applied.
|
||||||
|
func TestParseDefaultTags(t *testing.T) {
|
||||||
|
parser := PrometheusParser{
|
||||||
|
DefaultTags: map[string]string{
|
||||||
|
"tag": "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := parser.Parse([]byte(prometheusMultiSomeInvalid))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, metrics, 4)
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
assert.Equal(t, "cpu", metric.Name())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"datacenter": "us-east",
|
||||||
|
"host": "foo",
|
||||||
|
"tag": "default",
|
||||||
|
}, metrics[0].Tags())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that metric tags will override default tags
|
||||||
|
func TestParseDefaultTagsOverride(t *testing.T) {
|
||||||
|
parser := PrometheusParser{
|
||||||
|
DefaultTags: map[string]string{
|
||||||
|
"host": "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := parser.Parse([]byte(prometheusMultiSomeInvalid))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Len(t, metrics, 4)
|
||||||
|
|
||||||
|
for _, metric := range metrics {
|
||||||
|
assert.Equal(t, "cpu", metric.Name())
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"datacenter": "us-east",
|
||||||
|
"host": "foo",
|
||||||
|
}, metrics[0].Tags())
|
||||||
|
assert.Equal(t, map[string]interface{}{
|
||||||
|
"usage_idle": float64(99),
|
||||||
|
"usage_busy": float64(1),
|
||||||
|
}, metrics[0].Fields())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInvalidPrometheus(t *testing.T) {
|
||||||
|
parser := PrometheusParser{}
|
||||||
|
|
||||||
|
_, err := parser.Parse([]byte(invalidPrometheus))
|
||||||
|
assert.Error(t, err)
|
||||||
|
_, err = parser.Parse([]byte(invalidPrometheus2))
|
||||||
|
assert.Error(t, err)
|
||||||
|
_, err = parser.ParseLine(invalidPrometheus)
|
||||||
|
assert.Error(t, err)
|
||||||
|
_, err = parser.ParseLine(invalidPrometheus2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
*/
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/influxdata/telegraf/plugins/parsers/graphite"
|
"github.com/influxdata/telegraf/plugins/parsers/graphite"
|
||||||
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
||||||
"github.com/influxdata/telegraf/plugins/parsers/json"
|
"github.com/influxdata/telegraf/plugins/parsers/json"
|
||||||
|
"github.com/influxdata/telegraf/plugins/parsers/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParserInput is an interface for input plugins that are able to parse
|
// ParserInput is an interface for input plugins that are able to parse
|
||||||
|
@ -38,7 +39,7 @@ type Parser interface {
|
||||||
// Config is a struct that covers the data types needed for all parser types,
|
// Config is a struct that covers the data types needed for all parser types,
|
||||||
// and can be used to instantiate _any_ of the parsers.
|
// and can be used to instantiate _any_ of the parsers.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Dataformat can be one of: json, influx, graphite
|
// Dataformat can be one of: json, influx, graphite, prometheus
|
||||||
DataFormat string
|
DataFormat string
|
||||||
|
|
||||||
// Separator only applied to Graphite data.
|
// Separator only applied to Graphite data.
|
||||||
|
@ -65,6 +66,8 @@ func NewParser(config *Config) (Parser, error) {
|
||||||
config.TagKeys, config.DefaultTags)
|
config.TagKeys, config.DefaultTags)
|
||||||
case "influx":
|
case "influx":
|
||||||
parser, err = NewInfluxParser()
|
parser, err = NewInfluxParser()
|
||||||
|
case "prometheus":
|
||||||
|
parser, err = NewPrometheusParser()
|
||||||
case "graphite":
|
case "graphite":
|
||||||
parser, err = NewGraphiteParser(config.Separator,
|
parser, err = NewGraphiteParser(config.Separator,
|
||||||
config.Templates, config.DefaultTags)
|
config.Templates, config.DefaultTags)
|
||||||
|
@ -87,6 +90,10 @@ func NewJSONParser(
|
||||||
return parser, nil
|
return parser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPrometheusParser() (Parser, error) {
|
||||||
|
return &prometheus.PrometheusParser{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewInfluxParser() (Parser, error) {
|
func NewInfluxParser() (Parser, error) {
|
||||||
return &influx.InfluxParser{}, nil
|
return &influx.InfluxParser{}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue