Skip to content

Commit

Permalink
Extend metrics library
Browse files Browse the repository at this point in the history
Details of the goals and implementation of the metrics library are documented
in pkg/metrics/doc.go and in the code doc comments.

Fixes: cilium#2376

Signed-off-by: Anna Kapuscinska <[email protected]>
  • Loading branch information
lambdanis committed Jun 21, 2024
1 parent dd602b0 commit 3f6b4de
Show file tree
Hide file tree
Showing 7 changed files with 844 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 interface 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 generating documentation.
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 separate one for docs
if c.collectForDocsFunc != nil {
c.collectFunc = c.collectForDocsFunc
}
}
152 changes: 152 additions & 0 deletions pkg/metrics/custommetric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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
}

// MustNewGranularCustomCounter is a convenience function that wraps
// NewGranularCustomCounter and panics on error.
func MustNewGranularCustomCounter[L FilteredLabels](opts MetricOpts) GranularCustomMetric[L] {
m, err := NewGranularCustomCounter[L](opts)
if err != nil {
panic(err)
}
return m
}

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

// MustNewCustomCounter is a convenience function that wraps NewCustomCounter
// and panics on error.
func MustNewCustomCounter(opts MetricOpts) GranularCustomMetric[NilLabels] {
return MustNewGranularCustomCounter[NilLabels](opts)
}

// Desc implements GranularCustomMetric.
func (m *granularCustomCounter[L]) Desc() *prometheus.Desc {
return m.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
}

// MustNewGranularCustomGauge is a convenience function that wraps
// NewGranularCustomGauge and panics on error.
func MustNewGranularCustomGauge[L FilteredLabels](opts MetricOpts) GranularCustomMetric[L] {
m, err := NewGranularCustomGauge[L](opts)
if err != nil {
panic(err)
}
return m
}

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

// MustNewCustomGauge is a convenience function that wraps NewCustomGauge
// and panics on error.
func MustNewCustomGauge(opts MetricOpts) GranularCustomMetric[NilLabels] {
return MustNewGranularCustomGauge[NilLabels](opts)
}

// Desc implements GranularCustomMetric.
func (m *granularCustomGauge[L]) Desc() *prometheus.Desc {
return m.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 3f6b4de

Please sign in to comment.