Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
99
plugins/outputs/prometheus_client/README.md
Normal file
99
plugins/outputs/prometheus_client/README.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Prometheus Output Plugin
|
||||
|
||||
This plugin starts a [Prometheus][prometheus] client and exposes the written
|
||||
metrics on a `/metrics` endpoint by default. This endpoint can then be polled
|
||||
by a Prometheus server.
|
||||
|
||||
⭐ Telegraf v0.2.1
|
||||
🏷️ applications
|
||||
💻 all
|
||||
|
||||
[prometheus]: https://prometheus.io
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Secret-store support
|
||||
|
||||
This plugin supports secrets from secret-stores for the `basic_password` option.
|
||||
See the [secret-store documentation][SECRETSTORE] for more details on how
|
||||
to use them.
|
||||
|
||||
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# Configuration for the Prometheus client to spawn
|
||||
[[outputs.prometheus_client]]
|
||||
## Address to listen on.
|
||||
## ex:
|
||||
## listen = ":9273"
|
||||
## listen = "vsock://:9273"
|
||||
listen = ":9273"
|
||||
|
||||
## Maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## Maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
|
||||
## Metric version controls the mapping from Prometheus metrics into Telegraf metrics.
|
||||
## See "Metric Format Configuration" in plugins/inputs/prometheus/README.md for details.
|
||||
## Valid options: 1, 2
|
||||
# metric_version = 1
|
||||
|
||||
## Use HTTP Basic Authentication.
|
||||
# basic_username = "Foo"
|
||||
# basic_password = "Bar"
|
||||
|
||||
## If set, the IP Ranges which are allowed to access metrics.
|
||||
## ex: ip_range = ["192.168.0.0/24", "192.168.1.0/30"]
|
||||
# ip_range = []
|
||||
|
||||
## Path to publish the metrics on.
|
||||
# path = "/metrics"
|
||||
|
||||
## Expiration interval for each metric. 0 == no expiration
|
||||
# expiration_interval = "60s"
|
||||
|
||||
## Collectors to enable, valid entries are "gocollector" and "process".
|
||||
## If unset, both are enabled.
|
||||
# collectors_exclude = ["gocollector", "process"]
|
||||
|
||||
## Send string metrics as Prometheus labels.
|
||||
## Unless set to false all string metrics will be sent as labels.
|
||||
# string_as_label = true
|
||||
|
||||
## If set, enable TLS with the given certificate.
|
||||
# tls_cert = "/etc/ssl/telegraf.crt"
|
||||
# tls_key = "/etc/ssl/telegraf.key"
|
||||
|
||||
## Set one or more allowed client CA certificate file names to
|
||||
## enable mutually authenticated TLS connections
|
||||
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
|
||||
|
||||
## Export metric collection time.
|
||||
# export_timestamp = false
|
||||
|
||||
## Set custom headers for HTTP responses.
|
||||
# http_headers = {"X-Special-Header" = "Special-Value"}
|
||||
|
||||
## Specify the metric type explicitly.
|
||||
## This overrides the metric-type of the Telegraf metric. Globbing is allowed.
|
||||
# [outputs.prometheus_client.metric_types]
|
||||
# counter = []
|
||||
# gauge = []
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
Prometheus metrics are produced in the same manner as the [prometheus
|
||||
serializer][].
|
||||
|
||||
[prometheus serializer]: /plugins/serializers/prometheus/README.md#Metrics
|
312
plugins/outputs/prometheus_client/prometheus_client.go
Normal file
312
plugins/outputs/prometheus_client/prometheus_client.go
Normal file
|
@ -0,0 +1,312 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package prometheus_client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mdlayher/vsock"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
"github.com/influxdata/telegraf/plugins/outputs/prometheus_client/v1"
|
||||
"github.com/influxdata/telegraf/plugins/outputs/prometheus_client/v2"
|
||||
serializers_prometheus "github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
defaultListen = ":9273"
|
||||
defaultPath = "/metrics"
|
||||
defaultExpirationInterval = config.Duration(60 * time.Second)
|
||||
defaultReadTimeout = 10 * time.Second
|
||||
defaultWriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type Collector interface {
|
||||
Describe(ch chan<- *prometheus.Desc)
|
||||
Collect(ch chan<- prometheus.Metric)
|
||||
Add(metrics []telegraf.Metric) error
|
||||
}
|
||||
|
||||
type PrometheusClient struct {
|
||||
Listen string `toml:"listen"`
|
||||
ReadTimeout config.Duration `toml:"read_timeout"`
|
||||
WriteTimeout config.Duration `toml:"write_timeout"`
|
||||
MetricVersion int `toml:"metric_version"`
|
||||
BasicUsername string `toml:"basic_username"`
|
||||
BasicPassword config.Secret `toml:"basic_password"`
|
||||
IPRange []string `toml:"ip_range"`
|
||||
ExpirationInterval config.Duration `toml:"expiration_interval"`
|
||||
Path string `toml:"path"`
|
||||
CollectorsExclude []string `toml:"collectors_exclude"`
|
||||
StringAsLabel bool `toml:"string_as_label"`
|
||||
ExportTimestamp bool `toml:"export_timestamp"`
|
||||
TypeMappings serializers_prometheus.MetricTypes `toml:"metric_types"`
|
||||
HTTPHeaders map[string]*config.Secret `toml:"http_headers"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
common_tls.ServerConfig
|
||||
|
||||
server *http.Server
|
||||
url *url.URL
|
||||
collector Collector
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (*PrometheusClient) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) Init() error {
|
||||
defaultCollectors := map[string]bool{
|
||||
"gocollector": true,
|
||||
"process": true,
|
||||
}
|
||||
for _, collector := range p.CollectorsExclude {
|
||||
delete(defaultCollectors, collector)
|
||||
}
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
for collector := range defaultCollectors {
|
||||
switch collector {
|
||||
case "gocollector":
|
||||
err := registry.Register(collectors.NewGoCollector())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "process":
|
||||
err := registry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized collector %s", collector)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.TypeMappings.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch p.MetricVersion {
|
||||
default:
|
||||
fallthrough
|
||||
case 1:
|
||||
p.collector = v1.NewCollector(
|
||||
time.Duration(p.ExpirationInterval),
|
||||
p.StringAsLabel,
|
||||
p.ExportTimestamp,
|
||||
p.TypeMappings,
|
||||
p.Log,
|
||||
)
|
||||
err := registry.Register(p.collector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 2:
|
||||
p.collector = v2.NewCollector(
|
||||
time.Duration(p.ExpirationInterval),
|
||||
p.StringAsLabel,
|
||||
p.ExportTimestamp,
|
||||
p.TypeMappings,
|
||||
)
|
||||
err := registry.Register(p.collector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ipRange := make([]*net.IPNet, 0, len(p.IPRange))
|
||||
for _, cidr := range p.IPRange {
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing ip_range: %w", err)
|
||||
}
|
||||
|
||||
ipRange = append(ipRange, ipNet)
|
||||
}
|
||||
|
||||
psecret, err := p.BasicPassword.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password := psecret.String()
|
||||
psecret.Destroy()
|
||||
|
||||
authHandler := internal.BasicAuthHandler(p.BasicUsername, password, "prometheus", onAuthError)
|
||||
rangeHandler := internal.IPRangeHandler(ipRange, onError)
|
||||
promHandler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError})
|
||||
landingPageHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, err := w.Write([]byte("Telegraf Output Plugin: Prometheus Client "))
|
||||
if err != nil {
|
||||
p.Log.Errorf("Error occurred when writing HTTP reply: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
if p.Path == "" {
|
||||
p.Path = "/metrics"
|
||||
}
|
||||
mux.Handle(p.Path, p.headerHandler(authHandler(rangeHandler(promHandler))))
|
||||
mux.Handle("/", p.headerHandler(authHandler(rangeHandler(landingPageHandler))))
|
||||
|
||||
tlsConfig, err := p.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.ReadTimeout < config.Duration(time.Second) {
|
||||
p.ReadTimeout = config.Duration(defaultReadTimeout)
|
||||
}
|
||||
if p.WriteTimeout < config.Duration(time.Second) {
|
||||
p.WriteTimeout = config.Duration(defaultWriteTimeout)
|
||||
}
|
||||
|
||||
p.server = &http.Server{
|
||||
Addr: p.Listen,
|
||||
Handler: mux,
|
||||
TLSConfig: tlsConfig,
|
||||
ReadTimeout: time.Duration(p.ReadTimeout),
|
||||
WriteTimeout: time.Duration(p.WriteTimeout),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) listenTCP(host string) (net.Listener, error) {
|
||||
if p.server.TLSConfig != nil {
|
||||
return tls.Listen("tcp", host, p.server.TLSConfig)
|
||||
}
|
||||
return net.Listen("tcp", host)
|
||||
}
|
||||
|
||||
func listenVsock(host string) (net.Listener, error) {
|
||||
_, portStr, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseUint(portStr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vsock.Listen(uint32(port), nil)
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) listen() (net.Listener, error) {
|
||||
u, err := url.ParseRequestURI(p.Listen)
|
||||
// fallback to legacy way
|
||||
if err != nil {
|
||||
return p.listenTCP(p.Listen)
|
||||
}
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "", "tcp", "http":
|
||||
return p.listenTCP(u.Host)
|
||||
case "vsock":
|
||||
return listenVsock(u.Host)
|
||||
default:
|
||||
return p.listenTCP(u.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) Connect() error {
|
||||
listener, err := p.listen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if p.server.TLSConfig != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
p.url = &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: listener.Addr().String(),
|
||||
Path: p.Path,
|
||||
}
|
||||
|
||||
p.Log.Infof("Listening on %s", p.URL())
|
||||
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
err := p.server.Serve(listener)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
p.Log.Errorf("Server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) headerHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for key, secret := range p.HTTPHeaders {
|
||||
value, err := secret.Get()
|
||||
if err == nil {
|
||||
w.Header().Set(key, value.String())
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func onAuthError(_ http.ResponseWriter) {
|
||||
}
|
||||
|
||||
func onError(rw http.ResponseWriter, code int) {
|
||||
http.Error(rw, http.StatusText(code), code)
|
||||
}
|
||||
|
||||
// URL returns the address the plugin is listening on. If not listening
|
||||
// an empty string is returned.
|
||||
func (p *PrometheusClient) URL() string {
|
||||
if p.url != nil {
|
||||
return p.url.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) Close() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := p.server.Shutdown(ctx)
|
||||
p.wg.Wait()
|
||||
p.url = nil
|
||||
prometheus.Unregister(p.collector)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PrometheusClient) Write(metrics []telegraf.Metric) error {
|
||||
return p.collector.Add(metrics)
|
||||
}
|
||||
|
||||
func init() {
|
||||
outputs.Add("prometheus_client", func() telegraf.Output {
|
||||
return &PrometheusClient{
|
||||
Listen: defaultListen,
|
||||
Path: defaultPath,
|
||||
ExpirationInterval: defaultExpirationInterval,
|
||||
StringAsLabel: true,
|
||||
}
|
||||
})
|
||||
}
|
123
plugins/outputs/prometheus_client/prometheus_client_test.go
Normal file
123
plugins/outputs/prometheus_client/prometheus_client_test.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package prometheus_client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestLandingPage(t *testing.T) {
|
||||
output := PrometheusClient{
|
||||
Listen: ":0",
|
||||
CollectorsExclude: []string{"process"},
|
||||
MetricVersion: 1,
|
||||
Log: &testutil.Logger{Name: "outputs.prometheus_client"},
|
||||
}
|
||||
expected := "Telegraf Output Plugin: Prometheus Client"
|
||||
|
||||
require.NoError(t, output.Init())
|
||||
require.NoError(t, output.Connect())
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("http://%s/", output.url.Host))
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.Get(u.String())
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
actual, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, strings.TrimSpace(string(actual)))
|
||||
}
|
||||
|
||||
func TestFormatHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
accept string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "invalid accept ",
|
||||
accept: "applications/json",
|
||||
expected: "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "no accept header",
|
||||
expected: "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "text no version",
|
||||
accept: "text/plain",
|
||||
expected: "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "text with version 0.0.4",
|
||||
accept: "text/plain; version=0.0.4",
|
||||
expected: "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "protobuf text format",
|
||||
accept: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text",
|
||||
expected: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "protobuf compact text format",
|
||||
accept: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text",
|
||||
expected: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "protobuf delimited format",
|
||||
accept: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited",
|
||||
expected: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "multiple accept preferring protobuf",
|
||||
accept: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited, text/plain",
|
||||
expected: "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores",
|
||||
},
|
||||
{
|
||||
name: "multiple accept preferring text",
|
||||
accept: "text/plain, application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited",
|
||||
expected: "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
|
||||
},
|
||||
}
|
||||
|
||||
// Setup the plugin
|
||||
plugin := PrometheusClient{
|
||||
Listen: ":0",
|
||||
Log: testutil.Logger{Name: "outputs.prometheus_client"},
|
||||
}
|
||||
require.NoError(t, plugin.Init())
|
||||
require.NoError(t, plugin.Connect())
|
||||
|
||||
// Get the plugin's address so we can send data to it
|
||||
addr := fmt.Sprintf("http://%s/metrics", plugin.url.Host)
|
||||
|
||||
// Run the actual tests
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Construct a request with the given "Accept" header
|
||||
req, err := http.NewRequest("GET", addr, nil)
|
||||
require.NoError(t, err)
|
||||
if tt.accept != "" {
|
||||
req.Header.Add("Accept", tt.accept)
|
||||
}
|
||||
|
||||
// Get the metrics
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Test the result
|
||||
require.NotEmpty(t, resp.Body)
|
||||
require.Equal(t, tt.expected, resp.Header.Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
}
|
497
plugins/outputs/prometheus_client/prometheus_client_v1_test.go
Normal file
497
plugins/outputs/prometheus_client/prometheus_client_v1_test.go
Normal file
|
@ -0,0 +1,497 @@
|
|||
package prometheus_client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
inputs "github.com/influxdata/telegraf/plugins/inputs/prometheus"
|
||||
"github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestMetricVersion1(t *testing.T) {
|
||||
logger := testutil.Logger{Name: "outputs.prometheus_client"}
|
||||
tests := []struct {
|
||||
name string
|
||||
output *PrometheusClient
|
||||
metrics []telegraf.Metric
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"time_idle": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus untyped",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus counter",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"counter": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Counter,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "when export timestamp is true timestamp is present in the metric",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
ExportTimestamp: true,
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"counter": 42.0,
|
||||
},
|
||||
time.Unix(1257894000, 0),
|
||||
telegraf.Counter,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42 1257894000000
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "replace characters when using string as label",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
StringAsLabel: true,
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"host:name": "example.org",
|
||||
"counter": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Counter,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host_name="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus gauge",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"gauge": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Gauge,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus histogram",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"http_request_duration_seconds",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"sum": 53423,
|
||||
"0.05": 24054,
|
||||
"0.1": 33444,
|
||||
"0.2": 100392,
|
||||
"0.5": 129389,
|
||||
"1": 133988,
|
||||
"+Inf": 144320,
|
||||
"count": 144320,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP http_request_duration_seconds Telegraf collected metric
|
||||
# TYPE http_request_duration_seconds histogram
|
||||
http_request_duration_seconds_bucket{le="0.05"} 24054
|
||||
http_request_duration_seconds_bucket{le="0.1"} 33444
|
||||
http_request_duration_seconds_bucket{le="0.2"} 100392
|
||||
http_request_duration_seconds_bucket{le="0.5"} 129389
|
||||
http_request_duration_seconds_bucket{le="1"} 133988
|
||||
http_request_duration_seconds_bucket{le="+Inf"} 144320
|
||||
http_request_duration_seconds_sum 53423
|
||||
http_request_duration_seconds_count 144320
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus summary",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"rpc_duration_seconds",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"0.01": 3102,
|
||||
"0.05": 3272,
|
||||
"0.5": 4773,
|
||||
"0.9": 9001,
|
||||
"0.99": 76656,
|
||||
"count": 2693,
|
||||
"sum": 17560473,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Summary,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP rpc_duration_seconds Telegraf collected metric
|
||||
# TYPE rpc_duration_seconds summary
|
||||
rpc_duration_seconds{quantile="0.01"} 3102
|
||||
rpc_duration_seconds{quantile="0.05"} 3272
|
||||
rpc_duration_seconds{quantile="0.5"} 4773
|
||||
rpc_duration_seconds{quantile="0.9"} 9001
|
||||
rpc_duration_seconds{quantile="0.99"} 76656
|
||||
rpc_duration_seconds_sum 1.7560473e+07
|
||||
rpc_duration_seconds_count 2693
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus untyped forced to counter",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
TypeMappings: prometheus.MetricTypes{Counter: []string{"cpu_time_idle"}},
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "prometheus untyped forced to gauge",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 1,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
TypeMappings: prometheus.MetricTypes{Gauge: []string{"cpu_time_idle"}},
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu_time_idle",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.NoError(t, tt.output.Init())
|
||||
|
||||
require.NoError(t, tt.output.Connect())
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, tt.output.Close())
|
||||
}()
|
||||
|
||||
require.NoError(t, tt.output.Write(tt.metrics))
|
||||
|
||||
resp, err := http.Get(tt.output.URL())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t,
|
||||
strings.TrimSpace(string(tt.expected)),
|
||||
strings.TrimSpace(string(body)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripMetricVersion1(t *testing.T) {
|
||||
logger := testutil.Logger{Name: "outputs.prometheus_client"}
|
||||
regxPattern := regexp.MustCompile(`.*prometheus_request_.*`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
name: "untyped",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "counter",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "gauge",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multi",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_guest Telegraf collected metric
|
||||
# TYPE cpu_time_guest gauge
|
||||
cpu_time_guest{host="one.example.org"} 42
|
||||
cpu_time_guest{host="two.example.org"} 42
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="one.example.org"} 42
|
||||
cpu_time_idle{host="two.example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "histogram",
|
||||
data: []byte(`
|
||||
# HELP http_request_duration_seconds Telegraf collected metric
|
||||
# TYPE http_request_duration_seconds histogram
|
||||
http_request_duration_seconds_bucket{le="0.05"} 24054
|
||||
http_request_duration_seconds_bucket{le="0.1"} 33444
|
||||
http_request_duration_seconds_bucket{le="0.2"} 100392
|
||||
http_request_duration_seconds_bucket{le="0.5"} 129389
|
||||
http_request_duration_seconds_bucket{le="1"} 133988
|
||||
http_request_duration_seconds_bucket{le="+Inf"} 144320
|
||||
http_request_duration_seconds_sum 53423
|
||||
http_request_duration_seconds_count 144320
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "summary",
|
||||
data: []byte(`
|
||||
# HELP rpc_duration_seconds Telegraf collected metric
|
||||
# TYPE rpc_duration_seconds summary
|
||||
rpc_duration_seconds{quantile="0.01"} 3102
|
||||
rpc_duration_seconds{quantile="0.05"} 3272
|
||||
rpc_duration_seconds{quantile="0.5"} 4773
|
||||
rpc_duration_seconds{quantile="0.9"} 9001
|
||||
rpc_duration_seconds{quantile="0.99"} 76656
|
||||
rpc_duration_seconds_sum 1.7560473e+07
|
||||
rpc_duration_seconds_count 2693
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
address := fmt.Sprintf("http://%s", ts.Listener.Addr())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err := w.Write(tt.data); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
input := &inputs.Prometheus{
|
||||
Log: logger,
|
||||
URLs: []string{address},
|
||||
URLTag: "",
|
||||
MetricVersion: 1,
|
||||
}
|
||||
err := input.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err = input.Start(&acc)
|
||||
require.NoError(t, err)
|
||||
err = input.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
input.Stop()
|
||||
|
||||
metrics := acc.GetTelegrafMetrics()
|
||||
|
||||
output := &PrometheusClient{
|
||||
Listen: "127.0.0.1:0",
|
||||
Path: defaultPath,
|
||||
MetricVersion: 1,
|
||||
Log: logger,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
}
|
||||
err = output.Init()
|
||||
require.NoError(t, err)
|
||||
err = output.Connect()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err = output.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = output.Write(metrics)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.Get(output.URL())
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
actual, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
current := regxPattern.ReplaceAllLiteralString(string(actual), "")
|
||||
require.Equal(t,
|
||||
strings.TrimSpace(string(tt.data)),
|
||||
strings.TrimSpace(current))
|
||||
})
|
||||
}
|
||||
}
|
528
plugins/outputs/prometheus_client/prometheus_client_v2_test.go
Normal file
528
plugins/outputs/prometheus_client/prometheus_client_v2_test.go
Normal file
|
@ -0,0 +1,528 @@
|
|||
package prometheus_client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
inputs "github.com/influxdata/telegraf/plugins/inputs/prometheus"
|
||||
"github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func TestMetricVersion2(t *testing.T) {
|
||||
logger := testutil.Logger{Name: "outputs.prometheus_client"}
|
||||
tests := []struct {
|
||||
name string
|
||||
output *PrometheusClient
|
||||
metrics []telegraf.Metric
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
name: "untyped telegraf metric",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"time_idle": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "summary no quantiles",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"prometheus",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"rpc_duration_seconds_sum": 1.7560473e+07,
|
||||
"rpc_duration_seconds_count": 2693,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Summary,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP rpc_duration_seconds Telegraf collected metric
|
||||
# TYPE rpc_duration_seconds summary
|
||||
rpc_duration_seconds_sum 1.7560473e+07
|
||||
rpc_duration_seconds_count 2693
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "when export timestamp is true timestamp is present in the metric",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
ExportTimestamp: true,
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"time_idle": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42 0
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "strings as labels",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
StringAsLabel: true,
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"time_idle": 42.0,
|
||||
"host": "example.org",
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "when strings as labels is false string fields are discarded",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
StringAsLabel: false,
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"time_idle": 42.0,
|
||||
"host": "example.org",
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "untype prometheus metric",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"prometheus",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cpu_time_idle": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "telegraf histogram",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_sum": 2000.0,
|
||||
"usage_idle_count": 20.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
"le": "0.0",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_bucket": 0.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
"le": "50.0",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_bucket": 7.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
"le": "100.0",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_bucket": 20.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
"le": "+Inf",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_bucket": 20.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_usage_idle Telegraf collected metric
|
||||
# TYPE cpu_usage_idle histogram
|
||||
cpu_usage_idle_bucket{cpu="cpu1",le="0"} 0
|
||||
cpu_usage_idle_bucket{cpu="cpu1",le="50"} 7
|
||||
cpu_usage_idle_bucket{cpu="cpu1",le="100"} 20
|
||||
cpu_usage_idle_bucket{cpu="cpu1",le="+Inf"} 20
|
||||
cpu_usage_idle_sum{cpu="cpu1"} 2000
|
||||
cpu_usage_idle_count{cpu="cpu1"} 20
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "histogram no buckets",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"cpu",
|
||||
map[string]string{
|
||||
"cpu": "cpu1",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"usage_idle_sum": 2000.0,
|
||||
"usage_idle_count": 20.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
telegraf.Histogram,
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_usage_idle Telegraf collected metric
|
||||
# TYPE cpu_usage_idle histogram
|
||||
cpu_usage_idle_bucket{cpu="cpu1",le="+Inf"} 20
|
||||
cpu_usage_idle_sum{cpu="cpu1"} 2000
|
||||
cpu_usage_idle_count{cpu="cpu1"} 20
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "untyped forced to counter",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
TypeMappings: prometheus.MetricTypes{Counter: []string{"cpu_time_idle"}},
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"prometheus",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cpu_time_idle": 42,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "untyped forced to gauge",
|
||||
output: &PrometheusClient{
|
||||
Listen: ":0",
|
||||
MetricVersion: 2,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
Path: "/metrics",
|
||||
TypeMappings: prometheus.MetricTypes{Gauge: []string{"cpu_time_idle"}},
|
||||
Log: logger,
|
||||
},
|
||||
metrics: []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"prometheus",
|
||||
map[string]string{
|
||||
"host": "example.org",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"cpu_time_idle": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
},
|
||||
expected: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.NoError(t, tt.output.Init())
|
||||
require.NoError(t, tt.output.Connect())
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, tt.output.Close())
|
||||
}()
|
||||
|
||||
require.NoError(t, tt.output.Write(tt.metrics))
|
||||
|
||||
resp, err := http.Get(tt.output.URL())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t,
|
||||
strings.TrimSpace(string(tt.expected)),
|
||||
strings.TrimSpace(string(body)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripMetricVersion2(t *testing.T) {
|
||||
logger := testutil.Logger{Name: "outputs.prometheus_client"}
|
||||
regxPattern := regexp.MustCompile(`.*prometheus_request_.*`)
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
name: "untyped",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle untyped
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "counter",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle counter
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "gauge",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multi",
|
||||
data: []byte(`
|
||||
# HELP cpu_time_guest Telegraf collected metric
|
||||
# TYPE cpu_time_guest gauge
|
||||
cpu_time_guest{host="one.example.org"} 42
|
||||
cpu_time_guest{host="two.example.org"} 42
|
||||
# HELP cpu_time_idle Telegraf collected metric
|
||||
# TYPE cpu_time_idle gauge
|
||||
cpu_time_idle{host="one.example.org"} 42
|
||||
cpu_time_idle{host="two.example.org"} 42
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "histogram",
|
||||
data: []byte(`
|
||||
# HELP http_request_duration_seconds Telegraf collected metric
|
||||
# TYPE http_request_duration_seconds histogram
|
||||
http_request_duration_seconds_bucket{le="0.05"} 24054
|
||||
http_request_duration_seconds_bucket{le="0.1"} 33444
|
||||
http_request_duration_seconds_bucket{le="0.2"} 100392
|
||||
http_request_duration_seconds_bucket{le="0.5"} 129389
|
||||
http_request_duration_seconds_bucket{le="1"} 133988
|
||||
http_request_duration_seconds_bucket{le="+Inf"} 144320
|
||||
http_request_duration_seconds_sum 53423
|
||||
http_request_duration_seconds_count 144320
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "summary",
|
||||
data: []byte(`
|
||||
# HELP rpc_duration_seconds Telegraf collected metric
|
||||
# TYPE rpc_duration_seconds summary
|
||||
rpc_duration_seconds{quantile="0.01"} 3102
|
||||
rpc_duration_seconds{quantile="0.05"} 3272
|
||||
rpc_duration_seconds{quantile="0.5"} 4773
|
||||
rpc_duration_seconds{quantile="0.9"} 9001
|
||||
rpc_duration_seconds{quantile="0.99"} 76656
|
||||
rpc_duration_seconds_sum 1.7560473e+07
|
||||
rpc_duration_seconds_count 2693
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
url := fmt.Sprintf("http://%s", ts.Listener.Addr())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err := w.Write(tt.data); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
input := &inputs.Prometheus{
|
||||
Log: logger,
|
||||
URLs: []string{url},
|
||||
URLTag: "",
|
||||
MetricVersion: 2,
|
||||
}
|
||||
err := input.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err = input.Start(&acc)
|
||||
require.NoError(t, err)
|
||||
err = input.Gather(&acc)
|
||||
require.NoError(t, err)
|
||||
input.Stop()
|
||||
|
||||
metrics := acc.GetTelegrafMetrics()
|
||||
|
||||
output := &PrometheusClient{
|
||||
Listen: "127.0.0.1:0",
|
||||
Path: defaultPath,
|
||||
MetricVersion: 2,
|
||||
Log: logger,
|
||||
CollectorsExclude: []string{"gocollector", "process"},
|
||||
}
|
||||
err = output.Init()
|
||||
require.NoError(t, err)
|
||||
err = output.Connect()
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err = output.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
err = output.Write(metrics)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.Get(output.URL())
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
actual, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
current := regxPattern.ReplaceAllLiteralString(string(actual), "")
|
||||
require.Equal(t,
|
||||
strings.TrimSpace(string(tt.data)),
|
||||
strings.TrimSpace(current))
|
||||
})
|
||||
}
|
||||
}
|
59
plugins/outputs/prometheus_client/sample.conf
Normal file
59
plugins/outputs/prometheus_client/sample.conf
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Configuration for the Prometheus client to spawn
|
||||
[[outputs.prometheus_client]]
|
||||
## Address to listen on.
|
||||
## ex:
|
||||
## listen = ":9273"
|
||||
## listen = "vsock://:9273"
|
||||
listen = ":9273"
|
||||
|
||||
## Maximum duration before timing out read of the request
|
||||
# read_timeout = "10s"
|
||||
## Maximum duration before timing out write of the response
|
||||
# write_timeout = "10s"
|
||||
|
||||
## Metric version controls the mapping from Prometheus metrics into Telegraf metrics.
|
||||
## See "Metric Format Configuration" in plugins/inputs/prometheus/README.md for details.
|
||||
## Valid options: 1, 2
|
||||
# metric_version = 1
|
||||
|
||||
## Use HTTP Basic Authentication.
|
||||
# basic_username = "Foo"
|
||||
# basic_password = "Bar"
|
||||
|
||||
## If set, the IP Ranges which are allowed to access metrics.
|
||||
## ex: ip_range = ["192.168.0.0/24", "192.168.1.0/30"]
|
||||
# ip_range = []
|
||||
|
||||
## Path to publish the metrics on.
|
||||
# path = "/metrics"
|
||||
|
||||
## Expiration interval for each metric. 0 == no expiration
|
||||
# expiration_interval = "60s"
|
||||
|
||||
## Collectors to enable, valid entries are "gocollector" and "process".
|
||||
## If unset, both are enabled.
|
||||
# collectors_exclude = ["gocollector", "process"]
|
||||
|
||||
## Send string metrics as Prometheus labels.
|
||||
## Unless set to false all string metrics will be sent as labels.
|
||||
# string_as_label = true
|
||||
|
||||
## If set, enable TLS with the given certificate.
|
||||
# tls_cert = "/etc/ssl/telegraf.crt"
|
||||
# tls_key = "/etc/ssl/telegraf.key"
|
||||
|
||||
## Set one or more allowed client CA certificate file names to
|
||||
## enable mutually authenticated TLS connections
|
||||
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
|
||||
|
||||
## Export metric collection time.
|
||||
# export_timestamp = false
|
||||
|
||||
## Set custom headers for HTTP responses.
|
||||
# http_headers = {"X-Special-Header" = "Special-Value"}
|
||||
|
||||
## Specify the metric type explicitly.
|
||||
## This overrides the metric-type of the Telegraf metric. Globbing is allowed.
|
||||
# [outputs.prometheus_client.metric_types]
|
||||
# counter = []
|
||||
# gauge = []
|
424
plugins/outputs/prometheus_client/v1/collector.go
Normal file
424
plugins/outputs/prometheus_client/v1/collector.go
Normal file
|
@ -0,0 +1,424 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
serializers_prometheus "github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
invalidNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
|
||||
validNameCharRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*`)
|
||||
)
|
||||
|
||||
// SampleID uniquely identifies a Sample
|
||||
type SampleID string
|
||||
|
||||
// Sample represents the current value of a series.
|
||||
type Sample struct {
|
||||
// Labels are the Prometheus labels.
|
||||
Labels map[string]string
|
||||
// Value is the value in the Prometheus output. Only one of these will populated.
|
||||
Value float64
|
||||
HistogramValue map[float64]uint64
|
||||
SummaryValue map[float64]float64
|
||||
// Histograms and Summaries need a count and a sum
|
||||
Count uint64
|
||||
Sum float64
|
||||
// Metric timestamp
|
||||
Timestamp time.Time
|
||||
// Expiration is the deadline that this Sample is valid until.
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
// MetricFamily contains the data required to build valid prometheus Metrics.
|
||||
type MetricFamily struct {
|
||||
// Samples are the Sample belonging to this MetricFamily.
|
||||
Samples map[SampleID]*Sample
|
||||
// Need the telegraf ValueType because there isn't a Prometheus ValueType
|
||||
// representing Histogram or Summary
|
||||
TelegrafValueType telegraf.ValueType
|
||||
// LabelSet is the label counts for all Samples.
|
||||
LabelSet map[string]int
|
||||
}
|
||||
|
||||
type Collector struct {
|
||||
ExpirationInterval time.Duration
|
||||
StringAsLabel bool
|
||||
ExportTimestamp bool
|
||||
TypeMapping serializers_prometheus.MetricTypes
|
||||
Log telegraf.Logger
|
||||
|
||||
sync.Mutex
|
||||
fam map[string]*MetricFamily
|
||||
expireTicker *time.Ticker
|
||||
}
|
||||
|
||||
func NewCollector(expire time.Duration, stringsAsLabel, exportTimestamp bool, typeMapping serializers_prometheus.MetricTypes, log telegraf.Logger) *Collector {
|
||||
c := &Collector{
|
||||
ExpirationInterval: expire,
|
||||
StringAsLabel: stringsAsLabel,
|
||||
ExportTimestamp: exportTimestamp,
|
||||
TypeMapping: typeMapping,
|
||||
Log: log,
|
||||
fam: make(map[string]*MetricFamily),
|
||||
}
|
||||
|
||||
if c.ExpirationInterval != 0 {
|
||||
c.expireTicker = time.NewTicker(c.ExpirationInterval)
|
||||
go func() {
|
||||
for {
|
||||
<-c.expireTicker.C
|
||||
c.Expire(time.Now())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (*Collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
prometheus.NewGauge(prometheus.GaugeOpts{Name: "Dummy", Help: "Dummy"}).Describe(ch)
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
// Expire metrics, doing this on Collect ensure metrics are removed even if no
|
||||
// new metrics are added to the output.
|
||||
if c.ExpirationInterval != 0 {
|
||||
c.Expire(time.Now())
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for name, family := range c.fam {
|
||||
// Get list of all labels on metricFamily
|
||||
var labelNames []string
|
||||
for k, v := range family.LabelSet {
|
||||
if v > 0 {
|
||||
labelNames = append(labelNames, k)
|
||||
}
|
||||
}
|
||||
desc := prometheus.NewDesc(name, "Telegraf collected metric", labelNames, nil)
|
||||
|
||||
for _, sample := range family.Samples {
|
||||
// Get labels for this sample; unset labels will be set to the
|
||||
// empty string
|
||||
var labels []string
|
||||
for _, label := range labelNames {
|
||||
v := sample.Labels[label]
|
||||
labels = append(labels, v)
|
||||
}
|
||||
|
||||
var metric prometheus.Metric
|
||||
var err error
|
||||
switch family.TelegrafValueType {
|
||||
case telegraf.Summary:
|
||||
metric, err = prometheus.NewConstSummary(desc, sample.Count, sample.Sum, sample.SummaryValue, labels...)
|
||||
case telegraf.Histogram:
|
||||
metric, err = prometheus.NewConstHistogram(desc, sample.Count, sample.Sum, sample.HistogramValue, labels...)
|
||||
default:
|
||||
metric, err = prometheus.NewConstMetric(desc, getPromValueType(family.TelegrafValueType), sample.Value, labels...)
|
||||
}
|
||||
if err != nil {
|
||||
c.Log.Errorf("Error creating prometheus metric: "+
|
||||
"key: %s, labels: %v, err: %v",
|
||||
name, labels, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if c.ExportTimestamp {
|
||||
metric = prometheus.NewMetricWithTimestamp(sample.Timestamp, metric)
|
||||
}
|
||||
ch <- metric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sanitize(value string) string {
|
||||
return invalidNameCharRE.ReplaceAllString(value, "_")
|
||||
}
|
||||
|
||||
func isValidTagName(tag string) bool {
|
||||
return validNameCharRE.MatchString(tag)
|
||||
}
|
||||
|
||||
func getPromValueType(tt telegraf.ValueType) prometheus.ValueType {
|
||||
switch tt {
|
||||
case telegraf.Counter:
|
||||
return prometheus.CounterValue
|
||||
case telegraf.Gauge:
|
||||
return prometheus.GaugeValue
|
||||
default:
|
||||
return prometheus.UntypedValue
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSampleID creates a SampleID based on the tags of a telegraf.Metric.
|
||||
func CreateSampleID(tags map[string]string) SampleID {
|
||||
pairs := make([]string, 0, len(tags))
|
||||
for k, v := range tags {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return SampleID(strings.Join(pairs, ","))
|
||||
}
|
||||
|
||||
func addSample(fam *MetricFamily, sample *Sample, sampleID SampleID) {
|
||||
for k := range sample.Labels {
|
||||
fam.LabelSet[k]++
|
||||
}
|
||||
|
||||
fam.Samples[sampleID] = sample
|
||||
}
|
||||
|
||||
func (c *Collector) addMetricFamily(point telegraf.Metric, sample *Sample, mname string, sampleID SampleID) {
|
||||
var fam *MetricFamily
|
||||
var ok bool
|
||||
if fam, ok = c.fam[mname]; !ok {
|
||||
pointType := c.TypeMapping.DetermineType(mname, point)
|
||||
fam = &MetricFamily{
|
||||
Samples: make(map[SampleID]*Sample),
|
||||
TelegrafValueType: pointType,
|
||||
LabelSet: make(map[string]int),
|
||||
}
|
||||
c.fam[mname] = fam
|
||||
}
|
||||
|
||||
addSample(fam, sample, sampleID)
|
||||
}
|
||||
|
||||
// Sorted returns a copy of the metrics in time ascending order. A copy is
|
||||
// made to avoid modifying the input metric slice since doing so is not
|
||||
// allowed.
|
||||
func sorted(metrics []telegraf.Metric) []telegraf.Metric {
|
||||
batch := make([]telegraf.Metric, 0, len(metrics))
|
||||
for i := len(metrics) - 1; i >= 0; i-- {
|
||||
batch = append(batch, metrics[i])
|
||||
}
|
||||
sort.Slice(batch, func(i, j int) bool {
|
||||
return batch[i].Time().Before(batch[j].Time())
|
||||
})
|
||||
return batch
|
||||
}
|
||||
|
||||
func (c *Collector) Add(metrics []telegraf.Metric) error {
|
||||
c.addMetrics(metrics)
|
||||
|
||||
// Expire metrics, doing this on Add ensure metrics are removed even if no
|
||||
// new metrics are added to the output.
|
||||
if c.ExpirationInterval != 0 {
|
||||
c.Expire(time.Now())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) addMetrics(metrics []telegraf.Metric) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for _, point := range sorted(metrics) {
|
||||
tags := point.Tags()
|
||||
sampleID := CreateSampleID(tags)
|
||||
|
||||
labels := make(map[string]string)
|
||||
for k, v := range tags {
|
||||
name, ok := serializers_prometheus.SanitizeLabelName(k)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
labels[name] = v
|
||||
}
|
||||
|
||||
// Prometheus doesn't have a string value type, so convert string
|
||||
// fields to labels if enabled.
|
||||
if c.StringAsLabel {
|
||||
for fn, fv := range point.Fields() {
|
||||
sfv, ok := fv.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
name, ok := serializers_prometheus.SanitizeLabelName(fn)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
labels[name] = sfv
|
||||
}
|
||||
}
|
||||
|
||||
switch point.Type() {
|
||||
case telegraf.Summary:
|
||||
var mname string
|
||||
var sum float64
|
||||
var count uint64
|
||||
summaryvalue := make(map[float64]float64)
|
||||
for fn, fv := range point.Fields() {
|
||||
var value float64
|
||||
switch fv := fv.(type) {
|
||||
case int64:
|
||||
value = float64(fv)
|
||||
case uint64:
|
||||
value = float64(fv)
|
||||
case float64:
|
||||
value = fv
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
switch fn {
|
||||
case "sum":
|
||||
sum = value
|
||||
case "count":
|
||||
count = uint64(value)
|
||||
default:
|
||||
limit, err := strconv.ParseFloat(fn, 64)
|
||||
if err == nil {
|
||||
summaryvalue[limit] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
sample := &Sample{
|
||||
Labels: labels,
|
||||
SummaryValue: summaryvalue,
|
||||
Count: count,
|
||||
Sum: sum,
|
||||
Timestamp: point.Time(),
|
||||
Expiration: now.Add(c.ExpirationInterval),
|
||||
}
|
||||
mname = sanitize(point.Name())
|
||||
|
||||
if !isValidTagName(mname) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.addMetricFamily(point, sample, mname, sampleID)
|
||||
|
||||
case telegraf.Histogram:
|
||||
var mname string
|
||||
var sum float64
|
||||
var count uint64
|
||||
histogramvalue := make(map[float64]uint64)
|
||||
for fn, fv := range point.Fields() {
|
||||
var value float64
|
||||
switch fv := fv.(type) {
|
||||
case int64:
|
||||
value = float64(fv)
|
||||
case uint64:
|
||||
value = float64(fv)
|
||||
case float64:
|
||||
value = fv
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
switch fn {
|
||||
case "sum":
|
||||
sum = value
|
||||
case "count":
|
||||
count = uint64(value)
|
||||
default:
|
||||
limit, err := strconv.ParseFloat(fn, 64)
|
||||
if err == nil {
|
||||
histogramvalue[limit] = uint64(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
sample := &Sample{
|
||||
Labels: labels,
|
||||
HistogramValue: histogramvalue,
|
||||
Count: count,
|
||||
Sum: sum,
|
||||
Timestamp: point.Time(),
|
||||
Expiration: now.Add(c.ExpirationInterval),
|
||||
}
|
||||
mname = sanitize(point.Name())
|
||||
|
||||
if !isValidTagName(mname) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.addMetricFamily(point, sample, mname, sampleID)
|
||||
|
||||
default:
|
||||
for fn, fv := range point.Fields() {
|
||||
// Ignore string and bool fields.
|
||||
var value float64
|
||||
switch fv := fv.(type) {
|
||||
case int64:
|
||||
value = float64(fv)
|
||||
case uint64:
|
||||
value = float64(fv)
|
||||
case float64:
|
||||
value = fv
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
sample := &Sample{
|
||||
Labels: labels,
|
||||
Value: value,
|
||||
Timestamp: point.Time(),
|
||||
Expiration: now.Add(c.ExpirationInterval),
|
||||
}
|
||||
|
||||
// Special handling of value field; supports passthrough from
|
||||
// the prometheus input.
|
||||
var mname string
|
||||
switch point.Type() {
|
||||
case telegraf.Counter:
|
||||
if fn == "counter" {
|
||||
mname = sanitize(point.Name())
|
||||
}
|
||||
case telegraf.Gauge:
|
||||
if fn == "gauge" {
|
||||
mname = sanitize(point.Name())
|
||||
}
|
||||
}
|
||||
if mname == "" {
|
||||
if fn == "value" {
|
||||
mname = sanitize(point.Name())
|
||||
} else {
|
||||
mname = sanitize(fmt.Sprintf("%s_%s", point.Name(), fn))
|
||||
}
|
||||
}
|
||||
if !isValidTagName(mname) {
|
||||
continue
|
||||
}
|
||||
c.addMetricFamily(point, sample, mname, sampleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) Expire(now time.Time) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for name, family := range c.fam {
|
||||
for key, sample := range family.Samples {
|
||||
if now.After(sample.Expiration) {
|
||||
for k := range sample.Labels {
|
||||
family.LabelSet[k]--
|
||||
}
|
||||
delete(family.Samples, key)
|
||||
|
||||
if len(family.Samples) == 0 {
|
||||
delete(c.fam, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
98
plugins/outputs/prometheus_client/v2/collector.go
Normal file
98
plugins/outputs/prometheus_client/v2/collector.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
serializers_prometheus "github.com/influxdata/telegraf/plugins/serializers/prometheus"
|
||||
)
|
||||
|
||||
type Metric struct {
|
||||
family *dto.MetricFamily
|
||||
metric *dto.Metric
|
||||
}
|
||||
|
||||
func (m *Metric) Desc() *prometheus.Desc {
|
||||
labelNames := make([]string, 0, len(m.metric.Label))
|
||||
for _, label := range m.metric.Label {
|
||||
labelNames = append(labelNames, *label.Name)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(*m.family.Name, *m.family.Help, labelNames, nil)
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
func (m *Metric) Write(out *dto.Metric) error {
|
||||
out.Label = m.metric.Label
|
||||
out.Counter = m.metric.Counter
|
||||
out.Untyped = m.metric.Untyped
|
||||
out.Gauge = m.metric.Gauge
|
||||
out.Histogram = m.metric.Histogram
|
||||
out.Summary = m.metric.Summary
|
||||
out.TimestampMs = m.metric.TimestampMs
|
||||
return nil
|
||||
}
|
||||
|
||||
type Collector struct {
|
||||
sync.Mutex
|
||||
expireDuration time.Duration
|
||||
coll *serializers_prometheus.Collection
|
||||
}
|
||||
|
||||
func NewCollector(expire time.Duration, stringsAsLabel, exportTimestamp bool, typeMapping serializers_prometheus.MetricTypes) *Collector {
|
||||
cfg := serializers_prometheus.FormatConfig{
|
||||
StringAsLabel: stringsAsLabel,
|
||||
ExportTimestamp: exportTimestamp,
|
||||
TypeMappings: typeMapping,
|
||||
}
|
||||
|
||||
return &Collector{
|
||||
expireDuration: expire,
|
||||
coll: serializers_prometheus.NewCollection(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
func (*Collector) Describe(_ chan<- *prometheus.Desc) {
|
||||
// Sending no descriptor at all marks the Collector as "unchecked",
|
||||
// i.e. no checks will be performed at registration time, and the
|
||||
// Collector may yield any Metric it sees fit in its Collect method.
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
// Expire metrics, doing this on Collect ensure metrics are removed even if no
|
||||
// new metrics are added to the output.
|
||||
if c.expireDuration != 0 {
|
||||
c.coll.Expire(time.Now(), c.expireDuration)
|
||||
}
|
||||
|
||||
for _, family := range c.coll.GetProto() {
|
||||
for _, metric := range family.Metric {
|
||||
ch <- &Metric{family: family, metric: metric}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) Add(metrics []telegraf.Metric) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for _, metric := range metrics {
|
||||
c.coll.Add(metric, time.Now())
|
||||
}
|
||||
|
||||
// Expire metrics, doing this on Add ensure metrics are removed even if no
|
||||
// one is querying the data.
|
||||
if c.expireDuration != 0 {
|
||||
c.coll.Expire(time.Now(), c.expireDuration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue