github.com/waldiirawan/apm-agent-go/v2@v2.2.2/metrics.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apm // import "github.com/waldiirawan/apm-agent-go/v2"
    19  
    20  import (
    21  	"context"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/waldiirawan/apm-agent-go/v2/internal/wildcard"
    27  	"github.com/waldiirawan/apm-agent-go/v2/model"
    28  )
    29  
    30  // Metrics holds a set of metrics.
    31  type Metrics struct {
    32  	disabled wildcard.Matchers
    33  
    34  	mu      sync.Mutex
    35  	metrics []*model.Metrics
    36  
    37  	// transactionGroupMetrics holds metrics which are scoped to transaction
    38  	// groups, and are not sorted according to their labels.
    39  	transactionGroupMetrics []*model.Metrics
    40  }
    41  
    42  func (m *Metrics) reset() {
    43  	m.metrics = m.metrics[:0]
    44  	m.transactionGroupMetrics = m.transactionGroupMetrics[:0]
    45  }
    46  
    47  // MetricLabel is a name/value pair for labeling metrics.
    48  type MetricLabel struct {
    49  	// Name is the label name.
    50  	Name string
    51  
    52  	// Value is the label value.
    53  	Value string
    54  }
    55  
    56  // MetricsGatherer provides an interface for gathering metrics.
    57  type MetricsGatherer interface {
    58  	// GatherMetrics gathers metrics and adds them to m.
    59  	//
    60  	// If ctx.Done() is signaled, gathering should be aborted and
    61  	// ctx.Err() returned. If GatherMetrics returns an error, it
    62  	// will be logged, but otherwise there is no effect; the
    63  	// implementation must take care not to leave m in an invalid
    64  	// state due to errors.
    65  	GatherMetrics(ctx context.Context, m *Metrics) error
    66  }
    67  
    68  // GatherMetricsFunc is a function type implementing MetricsGatherer.
    69  type GatherMetricsFunc func(context.Context, *Metrics) error
    70  
    71  // GatherMetrics calls f(ctx, m).
    72  func (f GatherMetricsFunc) GatherMetrics(ctx context.Context, m *Metrics) error {
    73  	return f(ctx, m)
    74  }
    75  
    76  // Add adds a metric with the given name, labels, and value,
    77  // The labels are expected to be sorted lexicographically.
    78  func (m *Metrics) Add(name string, labels []MetricLabel, value float64) {
    79  	m.addMetric(name, labels, model.Metric{Value: value})
    80  }
    81  
    82  // AddHistogram adds a histogram metric with the given name, labels, counts,
    83  // and values. The labels are expected to be sorted lexicographically, and
    84  // bucket values provided in ascending order.
    85  func (m *Metrics) AddHistogram(name string, labels []MetricLabel, values []float64, counts []uint64) {
    86  	m.addMetric(name, labels, model.Metric{Values: values, Counts: counts, Type: "histogram"})
    87  }
    88  
    89  func (m *Metrics) addMetric(name string, labels []MetricLabel, metric model.Metric) {
    90  	if m.disabled.MatchAny(name) {
    91  		return
    92  	}
    93  	m.mu.Lock()
    94  	defer m.mu.Unlock()
    95  
    96  	var metrics *model.Metrics
    97  	results := make([]int, len(m.metrics))
    98  	i := sort.Search(len(m.metrics), func(j int) bool {
    99  		results[j] = compareLabels(m.metrics[j].Labels, labels)
   100  		return results[j] >= 0
   101  	})
   102  	if i < len(results) && results[i] == 0 {
   103  		// labels are equal
   104  		metrics = m.metrics[i]
   105  	} else {
   106  		var modelLabels model.StringMap
   107  		if len(labels) > 0 {
   108  			modelLabels = make(model.StringMap, len(labels))
   109  			for i, l := range labels {
   110  				modelLabels[i] = model.StringMapItem{
   111  					Key: l.Name, Value: l.Value,
   112  				}
   113  			}
   114  		}
   115  		metrics = &model.Metrics{
   116  			Labels:  modelLabels,
   117  			Samples: make(map[string]model.Metric),
   118  		}
   119  		if i == len(results) {
   120  			m.metrics = append(m.metrics, metrics)
   121  		} else {
   122  			m.metrics = append(m.metrics, nil)
   123  			copy(m.metrics[i+1:], m.metrics[i:])
   124  			m.metrics[i] = metrics
   125  		}
   126  	}
   127  	metrics.Samples[name] = metric
   128  }
   129  
   130  func compareLabels(a model.StringMap, b []MetricLabel) int {
   131  	na, nb := len(a), len(b)
   132  	n := na
   133  	if na > nb {
   134  		n = nb
   135  	}
   136  	for i := 0; i < n; i++ {
   137  		la, lb := a[i], b[i]
   138  		d := strings.Compare(la.Key, lb.Name)
   139  		if d == 0 {
   140  			d = strings.Compare(la.Value, lb.Value)
   141  		}
   142  		if d != 0 {
   143  			return d
   144  		}
   145  	}
   146  	switch {
   147  	case na < nb:
   148  		return -1
   149  	case na > nb:
   150  		return 1
   151  	}
   152  	return 0
   153  }
   154  
   155  func gatherMetrics(ctx context.Context, g MetricsGatherer, m *Metrics, logger Logger) {
   156  	defer func() {
   157  		if r := recover(); r != nil {
   158  			if logger != nil {
   159  				logger.Debugf("%T.GatherMetrics panicked: %s", g, r)
   160  			}
   161  		}
   162  	}()
   163  	if err := g.GatherMetrics(ctx, m); err != nil {
   164  		if logger != nil && err != context.Canceled {
   165  			logger.Debugf("%T.GatherMetrics failed: %s", g, err)
   166  		}
   167  	}
   168  }