|
6 | 6 | "strconv" |
7 | 7 | "strings" |
8 | 8 |
|
9 | | - "github.com/samber/lo" |
10 | 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
11 | 10 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
12 | 11 | "k8s.io/apimachinery/pkg/runtime/schema" |
@@ -94,41 +93,43 @@ func (h *MetricHandler) projectionsMonitor(ctx context.Context, list *unstructur |
94 | 93 | // Add base dimensions only if they have a non-empty value |
95 | 94 | h.setDataPointBaseDimensions(dataPoint) |
96 | 95 |
|
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 | + } |
105 | 105 | } |
| 106 | + |
| 107 | + dataPoints = append(dataPoints, dataPoint) |
106 | 108 | } |
107 | | - dataPoints = append(dataPoints, dataPoint) |
108 | | - } |
109 | 109 |
|
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 | + } |
115 | 115 |
|
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 | + } |
132 | 133 | return result, nil |
133 | 134 | } |
134 | 135 |
|
@@ -158,27 +159,31 @@ func (e *projectedField) GetID() string { |
158 | 159 | return fmt.Sprintf("%s: %s", e.name, e.value) |
159 | 160 | } |
160 | 161 |
|
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)) |
166 | 164 |
|
167 | 165 | 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 | + } |
175 | 173 | } |
| 174 | + collection = append(collection, fields) |
176 | 175 | } |
177 | 176 |
|
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 | + } |
182 | 187 |
|
183 | 188 | return groups |
184 | 189 | } |
|
0 commit comments