Skip to content

Commit

Permalink
Rewrite the metrics library
Browse files Browse the repository at this point in the history
Fixes: cilium#2376

Signed-off-by: Anna Kapuscinska <[email protected]>
  • Loading branch information
lambdanis committed Jun 17, 2024
1 parent dd602b0 commit 386866f
Show file tree
Hide file tree
Showing 8 changed files with 838 additions and 78 deletions.
48 changes: 31 additions & 17 deletions pkg/metrics/bpfmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,59 @@

package metrics

import "github.com/prometheus/client_golang/prometheus"
import (
"github.com/prometheus/client_golang/prometheus"
)

// The nterface in this file provides a bridge between the new metrics library
// and the existing code defining metrics. It's considered deprecated - use the
// interface from custommetric.go instead.

// BPFMetric represents a metric read directly from a BPF map.
// It's intended to be used in custom collectors. The interface doesn't provide
// any validation, so it's up to the collector implementer to guarantee the
// metrics consistency.
type BPFMetric interface {
Desc() *prometheus.Desc
MustMetric(value float64, labelValues ...string) prometheus.Metric
}

type bpfCounter struct {
desc *prometheus.Desc
metric *granularCustomCounter[NilLabels]
}

// DEPRECATED: Use NewGranularCustomCounter instead.
func NewBPFCounter(desc *prometheus.Desc) BPFMetric {
return &bpfCounter{desc: desc}
return &bpfCounter{
metric: &granularCustomCounter[NilLabels]{
desc: desc,
constrained: false,
},
}
}

