metrics.go (112 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 apm // import "go.elastic.co/apm/v2"
import (
"context"
"sort"
"strings"
"sync"
"go.elastic.co/apm/v2/internal/wildcard"
"go.elastic.co/apm/v2/model"
)
// Metrics holds a set of metrics.
type Metrics struct {
disabled wildcard.Matchers
mu sync.Mutex
metrics []*model.Metrics
// transactionGroupMetrics holds metrics which are scoped to transaction
// groups, and are not sorted according to their labels.
transactionGroupMetrics []*model.Metrics
}
func (m *Metrics) reset() {
m.metrics = m.metrics[:0]
m.transactionGroupMetrics = m.transactionGroupMetrics[:0]
}
// MetricLabel is a name/value pair for labeling metrics.
type MetricLabel struct {
// Name is the label name.
Name string
// Value is the label value.
Value string
}
// MetricsGatherer provides an interface for gathering metrics.
type MetricsGatherer interface {
// GatherMetrics gathers metrics and adds them to m.
//
// If ctx.Done() is signaled, gathering should be aborted and
// ctx.Err() returned. If GatherMetrics returns an error, it
// will be logged, but otherwise there is no effect; the
// implementation must take care not to leave m in an invalid
// state due to errors.
GatherMetrics(ctx context.Context, m *Metrics) error
}
// GatherMetricsFunc is a function type implementing MetricsGatherer.
type GatherMetricsFunc func(context.Context, *Metrics) error
// GatherMetrics calls f(ctx, m).
func (f GatherMetricsFunc) GatherMetrics(ctx context.Context, m *Metrics) error {
return f(ctx, m)
}
// Add adds a metric with the given name, labels, and value,
// The labels are expected to be sorted lexicographically.
func (m *Metrics) Add(name string, labels []MetricLabel, value float64) {
m.addMetric(name, labels, model.Metric{Value: value})
}
// AddHistogram adds a histogram metric with the given name, labels, counts,
// and values. The labels are expected to be sorted lexicographically, and
// bucket values provided in ascending order.
func (m *Metrics) AddHistogram(name string, labels []MetricLabel, values []float64, counts []uint64) {
m.addMetric(name, labels, model.Metric{Values: values, Counts: counts, Type: "histogram"})
}
func (m *Metrics) addMetric(name string, labels []MetricLabel, metric model.Metric) {
if m.disabled.MatchAny(name) {
return
}
m.mu.Lock()
defer m.mu.Unlock()
var metrics *model.Metrics
results := make([]int, len(m.metrics))
i := sort.Search(len(m.metrics), func(j int) bool {
results[j] = compareLabels(m.metrics[j].Labels, labels)
return results[j] >= 0
})
if i < len(results) && results[i] == 0 {
// labels are equal
metrics = m.metrics[i]
} else {
var modelLabels model.StringMap
if len(labels) > 0 {
modelLabels = make(model.StringMap, len(labels))
for i, l := range labels {
modelLabels[i] = model.StringMapItem{
Key: l.Name, Value: l.Value,
}
}
}
metrics = &model.Metrics{
Labels: modelLabels,
Samples: make(map[string]model.Metric),
}
if i == len(results) {
m.metrics = append(m.metrics, metrics)
} else {
m.metrics = append(m.metrics, nil)
copy(m.metrics[i+1:], m.metrics[i:])
m.metrics[i] = metrics
}
}
metrics.Samples[name] = metric
}
func compareLabels(a model.StringMap, b []MetricLabel) int {
na, nb := len(a), len(b)
n := na
if na > nb {
n = nb
}
for i := 0; i < n; i++ {
la, lb := a[i], b[i]
d := strings.Compare(la.Key, lb.Name)
if d == 0 {
d = strings.Compare(la.Value, lb.Value)
}
if d != 0 {
return d
}
}
switch {
case na < nb:
return -1
case na > nb:
return 1
}
return 0
}
func gatherMetrics(ctx context.Context, g MetricsGatherer, m *Metrics, logger Logger) {
defer func() {
if r := recover(); r != nil {
if logger != nil {
logger.Debugf("%T.GatherMetrics panicked: %s", g, r)
}
}
}()
if err := g.GatherMetrics(ctx, m); err != nil {
if logger != nil && err != context.Canceled {
logger.Debugf("%T.GatherMetrics failed: %s", g, err)
}
}
}