Skip to content

Commit 3afac1c

Browse files
authored
feat: Metric multi dimensions (#107)
* add support for multi-dimensional metrics * fix func * preallocate array
1 parent 1fab70e commit 3afac1c

File tree

1 file changed

+53
-48
lines changed

1 file changed

+53
-48
lines changed

internal/orchestrator/metrichandler.go

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"strconv"
77
"strings"
88

9-
"github.com/samber/lo"
109
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1110
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1211
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -94,41 +93,43 @@ func (h *MetricHandler) projectionsMonitor(ctx context.Context, list *unstructur
9493
// Add base dimensions only if they have a non-empty value
9594
h.setDataPointBaseDimensions(dataPoint)
9695

97-
// Add projected dimensions for this specific group
98-
for _, pField := range group {
99-
// Add projected dimension only if the value is non-empty and no error occurred
100-
if pField.error == nil && pField.value != "" {
101-
dataPoint.AddDimension(pField.name, pField.value)
102-
} else {
103-
// Optionally log or handle projection errors
104-
recordErrors = append(recordErrors, fmt.Errorf("projection error for %s: %w", pField.name, pField.error))
96+
for _, inGroup := range group {
97+
for _, pField := range inGroup {
98+
// Add projected dimension only if the value is non-empty and no error occurred
99+
if pField.error == nil && pField.value != "" {
100+
dataPoint.AddDimension(pField.name, pField.value)
101+
} else {
102+
// Optionally log or handle projection errors
103+
recordErrors = append(recordErrors, fmt.Errorf("projection error for %s: %w", pField.name, pField.error))
104+
}
105105
}
106+
107+
dataPoints = append(dataPoints, dataPoint)
106108
}
107-
dataPoints = append(dataPoints, dataPoint)
108-
}
109109

110-
// Record all collected data points
111-
errRecord := h.gaugeMetric.RecordMetrics(ctx, dataPoints...)
112-
if errRecord != nil {
113-
recordErrors = append(recordErrors, errRecord)
114-
}
110+
// Record all collected data points
111+
errRecord := h.gaugeMetric.RecordMetrics(ctx, dataPoints...)
112+
if errRecord != nil {
113+
recordErrors = append(recordErrors, errRecord)
114+
}
115115

116-
// Update result based on errors during projection or recording
117-
if len(recordErrors) > 0 {
118-
// Combine errors for reporting
119-
combinedError := fmt.Errorf("errors during metric recording: %v", recordErrors)
120-
result.Error = combinedError
121-
result.Phase = v1alpha1.PhaseFailed
122-
result.Reason = "RecordMetricFailed"
123-
result.Message = fmt.Sprintf("failed to record metric value(s): %s", combinedError.Error())
124-
} else {
125-
result.Phase = v1alpha1.PhaseActive
126-
result.Reason = v1alpha1.ReasonMonitoringActive
127-
result.Message = fmt.Sprintf("metric values recorded for resource '%s'", h.metric.GvkToString())
128-
// Observation might need adjustment depending on how results should be represented in status
129-
result.Observation = &v1alpha1.MetricObservation{Timestamp: metav1.Now(), LatestValue: strconv.Itoa(len(list.Items))} // Report total count for now
130-
}
131-
// Return the result, error indicates failure in Monitor execution, not necessarily metric export failure (handled by controller)
116+
// Update result based on errors during projection or recording
117+
if len(recordErrors) > 0 {
118+
// Combine errors for reporting
119+
combinedError := fmt.Errorf("errors during metric recording: %v", recordErrors)
120+
result.Error = combinedError
121+
result.Phase = v1alpha1.PhaseFailed
122+
result.Reason = "RecordMetricFailed"
123+
result.Message = fmt.Sprintf("failed to record metric value(s): %s", combinedError.Error())
124+
} else {
125+
result.Phase = v1alpha1.PhaseActive
126+
result.Reason = v1alpha1.ReasonMonitoringActive
127+
result.Message = fmt.Sprintf("metric values recorded for resource '%s'", h.metric.GvkToString())
128+
// Observation might need adjustment depending on how results should be represented in status
129+
result.Observation = &v1alpha1.MetricObservation{Timestamp: metav1.Now(), LatestValue: strconv.Itoa(len(list.Items))} // Report total count for now
130+
}
131+
// Return the result, error indicates failure in Monitor execution, not necessarily metric export failure (handled by controller)
132+
}
132133
return result, nil
133134
}
134135

@@ -158,27 +159,31 @@ func (e *projectedField) GetID() string {
158159
return fmt.Sprintf("%s: %s", e.name, e.value)
159160
}
160161

161-
func (h *MetricHandler) extractProjectionGroupsFrom(list *unstructured.UnstructuredList) map[string][]projectedField {
162-
// note: for now we only allow one projection, so we can use the first one
163-
// the reason for this is that if we have multiple projections, we need to create a cartesian product of all projections
164-
// this is to be done at a later time
165-
var collection []projectedField
162+
func (h *MetricHandler) extractProjectionGroupsFrom(list *unstructured.UnstructuredList) map[string][][]projectedField {
163+
collection := make([][]projectedField, 0, len(list.Items))
166164

167165
for _, obj := range list.Items {
168-
169-
projection := lo.FirstOr(h.metric.Spec.Projections, v1alpha1.Projection{})
170-
171-
if projection.Name != "" && projection.FieldPath != "" {
172-
name := projection.Name
173-
value, found, err := nestedPrimitiveValue(obj, projection.FieldPath)
174-
collection = append(collection, projectedField{name: name, value: value, found: found, error: err})
166+
var fields []projectedField
167+
for _, projection := range h.metric.Spec.Projections {
168+
if projection.Name != "" && projection.FieldPath != "" {
169+
name := projection.Name
170+
value, found, err := nestedPrimitiveValue(obj, projection.FieldPath)
171+
fields = append(fields, projectedField{name: name, value: value, found: found, error: err})
172+
}
175173
}
174+
collection = append(collection, fields)
176175
}
177176

178-
// group by the extracted values for the dimension .e.g. device: iPhone, device: Android and count them later
179-
groups := lo.GroupBy(collection, func(field projectedField) string {
180-
return field.GetID()
181-
})
177+
// Group by the combination of all projected values
178+
groups := make(map[string][][]projectedField)
179+
for _, fields := range collection {
180+
var keyParts []string
181+
for _, f := range fields {
182+
keyParts = append(keyParts, fmt.Sprintf("%s: %s", f.name, f.value))
183+
}
184+
key := strings.Join(keyParts, ", ")
185+
groups[key] = append(groups[key], fields)
186+
}
182187

183188
return groups
184189
}

0 commit comments

Comments
 (0)