func (c *bpfCounter) Desc() *prometheus.Desc {
return c.desc
func (m *bpfCounter) Desc() *prometheus.Desc {
return m.metric.Desc()
}

func (c *bpfCounter) MustMetric(value float64, labelValues ...string) prometheus.Metric {
return prometheus.MustNewConstMetric(c.desc, prometheus.CounterValue, value, labelValues...)
func (m *bpfCounter) MustMetric(value float64, labelValues ...string) prometheus.Metric {
return m.metric.MustMetric(value, &NilLabels{}, labelValues...)
}

type bpfGauge struct {
desc *prometheus.Desc
metric *granularCustomGauge[NilLabels]
}

// DEPRECATED: Use NewGranularCustomGauge instead.
func NewBPFGauge(desc *prometheus.Desc) BPFMetric {
return &bpfGauge{desc: desc}
return &bpfGauge{
metric: &granularCustomGauge[NilLabels]{
desc: desc,
constrained: false,
},
}
}

func (g *bpfGauge) Desc() *prometheus.Desc {
return g.desc
func (m *bpfGauge) Desc() *prometheus.Desc {
return m.metric.Desc()
}

func (g *bpfGauge) MustMetric(value float64, labelValues ...string) prometheus.Metric {
return prometheus.MustNewConstMetric(g.desc, prometheus.GaugeValue, value, labelValues...)
func (m *bpfGauge) MustMetric(value float64, labelValues ...string) prometheus.Metric {
return m.metric.MustMetric(value, &NilLabels{}, labelValues...)
}
67 changes: 67 additions & 0 deletions pkg/metrics/customcollector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package metrics

import (
"github.com/prometheus/client_golang/prometheus"
)

type collectFunc func(chan<- prometheus.Metric)

type customCollector[L FilteredLabels] struct {
metrics []GranularCustomMetric[L]
collectFunc collectFunc
collectForDocsFunc collectFunc
}

// NewCustomCollector creates a new customCollector.
//
// If collectForDocs is nil, the collector will use collect function for both
// regular metrics server and documentation generation.
func NewCustomCollector[L FilteredLabels](
metrics []GranularCustomMetric[L], collect collectFunc, collectForDocs collectFunc,
) CollectorWithInit {
return &customCollector[L]{
metrics: metrics,
collectFunc: collect,
collectForDocsFunc: collectForDocs,
}
}

// Describe implements CollectorWithInit (prometheus.Collector).
func (c *customCollector[L]) Describe(ch chan<- *prometheus.Desc) {
for _, m := range c.metrics {
ch <- m.Desc()
}
}

// Collect implements CollectorWithInit (prometheus.Collector).
func (c *customCollector[L]) Collect(ch chan<- prometheus.Metric) {
if c.collectFunc != nil {
c.collectFunc(ch)
}
}

// IsConstrained implements CollectorWithInit.
func (c *customCollector[L]) IsConstrained() bool {
for _, m := range c.metrics {
if !m.IsConstrained() {
return false
}
}
return true
}

// Init implements CollectorWithInit.
func (c *customCollector[L]) Init() {
// since metrics are collected independently, there's nothing to initialize
}

// InitForDocs implements CollectorWithInit.
func (c *customCollector[L]) InitForDocs() {
// override Collect method if there's a special one for docs
if c.collectForDocsFunc != nil {
c.collectFunc = c.collectForDocsFunc
}
}
120 changes: 120 additions & 0 deletions pkg/metrics/custommetric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package metrics

import (
"github.com/prometheus/client_golang/prometheus"
)

// GranularCustomMetric represents a metric collected independently of
// prometheus package, for example in a BPF map. It's intended to be used in
// a custom collector (see customcollector.go). The interface doesn't provide
// any validation, so it's up to the collector implementer to guarantee the
// metrics consistency.
type GranularCustomMetric[L FilteredLabels] interface {
Desc() *prometheus.Desc
MustMetric(value float64, commonLvs *L, extraLvs ...string) prometheus.Metric
IsConstrained() bool
}

// getDesc is a helper function to retrieve the descriptor for a metric and
// check if the metric is constrained.
//
// See getVariableLabels for the labels order.
func getDesc[L FilteredLabels](opts *MetricOpts) (*prometheus.Desc, bool, error) {
labels, constrained, err := getVariableLabels[L](opts)
if err != nil {
return nil, false, err
}

desc := prometheus.NewDesc(
prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help,
labels,
opts.ConstLabels,
)
return desc, constrained, nil
}

// counter

type granularCustomCounter[L FilteredLabels] struct {
desc *prometheus.Desc
constrained bool
}

// NewGranularCustomCounter creates a new granularCustomCounter.
func NewGranularCustomCounter[L FilteredLabels](opts MetricOpts) (GranularCustomMetric[L], error) {
desc, constrained, err := getDesc[L](&opts)
if err != nil {
return nil, err
}

return &granularCustomCounter[L]{
desc: desc,
constrained: constrained,
}, nil
}

// NewCustomCounter creates a new granularCustomCounter with no configurable labels.
func NewCustomCounter(opts MetricOpts) (GranularCustomMetric[NilLabels], error) {
return NewGranularCustomCounter[NilLabels](opts)
}

// Desc implements GranularCustomMetric.
func (c *granularCustomCounter[L]) Desc() *prometheus.Desc {
return c.desc
}

// MustMetric implements GranularCustomMetric.
func (m *granularCustomCounter[L]) MustMetric(value float64, commonLvs *L, extraLvs ...string) prometheus.Metric {
lvs := append((*commonLvs).Values(), extraLvs...)
return prometheus.MustNewConstMetric(m.desc, prometheus.CounterValue, value, lvs...)
}

// IsConstrained implements GranularCustomMetric.
func (m *granularCustomCounter[L]) IsConstrained() bool {
return m.constrained
}

// gauge

type granularCustomGauge[L FilteredLabels] struct {
desc *prometheus.Desc
constrained bool
}

// NewGranularCustomGauge creates a new granularCustomGauge.
func NewGranularCustomGauge[L FilteredLabels](opts MetricOpts) (GranularCustomMetric[L], error) {
desc, constrained, err := getDesc[L](&opts)
if err != nil {
return nil, err
}

return &granularCustomGauge[L]{
desc: desc,
constrained: constrained,
}, nil
}

// NewCustomGauge creates a new granularCustomGauge with no configurable labels.
func NewCustomGauge(opts MetricOpts) (GranularCustomMetric[NilLabels], error) {
return NewGranularCustomGauge[NilLabels](opts)
}

// Desc implements GranularCustomMetric.
func (c *granularCustomGauge[L]) Desc() *prometheus.Desc {
return c.desc
}

// MustMetric implements GranularCustomMetric.
func (m *granularCustomGauge[L]) MustMetric(value float64, commonLvs *L, extraLvs ...string) prometheus.Metric {
lvs := append((*commonLvs).Values(), extraLvs...)
return prometheus.MustNewConstMetric(m.desc, prometheus.GaugeValue, value, lvs...)
}

// IsConstrained implements GranularCustomMetric.
func (m *granularCustomGauge[L]) IsConstrained() bool {
return m.constrained
}
56 changes: 56 additions & 0 deletions pkg/metrics/filteredlabels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package metrics

import (
"github.com/cilium/tetragon/pkg/metrics/consts"
)

type FilteredLabels interface {
Keys() []string
Values() []string
}

// FilteredLabelsWithExamples extends FilteredLabels with a method returning
// example label values, intended to be used when generating documentation.
type FilteredLabelsWithExamples interface {
FilteredLabels
ExampleValues() []string
}

type NilLabels struct{}

func (l NilLabels) Keys() []string { return []string{} }

func (l NilLabels) Values() []string { return []string{} }

func (l NilLabels) ExampleValues() []string { return []string{} }

type ProcessLabels struct {
Namespace string
Workload string
Pod string
Binary string
}

func NewProcessLabels(namespace, workload, pod, binary string) *ProcessLabels {
return &ProcessLabels{
Namespace: namespace,
Workload: workload,
Pod: pod,
Binary: binary,
}
}

func (l ProcessLabels) Keys() []string {
return []string{"namespace", "workload", "pod", "binary"}
}

func (l ProcessLabels) Values() []string {
return []string{l.Namespace, l.Workload, l.Pod, l.Binary}
}

func (l ProcessLabels) ExampleValues() []string {
return []string{consts.ExampleNamespace, consts.ExampleWorkload, consts.ExamplePod, consts.ExampleBinary}
}
Loading

0 comments on commit 386866f

Please sign in to comment.