2016-03-01 16:12:23 +00:00
|
|
|
package prometheus
|
|
|
|
|
|
|
|
// Parser inspired from
|
|
|
|
// https://github.com/prometheus/prom2json/blob/master/main.go
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
"mime"
|
2016-07-07 10:15:47 +00:00
|
|
|
"net/http"
|
2016-06-13 14:21:11 +00:00
|
|
|
"time"
|
2016-03-01 16:12:23 +00:00
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
2016-11-22 12:51:57 +00:00
|
|
|
"github.com/influxdata/telegraf/metric"
|
2016-03-01 16:12:23 +00:00
|
|
|
"github.com/matttproud/golang_protobuf_extensions/pbutil"
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
|
|
|
"github.com/prometheus/common/expfmt"
|
|
|
|
)
|
|
|
|
|
2019-11-21 04:53:57 +00:00
|
|
|
// Parse returns a slice of Metrics from a text representation of a
|
|
|
|
// metrics
|
|
|
|
func ParseV2(buf []byte, header http.Header) ([]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)
|
|
|
|
|
|
|
|
mediatype, params, err := mime.ParseMediaType(header.Get("Content-Type"))
|
|
|
|
// Prepare output
|
|
|
|
metricFamilies := make(map[string]*dto.MetricFamily)
|
|
|
|
|
|
|
|
if err == nil && mediatype == "application/vnd.google.protobuf" &&
|
|
|
|
params["encoding"] == "delimited" &&
|
|
|
|
params["proto"] == "io.prometheus.client.MetricFamily" {
|
|
|
|
for {
|
|
|
|
mf := &dto.MetricFamily{}
|
|
|
|
if _, ierr := pbutil.ReadDelimited(reader, mf); ierr != nil {
|
|
|
|
if ierr == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("reading metric family protocol buffer failed: %s", ierr)
|
|
|
|
}
|
|
|
|
metricFamilies[mf.GetName()] = mf
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
metricFamilies, err = parser.TextToMetricFamilies(reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("reading text format failed: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 20:19:28 +00:00
|
|
|
// make sure all metrics have a consistent timestamp so that metrics don't straddle two different seconds
|
|
|
|
now := time.Now()
|
2019-11-21 04:53:57 +00:00
|
|
|
// read metrics
|
|
|
|
for metricName, mf := range metricFamilies {
|
|
|
|
for _, m := range mf.Metric {
|
|
|
|
// reading tags
|
|
|
|
tags := makeLabels(m)
|
|
|
|
|
|
|
|
if mf.GetType() == dto.MetricType_SUMMARY {
|
|
|
|
// summary metric
|
2020-02-25 20:19:28 +00:00
|
|
|
telegrafMetrics := makeQuantilesV2(m, tags, metricName, mf.GetType(), now)
|
2019-11-21 04:53:57 +00:00
|
|
|
metrics = append(metrics, telegrafMetrics...)
|
|
|
|
} else if mf.GetType() == dto.MetricType_HISTOGRAM {
|
|
|
|
// histogram metric
|
2020-02-25 20:19:28 +00:00
|
|
|
telegrafMetrics := makeBucketsV2(m, tags, metricName, mf.GetType(), now)
|
2019-11-21 04:53:57 +00:00
|
|
|
metrics = append(metrics, telegrafMetrics...)
|
|
|
|
} else {
|
|
|
|
// standard metric
|
|
|
|
// reading fields
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
fields = getNameAndValueV2(m, metricName)
|
|
|
|
// converting to telegraf metric
|
|
|
|
if len(fields) > 0 {
|
|
|
|
var t time.Time
|
|
|
|
if m.TimestampMs != nil && *m.TimestampMs > 0 {
|
|
|
|
t = time.Unix(0, *m.TimestampMs*1000000)
|
|
|
|
} else {
|
2020-02-25 20:19:28 +00:00
|
|
|
t = now
|
2019-11-21 04:53:57 +00:00
|
|
|
}
|
|
|
|
metric, err := metric.New("prometheus", tags, fields, t, valueType(mf.GetType()))
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, metric)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return metrics, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Quantiles for summary metric & Buckets for histogram
|
2020-02-25 20:19:28 +00:00
|
|
|
func makeQuantilesV2(m *dto.Metric, tags map[string]string, metricName string, metricType dto.MetricType, now time.Time) []telegraf.Metric {
|
2019-11-21 04:53:57 +00:00
|
|
|
var metrics []telegraf.Metric
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
var t time.Time
|
|
|
|
if m.TimestampMs != nil && *m.TimestampMs > 0 {
|
|
|
|
t = time.Unix(0, *m.TimestampMs*1000000)
|
|
|
|
} else {
|
2020-02-25 20:19:28 +00:00
|
|
|
t = now
|
2019-11-21 04:53:57 +00:00
|
|
|
}
|
|
|
|
fields[metricName+"_count"] = float64(m.GetSummary().GetSampleCount())
|
|
|
|
fields[metricName+"_sum"] = float64(m.GetSummary().GetSampleSum())
|
|
|
|
met, err := metric.New("prometheus", tags, fields, t, valueType(metricType))
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, met)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, q := range m.GetSummary().Quantile {
|
|
|
|
newTags := tags
|
|
|
|
fields = make(map[string]interface{})
|
|
|
|
|
2020-02-10 22:18:30 +00:00
|
|
|
newTags["quantile"] = fmt.Sprint(q.GetQuantile())
|
|
|
|
fields[metricName] = float64(q.GetValue())
|
|
|
|
|
|
|
|
quantileMetric, err := metric.New("prometheus", newTags, fields, t, valueType(metricType))
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, quantileMetric)
|
2019-11-21 04:53:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return metrics
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Buckets from histogram metric
|
2020-02-25 20:19:28 +00:00
|
|
|
func makeBucketsV2(m *dto.Metric, tags map[string]string, metricName string, metricType dto.MetricType, now time.Time) []telegraf.Metric {
|
2019-11-21 04:53:57 +00:00
|
|
|
var metrics []telegraf.Metric
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
var t time.Time
|
|
|
|
if m.TimestampMs != nil && *m.TimestampMs > 0 {
|
|
|
|
t = time.Unix(0, *m.TimestampMs*1000000)
|
|
|
|
} else {
|
2020-02-25 20:19:28 +00:00
|
|
|
t = now
|
2019-11-21 04:53:57 +00:00
|
|
|
}
|
|
|
|
fields[metricName+"_count"] = float64(m.GetHistogram().GetSampleCount())
|
|
|
|
fields[metricName+"_sum"] = float64(m.GetHistogram().GetSampleSum())
|
|
|
|
|
|
|
|
met, err := metric.New("prometheus", tags, fields, t, valueType(metricType))
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, met)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range m.GetHistogram().Bucket {
|
|
|
|
newTags := tags
|
|
|
|
fields = make(map[string]interface{})
|
|
|
|
newTags["le"] = fmt.Sprint(b.GetUpperBound())
|
|
|
|
fields[metricName+"_bucket"] = float64(b.GetCumulativeCount())
|
|
|
|
|
|
|
|
histogramMetric, err := metric.New("prometheus", newTags, fields, t, valueType(metricType))
|
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, histogramMetric)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return metrics
|
|
|
|
}
|
|
|
|
|
2016-03-01 16:12:23 +00:00
|
|
|
// Parse returns a slice of Metrics from a text representation of a
|
|
|
|
// metrics
|
2016-07-07 10:15:47 +00:00
|
|
|
func Parse(buf []byte, header http.Header) ([]telegraf.Metric, error) {
|
2016-03-01 16:12:23 +00:00
|
|
|
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)
|
|
|
|
|
2016-07-07 10:15:47 +00:00
|
|
|
mediatype, params, err := mime.ParseMediaType(header.Get("Content-Type"))
|
2016-03-01 16:12:23 +00:00
|
|
|
// Prepare output
|
|
|
|
metricFamilies := make(map[string]*dto.MetricFamily)
|
2016-07-07 10:15:47 +00:00
|
|
|
|
2016-03-01 16:12:23 +00:00
|
|
|
if err == nil && mediatype == "application/vnd.google.protobuf" &&
|
|
|
|
params["encoding"] == "delimited" &&
|
|
|
|
params["proto"] == "io.prometheus.client.MetricFamily" {
|
|
|
|
for {
|
2016-07-07 10:15:47 +00:00
|
|
|
mf := &dto.MetricFamily{}
|
|
|
|
if _, ierr := pbutil.ReadDelimited(reader, mf); ierr != nil {
|
|
|
|
if ierr == io.EOF {
|
2016-03-01 16:12:23 +00:00
|
|
|
break
|
|
|
|
}
|
2016-07-07 10:15:47 +00:00
|
|
|
return nil, fmt.Errorf("reading metric family protocol buffer failed: %s", ierr)
|
2016-03-01 16:12:23 +00:00
|
|
|
}
|
2016-07-07 10:15:47 +00:00
|
|
|
metricFamilies[mf.GetName()] = mf
|
2016-03-01 16:12:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
metricFamilies, err = parser.TextToMetricFamilies(reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("reading text format failed: %s", err)
|
|
|
|
}
|
2016-07-07 10:15:47 +00:00
|
|
|
}
|
2016-03-01 16:12:23 +00:00
|
|
|
|
2020-02-25 20:19:28 +00:00
|
|
|
// make sure all metrics have a consistent timestamp so that metrics don't straddle two different seconds
|
|
|
|
now := time.Now()
|
2016-07-07 10:15:47 +00:00
|
|
|
// read metrics
|
|
|
|
for metricName, mf := range metricFamilies {
|
|
|
|
for _, m := range mf.Metric {
|
|
|
|
// reading tags
|
|
|
|
tags := makeLabels(m)
|
|
|
|
// reading fields
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
if mf.GetType() == dto.MetricType_SUMMARY {
|
|
|
|
// summary metric
|
|
|
|
fields = makeQuantiles(m)
|
|
|
|
fields["count"] = float64(m.GetSummary().GetSampleCount())
|
|
|
|
fields["sum"] = float64(m.GetSummary().GetSampleSum())
|
|
|
|
} else if mf.GetType() == dto.MetricType_HISTOGRAM {
|
2017-09-18 23:21:45 +00:00
|
|
|
// histogram metric
|
2016-07-07 10:15:47 +00:00
|
|
|
fields = makeBuckets(m)
|
|
|
|
fields["count"] = float64(m.GetHistogram().GetSampleCount())
|
|
|
|
fields["sum"] = float64(m.GetHistogram().GetSampleSum())
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// standard metric
|
|
|
|
fields = getNameAndValue(m)
|
|
|
|
}
|
|
|
|
// converting to telegraf metric
|
|
|
|
if len(fields) > 0 {
|
|
|
|
var t time.Time
|
|
|
|
if m.TimestampMs != nil && *m.TimestampMs > 0 {
|
|
|
|
t = time.Unix(0, *m.TimestampMs*1000000)
|
2016-03-01 16:12:23 +00:00
|
|
|
} else {
|
2020-02-25 20:19:28 +00:00
|
|
|
t = now
|
2016-03-01 16:12:23 +00:00
|
|
|
}
|
2017-10-18 21:51:08 +00:00
|
|
|
metric, err := metric.New(metricName, tags, fields, t, valueType(mf.GetType()))
|
2016-07-07 10:15:47 +00:00
|
|
|
if err == nil {
|
|
|
|
metrics = append(metrics, metric)
|
2016-03-01 16:12:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-07 10:15:47 +00:00
|
|
|
return metrics, err
|
2016-03-01 16:12:23 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 21:51:08 +00:00
|
|
|
func valueType(mt dto.MetricType) telegraf.ValueType {
|
|
|
|
switch mt {
|
|
|
|
case dto.MetricType_COUNTER:
|
|
|
|
return telegraf.Counter
|
|
|
|
case dto.MetricType_GAUGE:
|
|
|
|
return telegraf.Gauge
|
2017-10-24 23:28:52 +00:00
|
|
|
case dto.MetricType_SUMMARY:
|
|
|
|
return telegraf.Summary
|
|
|
|
case dto.MetricType_HISTOGRAM:
|
|
|
|
return telegraf.Histogram
|
2017-10-18 21:51:08 +00:00
|
|
|
default:
|
|
|
|
return telegraf.Untyped
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 16:12:23 +00:00
|
|
|
// 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 {
|
2017-10-24 23:28:52 +00:00
|
|
|
if !math.IsNaN(m.GetCounter().GetValue()) {
|
2016-03-01 16:12:23 +00:00
|
|
|
fields["counter"] = float64(m.GetCounter().GetValue())
|
|
|
|
}
|
|
|
|
} else if m.Untyped != nil {
|
2017-10-24 23:28:52 +00:00
|
|
|
if !math.IsNaN(m.GetUntyped().GetValue()) {
|
2016-03-01 16:12:23 +00:00
|
|
|
fields["value"] = float64(m.GetUntyped().GetValue())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fields
|
|
|
|
}
|
2019-11-21 04:53:57 +00:00
|
|
|
|
|
|
|
// Get name and value from metric
|
|
|
|
func getNameAndValueV2(m *dto.Metric, metricName string) map[string]interface{} {
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
if m.Gauge != nil {
|
|
|
|
if !math.IsNaN(m.GetGauge().GetValue()) {
|
|
|
|
fields[metricName] = float64(m.GetGauge().GetValue())
|
|
|
|
}
|
|
|
|
} else if m.Counter != nil {
|
|
|
|
if !math.IsNaN(m.GetCounter().GetValue()) {
|
|
|
|
fields[metricName] = float64(m.GetCounter().GetValue())
|
|
|
|
}
|
|
|
|
} else if m.Untyped != nil {
|
|
|
|
if !math.IsNaN(m.GetUntyped().GetValue()) {
|
|
|
|
fields[metricName] = float64(m.GetUntyped().GetValue())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fields
|
|
|
|
}
|