forked from cilium/tetragon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes cilium#2376 Signed-off-by: Anna Kapuscinska <[email protected]>
- Loading branch information
Showing
7 changed files
with
532 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// 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 | ||
} | ||
|
||
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() { | ||
// no-op | ||
} | ||
|
||
// InitForDocs implements CollectorWithInit. | ||
func (c *customCollector[L]) InitForDocs() { | ||
// override Collect method | ||
c.collectFunc = c.collectForDocsFunc | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Tetragon | ||
|
||
package metrics | ||
|
||
import ( | ||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
type GranularCustomMetric[L FilteredLabels] interface { | ||
Desc() *prometheus.Desc | ||
MustMetric(value float64, commonLvs *L, extraLvs ...string) prometheus.Metric | ||
IsConstrained() bool | ||
} | ||
|
||
type granularCustomCounter[L FilteredLabels] struct { | ||
desc *prometheus.Desc | ||
constrained bool | ||
} | ||
|
||
func NewGranularCustomCounter[L FilteredLabels](opts MetricOpts) (GranularCustomMetric[L], error) { | ||
labels, constrained, err := getVariableLabels[L](&opts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
desc := prometheus.NewDesc( | ||
prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), | ||
opts.Help, | ||
labels, | ||
opts.ConstLabels, | ||
) | ||
|
||
return &granularCustomCounter[L]{ | ||
desc: desc, | ||
constrained: constrained, | ||
}, nil | ||
} | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Tetragon | ||
|
||
package metrics | ||
|
||
type FilteredLabels interface { | ||
Keys() []string | ||
Values() []string | ||
} | ||
|
||
type NilLabels struct{} | ||
|
||
func (l NilLabels) Keys() []string { return []string{} } | ||
|
||
func (l NilLabels) Values() []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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Tetragon | ||
|
||
package metrics | ||
|
||
import ( | ||
"slices" | ||
|
||
"github.com/cilium/tetragon/pkg/metrics" | ||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
type initMetricFunc func(...string) | ||
|
||
// initAllCombinations initializes a metric with all possible combinations of label values. | ||
func initAllCombinations(initMetric initMetricFunc, labels []ConstrainedLabel) { | ||
initCombinations(initMetric, labels, make([]string, len(labels)), 0) | ||
} | ||
|
||
// initCombinations is a helper function that recursively initializes a metric with possible combinations of label | ||
// values. There are a few assumptions about the arguments: | ||
// - initMetric is not nil | ||
// - labels and lvs have the same length | ||
// - cursor is in the range [0, len(labels)] | ||
// If any of these is not met, the function will silently return. | ||
func initCombinations(initMetric initMetricFunc, labels []ConstrainedLabel, lvs []string, cursor int) { | ||
if initMetric == nil || len(labels) != len(lvs) || cursor < 0 || cursor > len(labels) { | ||
// The function was called with invalid arguments. Silently return. | ||
return | ||
} | ||
if cursor == len(labels) { | ||
initMetric(lvs...) | ||
return | ||
} | ||
for _, val := range labels[cursor].Values { | ||
lvs[cursor] = val | ||
initCombinations(initMetric, labels, lvs, cursor+1) | ||
} | ||
} | ||
|
||
// initForDocs initializes the metric for the purpose of generating documentation. | ||
// TODO: we need filtered labels here too | ||
func initForDocs(initMetric initMetricFunc, constrained []ConstrainedLabel, unconstrained []UnconstrainedLabel) { | ||
lvs := make([]string, len(constrained)+len(unconstrained)) | ||
for i := range constrained { | ||
for _, val := range constrained[i].Values { | ||
lvs[i] = val | ||
initMetric(lvs...) | ||
} | ||
lvs[i] = "" | ||
} | ||
for i := range unconstrained { | ||
lvs[len(constrained)+i] = unconstrained[i].ExampleValue | ||
initMetric(lvs...) | ||
lvs[len(constrained)+i] = "" | ||
} | ||
} | ||
|
||
// GranularCounter wraps prometheus.CounterVec and implements CollectorWithInit. | ||
type GranularCounter[L FilteredLabels] struct { | ||
metric *prometheus.CounterVec | ||
constrained bool | ||
initFunc func() | ||
initForDocs func() | ||
} | ||
|
||
// NewGranularCounter creates a new GranularCounter. | ||
// | ||
// The init argument is a function that initializes the metric with some label values. Doing so allows us to keep | ||
// resources usage predictable. If the metric is constrained (i.e. type parameter is NilLabels and there are no | ||
// unconstrained labels) and init is nil, then the metric will be initialized with all possible combinations of labels. | ||
// If the metric is unconstrained, it won't be initialized by default. | ||
// | ||
// Pass an init function if: | ||
// a) metric is constrained but not all combinations of labels make sense (e.g. there is a hierarchy between labels or | ||
// two labels represent the same thing in different formats, or two labels are mutually exclusive, or ...) | ||
// b) metric is unconstrained, but some of the unconstrained label values are known beforehand, so can be initialized. | ||
func NewGranularCounter[L FilteredLabels](opts MetricOpts, init func()) (*GranularCounter[L], error) { | ||
labels, constrained, err := getVariableLabels[L](&opts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
promOpts := prometheus.CounterOpts{ | ||
Namespace: opts.Namespace, | ||
Subsystem: opts.Subsystem, | ||
Name: opts.Name, | ||
Help: opts.Help, | ||
ConstLabels: opts.ConstLabels, | ||
} | ||
var metric *prometheus.CounterVec | ||
if slices.Contains(labels, "pod") && slices.Contains(labels, "namespace") { | ||
// set up metric to be deleted when a pod is deleted | ||
metric = metrics.NewCounterVecWithPod(promOpts, labels) | ||
} else { | ||
metric = prometheus.NewCounterVec(promOpts, labels) | ||
} | ||
|
||
initMetric := func(lvs ...string) { | ||
metric.WithLabelValues(lvs...).Add(0) | ||
} | ||
|
||
// if metric is constrained, default to initializing all combinations of labels | ||
if constrained && init == nil { | ||
init = func() { | ||
initAllCombinations(initMetric, opts.ConstrainedLabels) | ||
} | ||
} | ||
|
||
return &GranularCounter[L]{ | ||
metric: metric, | ||
constrained: constrained, | ||
initFunc: init, | ||
initForDocs: func() { | ||
initForDocs(initMetric, opts.ConstrainedLabels, opts.UnconstrainedLabels) | ||
}, | ||
}, nil | ||
} | ||
|
||
// Describe implements CollectorWithInit (prometheus.Collector). | ||
func (m *GranularCounter[L]) Describe(ch chan<- *prometheus.Desc) { | ||
m.metric.Describe(ch) | ||
} | ||
|
||
// Collect implements CollectorWithInit (prometheus.Collector). | ||
func (m *GranularCounter[L]) Collect(ch chan<- prometheus.Metric) { | ||
m.metric.Collect(ch) | ||
} | ||
|
||
// IsConstrained implements CollectorWithInit. | ||
func (m *GranularCounter[L]) IsConstrained() bool { | ||
return m.constrained | ||
} | ||
|
||
// Init implements CollectorWithInit. | ||
func (m *GranularCounter[L]) Init() { | ||
if m.initFunc != nil { | ||
m.initFunc() | ||
} | ||
} | ||
|
||
// InitForDocs implements CollectorWithInit. | ||
func (m *GranularCounter[L]) InitForDocs() { | ||
if m.initForDocs != nil { | ||
m.initForDocs() | ||
} | ||
} | ||
|
||
// WithLabelValues is similar to WithLabelValues method from prometheus package, but takes generic FilteredLabels as | ||
// the first argument. The following arguments are values of first constrained labels, then unconstrained labels. | ||
func (m *GranularCounter[L]) WithLabelValues(commonLvs *L, extraLvs ...string) prometheus.Counter { | ||
lvs := append((*commonLvs).Values(), extraLvs...) | ||
return m.metric.WithLabelValues(lvs...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Authors of Tetragon | ||
|
||
package metrics | ||
|
||
// ConstrainedLabel represents a label with constrained cardinality. | ||
// Values is a list of all possible values of the label. | ||
type ConstrainedLabel struct { | ||
Name string | ||
Values []string | ||
} | ||
|
||
// UnconstrainedLabel represents a label with unconstrained cardinality. | ||
// ExampleValue is an example value of the label used for documentation. | ||
type UnconstrainedLabel struct { | ||
Name string | ||
ExampleValue string | ||
} |
Oops, something went wrong.