Skip to content

Commit

Permalink
Add Prometheus Remote Write Exporter supporting Cortex - helper (#1555)
Browse files Browse the repository at this point in the history
* add helper for Prometheus remote write exporter

* remove copyright year

* test coverage 100%

* test coverage 100%
  • Loading branch information
huyan0 authored Aug 18, 2020
1 parent ed65294 commit 5ab3c84
Show file tree
Hide file tree
Showing 9 changed files with 663 additions and 11 deletions.
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/exporter.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/exporter_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
8 changes: 2 additions & 6 deletions exporter/prometheusremotewriteexporter/factory.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -66,11 +66,7 @@ func createMetricsExporter(_ context.Context, _ component.ExporterCreateParams,
exporterhelper.WithShutdown(prwe.shutdown),
)

if err != nil {
return nil, err
}

return prwexp, nil
return prwexp, err
}

func createDefaultConfig() configmodels.Exporter {
Expand Down
2 changes: 1 addition & 1 deletion exporter/prometheusremotewriteexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The OpenTelemetry Authors
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down
210 changes: 210 additions & 0 deletions exporter/prometheusremotewriteexporter/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheusremotewriteexporter

import (
"log"
"sort"
"strings"
"unicode"

"github.com/prometheus/prometheus/prompb"

common "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1"
otlp "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/metrics/v1"
)

const (
totalStr = "total"
delimeter = "_"
keyStr = "key"
)

// ByLabelName enables the usage of sort.Sort() with a slice of labels
type ByLabelName []prompb.Label

func (a ByLabelName) Len() int { return len(a) }
func (a ByLabelName) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a ByLabelName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// validateMetrics returns a bool representing whether the metric has a valid type and temporality combination.
func validateMetrics(desc *otlp.MetricDescriptor) bool {

if desc == nil {
return false
}

switch desc.GetType() {
case otlp.MetricDescriptor_MONOTONIC_DOUBLE, otlp.MetricDescriptor_MONOTONIC_INT64,
otlp.MetricDescriptor_HISTOGRAM, otlp.MetricDescriptor_SUMMARY:
return desc.GetTemporality() == otlp.MetricDescriptor_CUMULATIVE
case otlp.MetricDescriptor_INT64, otlp.MetricDescriptor_DOUBLE:
return true
}

return false
}

// addSample finds a TimeSeries in tsMap that corresponds to the label set labels, and add sample to the TimeSeries; it
// creates a new TimeSeries in the map if not found. tsMap is unmodified if either of its parameters is nil.
func addSample(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, labels []prompb.Label,
ty otlp.MetricDescriptor_Type) {

if sample == nil || labels == nil || tsMap == nil {
return
}

sig := timeSeriesSignature(ty, &labels)
ts, ok := tsMap[sig]

if ok {
ts.Samples = append(ts.Samples, *sample)
} else {
newTs := &prompb.TimeSeries{
Labels: labels,
Samples: []prompb.Sample{*sample},
}
tsMap[sig] = newTs
}
}

// timeSeries return a string signature in the form of:
// TYPE-label1-value1- ... -labelN-valueN
// the label slice should not contain duplicate label names; this method sorts the slice by label name before creating
// the signature.
func timeSeriesSignature(t otlp.MetricDescriptor_Type, labels *[]prompb.Label) string {
b := strings.Builder{}
b.WriteString(t.String())

sort.Sort(ByLabelName(*labels))

for _, lb := range *labels {
b.WriteString("-")
b.WriteString(lb.GetName())
b.WriteString("-")
b.WriteString(lb.GetValue())
}

return b.String()
}

// createLabelSet creates a slice of Cortex Label with OTLP labels and paris of string values.
// Unpaired string value is ignored. String pairs overwrites OTLP labels if collision happens, and the overwrite is
// logged. Resultant label names are sanitized.
func createLabelSet(labels []*common.StringKeyValue, extras ...string) []prompb.Label {

// map ensures no duplicate label name
l := map[string]prompb.Label{}

for _, lb := range labels {
l[lb.Key] = prompb.Label{
Name: sanitize(lb.Key),
Value: lb.Value,
}
}

for i := 0; i < len(extras); i += 2 {
if i+1 >= len(extras) {
break
}
_, found := l[extras[i]]
if found {
log.Println("label " + extras[i] + " is overwritten. Check if Prometheus reserved labels are used.")
}
// internal labels should be maintained
name := extras[i]
if !(len(name) > 4 && name[:2] == "__" && name[len(name)-2:] == "__") {
name = sanitize(name)
}
l[extras[i]] = prompb.Label{
Name: name,
Value: extras[i+1],
}
}

s := make([]prompb.Label, 0, len(l))

for _, lb := range l {
s = append(s, lb)
}

return s
}

// getPromMetricName creates a Prometheus metric name by attaching namespace prefix, and _total suffix for Monotonic
// metrics.
func getPromMetricName(desc *otlp.MetricDescriptor, ns string) string {

if desc == nil {
return ""
}
// whether _total suffix should be applied
isCounter := desc.Type == otlp.MetricDescriptor_MONOTONIC_INT64 ||
desc.Type == otlp.MetricDescriptor_MONOTONIC_DOUBLE

b := strings.Builder{}

b.WriteString(ns)

if b.Len() > 0 {
b.WriteString(delimeter)
}
b.WriteString(desc.GetName())

// Including units makes two metrics with the same name and label set belong to two different TimeSeries if the
// units are different.
/*
if b.Len() > 0 && len(desc.GetUnit()) > 0{
fmt.Fprintf(&b, delimeter)
fmt.Fprintf(&b, desc.GetUnit())
}
*/

if b.Len() > 0 && isCounter {
b.WriteString(delimeter)
b.WriteString(totalStr)
}
return sanitize(b.String())
}

// copied from prometheus-go-metric-exporter
// sanitize replaces non-alphanumeric characters with underscores in s.
func sanitize(s string) string {
if len(s) == 0 {
return s
}

// Note: No length limit for label keys because Prometheus doesn't
// define a length limit, thus we should NOT be truncating label keys.
// See https://github.com/orijtech/prometheus-go-metrics-exporter/issues/4.
s = strings.Map(sanitizeRune, s)
if unicode.IsDigit(rune(s[0])) {
s = keyStr + delimeter + s
}
if s[0] == '_' {
s = keyStr + s
}
return s
}

// copied from prometheus-go-metric-exporter
// sanitizeRune converts anything that is not a letter or digit to an underscore
func sanitizeRune(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return r
}
// Everything else turns into an underscore
return '_'
}
Loading

0 comments on commit 5ab3c84

Please sign in to comment.