Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion central/metrics/custom/image_vulnerabilities/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

var (
lazyLabels = []tracker.LazyLabel[finding]{
lazyLabels = []tracker.LazyLabel[*finding]{
{Label: "Cluster", Getter: func(f *finding) string { return f.deployment.GetClusterName() }},
}

Expand All @@ -17,9 +17,12 @@ var (
// The aggregator calls the lazy label's Getter function with every finding to
// compute the values for the list of defined labels.
type finding struct {
err error
deployment *storage.Deployment
}

func (f *finding) GetError() error { return f.err }

func ValidateConfiguration(config map[string]*storage.PrometheusMetrics_Group_Labels) error {
_, err := tracker.TranslateConfiguration(config, labels)
return err
Expand Down
4 changes: 2 additions & 2 deletions central/metrics/custom/tracker/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ type aggregatedRecord struct {
// {"Y": {labels: {L2="Y"}, total: 1}},
// {"Z": {labels: {L2="Z"}, total: 2}}
// }
type aggregator[Finding any] struct {
type aggregator[Finding WithError] struct {
result map[MetricName]map[aggregationKey]*aggregatedRecord
mcfg MetricsConfiguration
labelOrder map[Label]int
getters map[Label]func(Finding) string
}

func makeAggregator[Finding any](mcfg MetricsConfiguration, labelOrder map[Label]int, getters map[Label]func(Finding) string) *aggregator[Finding] {
func makeAggregator[Finding WithError](mcfg MetricsConfiguration, labelOrder map[Label]int, getters map[Label]func(Finding) string) *aggregator[Finding] {
aggregated := make(map[MetricName]map[aggregationKey]*aggregatedRecord)
for metric := range mcfg {
aggregated[metric] = make(map[aggregationKey]*aggregatedRecord)
Expand Down
30 changes: 30 additions & 0 deletions central/metrics/custom/tracker/configuration.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
package tracker

import (
"slices"
"time"
)

type Label string // Prometheus label.
type MetricName string // Prometheus metric name.

// MetricsConfiguration is the parsed aggregation configuration.
type MetricsConfiguration map[MetricName][]Label

// diff computes the difference between one instance of MetricsConfiguration and
// another. The result serves for runtime updates.
func (mcfg MetricsConfiguration) diff(another MetricsConfiguration) (toAdd []MetricName, toDelete []MetricName, changed []MetricName) {
for metricName, labels := range mcfg {
if anotherLabels, ok := another[metricName]; !ok {
toDelete = append(toDelete, metricName)
} else if !slices.Equal(labels, anotherLabels) {
changed = append(changed, metricName)
}
}
for metricName := range another {
if _, ok := mcfg[metricName]; !ok {
toAdd = append(toAdd, metricName)
}
}
return toAdd, toDelete, changed
}

type Configuration struct {
metrics MetricsConfiguration
toAdd []MetricName
toDelete []MetricName
period time.Duration
}
98 changes: 95 additions & 3 deletions central/metrics/custom/tracker/testing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ import (
"testing"

"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/errox"
"github.com/stretchr/testify/assert"
)

// The test tracker finds some integers to track.
type testFinding int

func (f testFinding) GetError() error {
if f == 0xbadf00d {
return errox.InvariantViolation.CausedByf("bad finding %v", f)
} else {
return nil
}
}

var testLabelGetters = []LazyLabel[testFinding]{
testLabel("test"),
testLabel("Cluster"),
Expand All @@ -25,7 +35,7 @@ var testLabelOrder = MakeLabelOrderMap(testLabelGetters)
func testLabel(label Label) LazyLabel[testFinding] {
return LazyLabel[testFinding]{
label,
func(i *testFinding) string { return testData[*i][label] }}
func(i testFinding) string { return testData[i][label] }}
}

var testData = []map[Label]string{
Expand Down Expand Up @@ -58,15 +68,97 @@ var testData = []map[Label]string{
func makeTestMetricLabels(t *testing.T) map[string]*storage.PrometheusMetrics_Group_Labels {
pfx := strings.ReplaceAll(t.Name(), "/", "_")
return map[string]*storage.PrometheusMetrics_Group_Labels{
pfx + "_metric1": {Labels: []string{"Severity", "Cluster"}},
pfx + "_metric1": {Labels: []string{"Cluster", "Severity"}},
pfx + "_metric2": {Labels: []string{"Namespace"}},
}
}

func makeTestMetricConfiguration(t *testing.T) MetricsConfiguration {
pfx := MetricName(strings.ReplaceAll(t.Name(), "/", "_"))
return MetricsConfiguration{
pfx + "_metric1": {"Severity", "Cluster"},
pfx + "_metric1": {"Cluster", "Severity"},
pfx + "_metric2": {"Namespace"},
}
}

func TestMetricsConfiguration_diff(t *testing.T) {
tests := []struct {
name string
a, b MetricsConfiguration
wantToAdd []MetricName
wantToDelete []MetricName
wantChanged []MetricName
}{
{
name: "both nil",
a: nil,
b: nil,
wantToAdd: nil,
wantToDelete: nil,
},
{
name: "a empty, b has one",
a: MetricsConfiguration{},
b: MetricsConfiguration{"metric1": {"label1"}},
wantToAdd: []MetricName{"metric1"},
wantToDelete: nil,
},
{
name: "a has one, b empty",
a: MetricsConfiguration{"metric1": {"label1"}},
b: MetricsConfiguration{},
wantToAdd: nil,
wantToDelete: []MetricName{"metric1"},
},
{
name: "a has one, b has another",
a: MetricsConfiguration{"metric1": {"label1"}},
b: MetricsConfiguration{"metric2": {"label2"}},
wantToAdd: []MetricName{"metric2"},
wantToDelete: []MetricName{"metric1"},
},
{
name: "a and b have overlap",
a: MetricsConfiguration{
"metric1": {"label1"},
"metric2": {"label2"},
},
b: MetricsConfiguration{
"metric2": {"label2"},
"metric3": {"label3"},
},
wantToAdd: []MetricName{"metric3"},
wantToDelete: []MetricName{"metric1"},
},
{
name: "identical",
a: MetricsConfiguration{
"metric1": {"label1"},
"metric2": {"label2"},
},
b: MetricsConfiguration{
"metric1": {"label1"},
"metric2": {"label2"},
},
wantToAdd: nil,
wantToDelete: nil,
},
{
name: "a has changed",
a: MetricsConfiguration{"metric1": {"label1"}, "metric2": {"label1"}},
b: MetricsConfiguration{"metric1": {"label2"}, "metric3": {"label1"}},
wantToAdd: []MetricName{"metric3"},
wantToDelete: []MetricName{"metric2"},
wantChanged: []MetricName{"metric1"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotToAdd, gotToDelete, gotChanged := tt.a.diff(tt.b)
assert.ElementsMatch(t, tt.wantToAdd, gotToAdd)
assert.ElementsMatch(t, tt.wantToDelete, gotToDelete)
assert.ElementsMatch(t, tt.wantChanged, gotChanged)
})
}
}
Loading
Loading