2020-03-24 07:41:10 +02:00
|
|
|
// Copyright The OpenTelemetry Authors
|
2019-11-26 21:47:15 +02:00
|
|
|
//
|
|
|
|
// 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 prometheus
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-12-23 19:47:51 +02:00
|
|
|
"fmt"
|
2019-11-26 21:47:15 +02:00
|
|
|
"net/http"
|
2020-01-02 20:41:21 +02:00
|
|
|
"time"
|
2019-11-26 21:47:15 +02:00
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
|
|
|
|
"go.opentelemetry.io/otel/api/core"
|
2020-01-02 20:41:21 +02:00
|
|
|
"go.opentelemetry.io/otel/api/global"
|
2020-04-23 21:10:58 +02:00
|
|
|
"go.opentelemetry.io/otel/api/label"
|
2019-11-26 21:47:15 +02:00
|
|
|
export "go.opentelemetry.io/otel/sdk/export/metric"
|
|
|
|
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
2020-04-15 01:07:11 +02:00
|
|
|
"go.opentelemetry.io/otel/sdk/metric/batcher/ungrouped"
|
2020-01-02 20:41:21 +02:00
|
|
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
|
|
|
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
2019-11-26 21:47:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Exporter is an implementation of metric.Exporter that sends metrics to
|
|
|
|
// Prometheus.
|
|
|
|
type Exporter struct {
|
|
|
|
handler http.Handler
|
|
|
|
|
|
|
|
registerer prometheus.Registerer
|
|
|
|
gatherer prometheus.Gatherer
|
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
snapshot export.CheckpointSet
|
|
|
|
onError func(error)
|
|
|
|
|
2020-04-01 23:36:37 +02:00
|
|
|
defaultSummaryQuantiles []float64
|
|
|
|
defaultHistogramBoundaries []core.Number
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ export.Exporter = &Exporter{}
|
|
|
|
var _ http.Handler = &Exporter{}
|
|
|
|
|
2020-01-03 19:48:45 +02:00
|
|
|
// Config is a set of configs for the tally reporter.
|
|
|
|
type Config struct {
|
2019-11-26 21:47:15 +02:00
|
|
|
// Registry is the prometheus registry that will be used as the default Registerer and
|
|
|
|
// Gatherer if these are not specified.
|
|
|
|
//
|
|
|
|
// If not set a new empty Registry is created.
|
|
|
|
Registry *prometheus.Registry
|
|
|
|
|
|
|
|
// Registerer is the prometheus registerer to register
|
|
|
|
// metrics with.
|
|
|
|
//
|
|
|
|
// If not specified the Registry will be used as default.
|
|
|
|
Registerer prometheus.Registerer
|
|
|
|
|
|
|
|
// Gatherer is the prometheus gatherer to gather
|
|
|
|
// metrics with.
|
|
|
|
//
|
|
|
|
// If not specified the Registry will be used as default.
|
|
|
|
Gatherer prometheus.Gatherer
|
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
// DefaultSummaryQuantiles is the default summary quantiles
|
|
|
|
// to use. Use nil to specify the system-default summary quantiles.
|
|
|
|
DefaultSummaryQuantiles []float64
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2020-04-01 23:36:37 +02:00
|
|
|
// DefaultHistogramBoundaries defines the default histogram bucket
|
|
|
|
// boundaries.
|
|
|
|
DefaultHistogramBoundaries []core.Number
|
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
// OnError is a function that handle errors that may occur while exporting metrics.
|
|
|
|
// TODO: This should be refactored or even removed once we have a better error handling mechanism.
|
|
|
|
OnError func(error)
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
2020-01-02 20:41:21 +02:00
|
|
|
// NewRawExporter returns a new prometheus exporter for prometheus metrics
|
|
|
|
// for use in a pipeline.
|
2020-01-03 19:48:45 +02:00
|
|
|
func NewRawExporter(config Config) (*Exporter, error) {
|
|
|
|
if config.Registry == nil {
|
|
|
|
config.Registry = prometheus.NewRegistry()
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
2020-01-03 19:48:45 +02:00
|
|
|
if config.Registerer == nil {
|
|
|
|
config.Registerer = config.Registry
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
2020-01-03 19:48:45 +02:00
|
|
|
if config.Gatherer == nil {
|
|
|
|
config.Gatherer = config.Registry
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
2020-01-03 19:48:45 +02:00
|
|
|
if config.OnError == nil {
|
|
|
|
config.OnError = func(err error) {
|
2019-12-23 19:47:51 +02:00
|
|
|
fmt.Println(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e := &Exporter{
|
2020-04-01 23:36:37 +02:00
|
|
|
handler: promhttp.HandlerFor(config.Gatherer, promhttp.HandlerOpts{}),
|
|
|
|
registerer: config.Registerer,
|
|
|
|
gatherer: config.Gatherer,
|
|
|
|
defaultSummaryQuantiles: config.DefaultSummaryQuantiles,
|
|
|
|
defaultHistogramBoundaries: config.DefaultHistogramBoundaries,
|
|
|
|
onError: config.OnError,
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
c := newCollector(e)
|
2020-01-03 19:48:45 +02:00
|
|
|
if err := config.Registerer.Register(c); err != nil {
|
|
|
|
config.OnError(fmt.Errorf("cannot register the collector: %w", err))
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return e, nil
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
2020-01-02 20:41:21 +02:00
|
|
|
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
|
|
|
|
// Typically called as:
|
2020-02-19 06:00:57 +02:00
|
|
|
//
|
|
|
|
// pipeline, hf, err := prometheus.InstallNewPipeline(prometheus.Config{...})
|
|
|
|
//
|
|
|
|
// if err != nil {
|
|
|
|
// ...
|
|
|
|
// }
|
|
|
|
// http.HandleFunc("/metrics", hf)
|
|
|
|
// defer pipeline.Stop()
|
|
|
|
// ... Done
|
2020-01-03 19:48:45 +02:00
|
|
|
func InstallNewPipeline(config Config) (*push.Controller, http.HandlerFunc, error) {
|
2020-02-29 03:08:30 +02:00
|
|
|
controller, hf, err := NewExportPipeline(config, time.Minute)
|
2020-01-02 20:41:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return controller, hf, err
|
|
|
|
}
|
|
|
|
global.SetMeterProvider(controller)
|
|
|
|
return controller, hf, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
|
|
|
// chaining a NewRawExporter into the recommended selectors and batchers.
|
2020-02-29 03:08:30 +02:00
|
|
|
func NewExportPipeline(config Config, period time.Duration) (*push.Controller, http.HandlerFunc, error) {
|
2020-04-01 23:36:37 +02:00
|
|
|
selector := simple.NewWithHistogramMeasure(config.DefaultHistogramBoundaries)
|
2020-01-03 19:48:45 +02:00
|
|
|
exporter, err := NewRawExporter(config)
|
2020-01-02 20:41:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prometheus needs to use a stateful batcher since counters (and histogram since they are a collection of Counters)
|
|
|
|
// are cumulative (i.e., monotonically increasing values) and should not be resetted after each export.
|
|
|
|
//
|
|
|
|
// Prometheus uses this approach to be resilient to scrape failures.
|
|
|
|
// If a Prometheus server tries to scrape metrics from a host and fails for some reason,
|
|
|
|
// it could try again on the next scrape and no data would be lost, only resolution.
|
|
|
|
//
|
|
|
|
// Gauges (or LastValues) and Summaries are an exception to this and have different behaviors.
|
2020-04-23 21:10:58 +02:00
|
|
|
batcher := ungrouped.New(selector, label.DefaultEncoder(), true)
|
2020-02-29 03:08:30 +02:00
|
|
|
pusher := push.New(batcher, exporter, period)
|
2020-01-02 20:41:21 +02:00
|
|
|
pusher.Start()
|
|
|
|
|
|
|
|
return pusher, exporter.ServeHTTP, nil
|
|
|
|
}
|
|
|
|
|
2019-11-26 21:47:15 +02:00
|
|
|
// Export exports the provide metric record to prometheus.
|
|
|
|
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
2019-12-23 19:47:51 +02:00
|
|
|
e.snapshot = checkpointSet
|
|
|
|
return nil
|
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
// collector implements prometheus.Collector interface.
|
|
|
|
type collector struct {
|
|
|
|
exp *Exporter
|
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
var _ prometheus.Collector = (*collector)(nil)
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
func newCollector(exporter *Exporter) *collector {
|
|
|
|
return &collector{
|
|
|
|
exp: exporter,
|
|
|
|
}
|
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2019-12-23 19:47:51 +02:00
|
|
|
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
|
|
|
if c.exp.snapshot == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-17 01:28:33 +02:00
|
|
|
_ = c.exp.snapshot.ForEach(func(record export.Record) error {
|
2019-12-23 19:47:51 +02:00
|
|
|
ch <- c.toDesc(&record)
|
2020-03-17 01:28:33 +02:00
|
|
|
return nil
|
2019-12-23 19:47:51 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collect exports the last calculated CheckpointSet.
|
|
|
|
//
|
|
|
|
// Collect is invoked whenever prometheus.Gatherer is also invoked.
|
|
|
|
// For example, when the HTTP endpoint is invoked by Prometheus.
|
|
|
|
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
|
|
|
if c.exp.snapshot == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-07 19:58:49 +02:00
|
|
|
err := c.exp.snapshot.ForEach(func(record export.Record) error {
|
2019-12-23 19:47:51 +02:00
|
|
|
agg := record.Aggregator()
|
|
|
|
numberKind := record.Descriptor().NumberKind()
|
|
|
|
labels := labelValues(record.Labels())
|
|
|
|
desc := c.toDesc(&record)
|
|
|
|
|
2020-04-01 23:36:37 +02:00
|
|
|
if hist, ok := agg.(aggregator.Histogram); ok {
|
2020-04-13 18:48:20 +02:00
|
|
|
if err := c.exportHistogram(ch, hist, numberKind, desc, labels); err != nil {
|
|
|
|
return fmt.Errorf("exporting histogram: %w", err)
|
|
|
|
}
|
2020-04-01 23:36:37 +02:00
|
|
|
} else if dist, ok := agg.(aggregator.Distribution); ok {
|
2019-12-23 19:47:51 +02:00
|
|
|
// TODO: summaries values are never being resetted.
|
|
|
|
// As measures are recorded, new records starts to have less impact on these summaries.
|
|
|
|
// We should implement an solution that is similar to the Prometheus Clients
|
|
|
|
// using a rolling window for summaries could be a solution.
|
|
|
|
//
|
|
|
|
// References:
|
|
|
|
// https://www.robustperception.io/how-does-a-prometheus-summary-work
|
|
|
|
// https://github.com/prometheus/client_golang/blob/fa4aa9000d2863904891d193dea354d23f3d712a/prometheus/summary.go#L135
|
2020-04-13 18:48:20 +02:00
|
|
|
if err := c.exportSummary(ch, dist, numberKind, desc, labels); err != nil {
|
|
|
|
return fmt.Errorf("exporting summary: %w", err)
|
|
|
|
}
|
2019-12-23 19:47:51 +02:00
|
|
|
} else if sum, ok := agg.(aggregator.Sum); ok {
|
2020-04-13 18:48:20 +02:00
|
|
|
if err := c.exportCounter(ch, sum, numberKind, desc, labels); err != nil {
|
|
|
|
return fmt.Errorf("exporting counter: %w", err)
|
|
|
|
}
|
2020-03-11 01:00:37 +02:00
|
|
|
} else if lastValue, ok := agg.(aggregator.LastValue); ok {
|
2020-04-13 18:48:20 +02:00
|
|
|
if err := c.exportLastValue(ch, lastValue, numberKind, desc, labels); err != nil {
|
|
|
|
return fmt.Errorf("exporting last value: %w", err)
|
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
2020-03-17 01:28:33 +02:00
|
|
|
return nil
|
2019-11-26 21:47:15 +02:00
|
|
|
})
|
2020-04-07 19:58:49 +02:00
|
|
|
if err != nil {
|
|
|
|
c.exp.onError(err)
|
|
|
|
}
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-07 19:58:49 +02:00
|
|
|
func (c *collector) exportLastValue(ch chan<- prometheus.Metric, lvagg aggregator.LastValue, kind core.NumberKind, desc *prometheus.Desc, labels []string) error {
|
2020-03-11 01:00:37 +02:00
|
|
|
lv, _, err := lvagg.LastValue()
|
2019-12-23 19:47:51 +02:00
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving last value: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
2020-03-11 01:00:37 +02:00
|
|
|
m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, lv.CoerceToFloat64(kind), labels...)
|
2019-12-23 19:47:51 +02:00
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error creating constant metric: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ch <- m
|
2020-04-07 19:58:49 +02:00
|
|
|
return nil
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-07 19:58:49 +02:00
|
|
|
func (c *collector) exportCounter(ch chan<- prometheus.Metric, sum aggregator.Sum, kind core.NumberKind, desc *prometheus.Desc, labels []string) error {
|
2019-12-23 19:47:51 +02:00
|
|
|
v, err := sum.Sum()
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving counter: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
m, err := prometheus.NewConstMetric(desc, prometheus.CounterValue, v.CoerceToFloat64(kind), labels...)
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error creating constant metric: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ch <- m
|
2020-04-07 19:58:49 +02:00
|
|
|
return nil
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
2020-04-07 19:58:49 +02:00
|
|
|
func (c *collector) exportSummary(ch chan<- prometheus.Metric, dist aggregator.Distribution, kind core.NumberKind, desc *prometheus.Desc, labels []string) error {
|
2019-12-23 19:47:51 +02:00
|
|
|
count, err := dist.Count()
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving count: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var sum core.Number
|
|
|
|
sum, err = dist.Sum()
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving distribution sum: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
quantiles := make(map[float64]float64)
|
|
|
|
for _, quantile := range c.exp.defaultSummaryQuantiles {
|
|
|
|
q, _ := dist.Quantile(quantile)
|
|
|
|
quantiles[quantile] = q.CoerceToFloat64(kind)
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := prometheus.NewConstSummary(desc, uint64(count), sum.CoerceToFloat64(kind), quantiles, labels...)
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error creating constant summary: %w", err)
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ch <- m
|
2020-04-07 19:58:49 +02:00
|
|
|
return nil
|
2019-12-23 19:47:51 +02:00
|
|
|
}
|
2019-11-26 21:47:15 +02:00
|
|
|
|
2020-04-07 19:58:49 +02:00
|
|
|
func (c *collector) exportHistogram(ch chan<- prometheus.Metric, hist aggregator.Histogram, kind core.NumberKind, desc *prometheus.Desc, labels []string) error {
|
2020-04-01 23:36:37 +02:00
|
|
|
buckets, err := hist.Histogram()
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving histogram: %w", err)
|
2020-04-01 23:36:37 +02:00
|
|
|
}
|
|
|
|
sum, err := hist.Sum()
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error retrieving sum: %w", err)
|
2020-04-01 23:36:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var totalCount uint64
|
|
|
|
// counts maps from the bucket upper-bound to the cumulative count.
|
|
|
|
// The bucket with upper-bound +inf is not included.
|
|
|
|
counts := make(map[float64]uint64, len(buckets.Boundaries))
|
|
|
|
for i := range buckets.Boundaries {
|
|
|
|
boundary := buckets.Boundaries[i].CoerceToFloat64(kind)
|
|
|
|
totalCount += buckets.Counts[i].AsUint64()
|
|
|
|
counts[boundary] = totalCount
|
|
|
|
}
|
|
|
|
// Include the +inf bucket in the total count.
|
|
|
|
totalCount += buckets.Counts[len(buckets.Counts)-1].AsUint64()
|
|
|
|
|
|
|
|
m, err := prometheus.NewConstHistogram(desc, totalCount, sum.CoerceToFloat64(kind), counts, labels...)
|
|
|
|
if err != nil {
|
2020-04-13 18:48:20 +02:00
|
|
|
return fmt.Errorf("error creating constant histogram: %w", err)
|
2020-04-01 23:36:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ch <- m
|
2020-04-07 19:58:49 +02:00
|
|
|
return nil
|
2020-04-01 23:36:37 +02:00
|
|
|
}
|
|
|
|
|
2020-03-27 23:06:48 +02:00
|
|
|
func (c *collector) toDesc(record *export.Record) *prometheus.Desc {
|
|
|
|
desc := record.Descriptor()
|
|
|
|
labels := labelsKeys(record.Labels())
|
2019-12-23 19:47:51 +02:00
|
|
|
return prometheus.NewDesc(sanitize(desc.Name()), desc.Description(), labels, nil)
|
2019-11-26 21:47:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
e.handler.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
2020-04-23 21:10:58 +02:00
|
|
|
func labelsKeys(labels *label.Set) []string {
|
Replace `Ordered` with an iterator in `export.Labels`. (#567)
* Do not expose a slice of labels in export.Record
This is really an inconvenient implementation detail leak - we may
want to store labels in a different way. Replace it with an iterator -
it does not force us to use slice of key values as a storage in the
long run.
* Add Len to LabelIterator
It may come in handy in several situations, where we don't have access
to export.Labels object, but only to the label iterator.
* Use reflect value label iterator for the fixed labels
* add reset operation to iterator
Makes my life easier when writing a benchmark. Might also be an
alternative to cloning the iterator.
* Add benchmarks for iterators
* Add import comment
* Add clone operation to label iterator
* Move iterator tests to a separate package
* Add tests for cloning iterators
* Pass label iterator to export labels
* Use non-addressable array reflect values
By not using the value created by `reflect.New()`, but rather by
`reflect.ValueOf()`, we get a non-addressable array in the value,
which does not infer an allocation cost when getting an element from
the array.
* Drop zero iterator
This can be substituted by a reflect value iterator that goes over a
value with a zero-sized array.
* Add a simple iterator that implements label iterator
In the long run this will completely replace the LabelIterator
interface.
* Replace reflect value iterator with simple iterator
* Pass label storage to new export labels, not label iterator
* Drop label iterator interface, rename storage iterator to label iterator
* Drop clone operation from iterator
It's a leftover from interface times and now it's pointless - the
iterator is a simple struct, so cloning it is a simple copy.
* Drop Reset from label iterator
The sole existence of Reset was actually for benchmarking convenience.
Now we can just copy the iterator cheaply, so a need for Reset is no
more.
* Drop noop iterator tests
* Move back iterator tests to export package
* Eagerly get the reflect value of ordered labels
So we won't get into problems when several goroutines want to iterate
the same labels at the same time. Not sure if this would be a big
deal, since every goroutine would compute the same reflect.Value, but
concurrent write to the same memory is bad anyway. And it doesn't cost
us any extra allocations anyway.
* Replace NewSliceLabelIterator() with a method of LabelSlice
* Add some documentation
* Documentation fixes
2020-03-20 00:01:34 +02:00
|
|
|
iter := labels.Iter()
|
|
|
|
keys := make([]string, 0, iter.Len())
|
|
|
|
for iter.Next() {
|
|
|
|
kv := iter.Label()
|
2019-11-26 21:47:15 +02:00
|
|
|
keys = append(keys, sanitize(string(kv.Key)))
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2020-04-23 21:10:58 +02:00
|
|
|
func labelValues(labels *label.Set) []string {
|
2019-11-26 21:47:15 +02:00
|
|
|
// TODO(paivagustavo): parse the labels.Encoded() instead of calling `Emit()` directly
|
|
|
|
// this would avoid unnecessary allocations.
|
Replace `Ordered` with an iterator in `export.Labels`. (#567)
* Do not expose a slice of labels in export.Record
This is really an inconvenient implementation detail leak - we may
want to store labels in a different way. Replace it with an iterator -
it does not force us to use slice of key values as a storage in the
long run.
* Add Len to LabelIterator
It may come in handy in several situations, where we don't have access
to export.Labels object, but only to the label iterator.
* Use reflect value label iterator for the fixed labels
* add reset operation to iterator
Makes my life easier when writing a benchmark. Might also be an
alternative to cloning the iterator.
* Add benchmarks for iterators
* Add import comment
* Add clone operation to label iterator
* Move iterator tests to a separate package
* Add tests for cloning iterators
* Pass label iterator to export labels
* Use non-addressable array reflect values
By not using the value created by `reflect.New()`, but rather by
`reflect.ValueOf()`, we get a non-addressable array in the value,
which does not infer an allocation cost when getting an element from
the array.
* Drop zero iterator
This can be substituted by a reflect value iterator that goes over a
value with a zero-sized array.
* Add a simple iterator that implements label iterator
In the long run this will completely replace the LabelIterator
interface.
* Replace reflect value iterator with simple iterator
* Pass label storage to new export labels, not label iterator
* Drop label iterator interface, rename storage iterator to label iterator
* Drop clone operation from iterator
It's a leftover from interface times and now it's pointless - the
iterator is a simple struct, so cloning it is a simple copy.
* Drop Reset from label iterator
The sole existence of Reset was actually for benchmarking convenience.
Now we can just copy the iterator cheaply, so a need for Reset is no
more.
* Drop noop iterator tests
* Move back iterator tests to export package
* Eagerly get the reflect value of ordered labels
So we won't get into problems when several goroutines want to iterate
the same labels at the same time. Not sure if this would be a big
deal, since every goroutine would compute the same reflect.Value, but
concurrent write to the same memory is bad anyway. And it doesn't cost
us any extra allocations anyway.
* Replace NewSliceLabelIterator() with a method of LabelSlice
* Add some documentation
* Documentation fixes
2020-03-20 00:01:34 +02:00
|
|
|
iter := labels.Iter()
|
|
|
|
values := make([]string, 0, iter.Len())
|
|
|
|
for iter.Next() {
|
|
|
|
label := iter.Label()
|
2019-11-26 21:47:15 +02:00
|
|
|
values = append(values, label.Value.Emit())
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|