influxdb/prometheus/codec.go

225 lines
5.2 KiB
Go

package prometheus
import (
"bytes"
"encoding/json"
"io"
"math"
"strconv"
"time"
"github.com/influxdata/influxdb/v2/models"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
)
// Encoder transforms metric families into bytes.
type Encoder interface {
// Encode encodes metrics into bytes.
Encode(mfs []*dto.MetricFamily) ([]byte, error)
}
// Expfmt encodes metric families into prometheus exposition format.
type Expfmt struct {
Format expfmt.Format
}
// Encode encodes metrics into prometheus exposition format bytes.
func (e *Expfmt) Encode(mfs []*dto.MetricFamily) ([]byte, error) {
return EncodeExpfmt(mfs, e.Format)
}
// DecodeExpfmt decodes the reader of format into metric families.
func DecodeExpfmt(r io.Reader, format expfmt.Format) ([]*dto.MetricFamily, error) {
dec := expfmt.NewDecoder(r, format)
mfs := []*dto.MetricFamily{}
for {
var mf dto.MetricFamily
if err := dec.Decode(&mf); err != nil {
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
}
mfs = append(mfs, &mf)
}
return mfs, nil
}
// EncodeExpfmt encodes the metrics family (defaults to expfmt.FmtProtoDelim).
func EncodeExpfmt(mfs []*dto.MetricFamily, opts ...expfmt.Format) ([]byte, error) {
format := expfmt.FmtProtoDelim
if len(opts) != 0 && opts[0] != "" {
format = opts[0]
}
buf := &bytes.Buffer{}
enc := expfmt.NewEncoder(buf, format)
for _, mf := range mfs {
if err := enc.Encode(mf); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
// JSON encodes metric families into JSON.
type JSON struct{}
// Encode encodes metrics JSON bytes. This not always works
// as some prometheus values are NaN or Inf.
func (j *JSON) Encode(mfs []*dto.MetricFamily) ([]byte, error) {
return EncodeJSON(mfs)
}
// DecodeJSON decodes a JSON array of metrics families.
func DecodeJSON(r io.Reader) ([]*dto.MetricFamily, error) {
dec := json.NewDecoder(r)
families := []*dto.MetricFamily{}
for {
mfs := []*dto.MetricFamily{}
if err := dec.Decode(&mfs); err == io.EOF {
break
} else if err != nil {
return nil, err
}
families = append(families, mfs...)
}
return families, nil
}
// EncodeJSON encodes the metric families to JSON.
func EncodeJSON(mfs []*dto.MetricFamily) ([]byte, error) {
return json.Marshal(mfs)
}
const (
// just in case the definition of time.Nanosecond changes from 1.
nsPerMilliseconds = int64(time.Millisecond / time.Nanosecond)
)
// LineProtocol encodes metric families into influxdb line protocol.
type LineProtocol struct{}
// Encode encodes metrics into line protocol format bytes.
func (l *LineProtocol) Encode(mfs []*dto.MetricFamily) ([]byte, error) {
return EncodeLineProtocol(mfs)
}
// EncodeLineProtocol converts prometheus metrics into line protocol.
func EncodeLineProtocol(mfs []*dto.MetricFamily) ([]byte, error) {
var b bytes.Buffer
pts := points(mfs)
for _, p := range pts {
if _, err := b.WriteString(p.String()); err != nil {
return nil, err
}
if err := b.WriteByte('\n'); err != nil {
return nil, err
}
}
return b.Bytes(), nil
}
func points(mfs []*dto.MetricFamily) models.Points {
pts := make(models.Points, 0, len(mfs))
for _, mf := range mfs {
mts := make(models.Points, 0, len(mf.Metric))
name := mf.GetName()
for _, m := range mf.Metric {
ts := tags(m.Label)
fs := fields(mf.GetType(), m)
tm := timestamp(m)
pt, err := models.NewPoint(name, ts, fs, tm)
if err != nil {
continue
}
mts = append(mts, pt)
}
pts = append(pts, mts...)
}
return pts
}
func timestamp(m *dto.Metric) time.Time {
var tm time.Time
if m.GetTimestampMs() > 0 {
tm = time.Unix(0, m.GetTimestampMs()*nsPerMilliseconds)
}
return tm
}
func tags(labels []*dto.LabelPair) models.Tags {
ts := make(models.Tags, len(labels))
for i, label := range labels {
ts[i] = models.NewTag([]byte(label.GetName()), []byte(label.GetValue()))
}
return ts
}
func fields(typ dto.MetricType, m *dto.Metric) models.Fields {
switch typ {
case dto.MetricType_SUMMARY:
return summary(m.GetSummary())
case dto.MetricType_HISTOGRAM:
return histogram(m.GetHistogram())
case dto.MetricType_GAUGE:
return value("gauge", m.GetGauge())
case dto.MetricType_COUNTER:
return value("counter", m.GetCounter())
case dto.MetricType_UNTYPED:
return value("value", m.GetUntyped())
default:
return nil
}
}
func summary(s *dto.Summary) map[string]interface{} {
fields := make(map[string]interface{}, len(s.Quantile)+2)
for _, q := range s.Quantile {
v := q.GetValue()
if !math.IsNaN(v) {
key := strconv.FormatFloat(q.GetQuantile(), 'f', -1, 64)
fields[key] = v
}
}
fields["count"] = float64(s.GetSampleCount())
fields["sum"] = float64(s.GetSampleSum())
return fields
}
func histogram(hist *dto.Histogram) map[string]interface{} {
fields := make(map[string]interface{}, len(hist.Bucket)+2)
for _, b := range hist.Bucket {
k := strconv.FormatFloat(b.GetUpperBound(), 'f', -1, 64)
fields[k] = float64(b.GetCumulativeCount())
}
fields["count"] = float64(hist.GetSampleCount())
fields["sum"] = float64(hist.GetSampleSum())
return fields
}
type valuer interface {
GetValue() float64
}
func value(typ string, m valuer) models.Fields {
vs := make(models.Fields, 1)
v := m.GetValue()
if !math.IsNaN(v) {
vs[typ] = v
}
return vs
}