|
2 | 2 |
|
3 | 3 | A flexible, memory efficient Prometheus `GaugeVec` wrapper for managing **sets** of metrics. |
4 | 4 |
|
5 | | ---- |
6 | | - |
7 | | -## GaugeVecSet |
8 | | - |
9 | 5 | The `GaugeVecSet` is a high-performance wrapper around Prometheus `GaugeVec` that enables bulk operations on series |
10 | 6 | by specified index and grouping labels. |
11 | 7 |
|
@@ -105,162 +101,13 @@ deleted := PodPhase.DeleteByIndex("prod") |
105 | 101 |
|
106 | 102 | ### GaugeVecSet: DeleteByGroup |
107 | 103 |
|
108 | | -Delete all series that match the given (index, group) |
| 104 | +Delete all series that match the given (index, group). The number of index and group values this method requires |
| 105 | +coincides with the number of values the gauge was initialized with, meaning you cannot specify partial values for |
| 106 | +deletion. |
109 | 107 |
|
110 | 108 | ```go |
111 | 109 | deleted := PodPhase.DeleteByGroup( |
112 | 110 | []string{"prod"}, // index |
113 | 111 | "nginx-6f4c", // group |
114 | 112 | ) |
115 | | -``` |
116 | | - |
117 | | -## ConditionMetricsRecorder |
118 | | - |
119 | | -The `ConditionMetricsRecorder` is an implementation of `GaugeVecSet` for kubernetes operators. It enables |
120 | | -controllers to record metrics for it's kubernetes `metav1.Conditions` on custom resources. |
121 | | - |
122 | | -It is inspired by kube-state-metrics patterns for metrics such as `kube_pod_status_phase`. KSM exports one time series |
123 | | -per phase for each (namespace, pod), and marks exactly one as active (1) while the others are inactive (0). This metric |
124 | | -can be thought of as a `GaugeVecSet` with the index label `namespace`, the group `pod` and the `extra` labels |
125 | | -(i.e. variants per group) as the options for `phase`. |
126 | | - |
127 | | -Example: |
128 | | - |
129 | | -``` |
130 | | -kube_pod_status_phase{namespace="default", pod="nginx", phase="Running"} 1 |
131 | | -kube_pod_status_phase{namespace="default", pod="nginx", phase="Pending"} 0 |
132 | | -kube_pod_status_phase{namespace="default", pod="nginx", phase="Failed"} 0 |
133 | | -``` |
134 | | - |
135 | | -We adopt the same pattern for controller Conditions, but we export only one time series per (status, reason) variant, |
136 | | -meaning we delete all other variants in the group when we set the metric, ensuring the cardinality stays under control. |
137 | | -Additionally, rather than return 1/0 indicating the activeness of the metric, we set the last transition time of the |
138 | | -condition as the value (unix timestamp). |
139 | | - |
140 | | -Example metric: |
141 | | - |
142 | | -``` |
143 | | -operator_controller_condition{ |
144 | | - controller="my_controller", |
145 | | - resource_kind="MyCR", |
146 | | - resource_name="my-cr", |
147 | | - resource_namespace="default", |
148 | | - condition="Ready", |
149 | | - status="False", |
150 | | - reason="FailedToProvision" |
151 | | -} 17591743210 |
152 | | -``` |
153 | | - |
154 | | -- **Index**: controller, resource_kind, resource_name, resource_namespace |
155 | | -- **Group**: condition |
156 | | -- **Extra**: status, reason |
157 | | -- **Metric Value**: Unix timestamp of last transition of given condition |
158 | | - |
159 | | -### Initialization |
160 | | - |
161 | | -The metric should be initialized and registered once. |
162 | | - |
163 | | -You can embed the `ControllerMetricsRecorder` in your controller's recorder. |
164 | | - |
165 | | -```go |
166 | | -package my_metrics |
167 | | - |
168 | | -import ( |
169 | | - controllermetrics "sigs.k8s.io/controller-runtime/pkg/metrics" |
170 | | - ocg "github.com/sourcehawk/go-prometheus-gaugevecset/pkg/operator_condition_metrics" |
171 | | -) |
172 | | - |
173 | | -// We need this variable later to create the ConditionMetricsRecorder |
174 | | -var OperatorConditionsGauge *ocg.OperatorConditionsGauge |
175 | | - |
176 | | -// Initialize the operator condition gauge once |
177 | | -func init() { |
178 | | - OperatorConditionsGauge = ocg.NewOperatorConditionsGauge("my-operator") |
179 | | - controllermetrics.Registry.MustRegister(OperatorConditionsGauge) |
180 | | -} |
181 | | - |
182 | | -// Embed in existing metrics recorder |
183 | | -type MyControllerRecorder struct { |
184 | | - ocg.ConditionMetricRecorder |
185 | | -} |
186 | | -``` |
187 | | - |
188 | | -When constructing your reconciler, initialize the condition metrics recorder with the |
189 | | -operator conditions gauge and a unique name for each controller. |
190 | | - |
191 | | -_cmd/main.go_ |
192 | | -```go |
193 | | -package main |
194 | | - |
195 | | -import ( |
196 | | - mymetrics "path/to/pkg/my_metrics" |
197 | | - ocg "github.com/sourcehawk/go-prometheus-gaugevecset/pkg/operator_condition_metrics" |
198 | | -) |
199 | | - |
200 | | -func main() { |
201 | | - // ... |
202 | | - recorder := mymetrics.MyControllerRecorder{ |
203 | | - ConditionMetricRecorder: ocg.ConditionMetricRecorder{ |
204 | | - Controller: "my-controller", // unique name per reconciler |
205 | | - OperatorConditionsGauge: mymetrics.OperatorConditionsGauge, |
206 | | - }, |
207 | | - } |
208 | | - |
209 | | - reconciler := &MyReconciler{ |
210 | | - Recorder: recorder, |
211 | | - } |
212 | | - // ... |
213 | | -} |
214 | | -``` |
215 | | - |
216 | | -## Usage |
217 | | - |
218 | | -The easiest drop-in way to start using the metrics recorder is by creating a `SetStatusCondition` wrapper, which |
219 | | -comes instead of `meta.SetStatusCondition`. |
220 | | - |
221 | | -To delete the metrics for a given custom resource, simply call `RemoveConditionsFor` and pass the object. |
222 | | - |
223 | | -```go |
224 | | -const ( |
225 | | - kind = "MyCR" |
226 | | -) |
227 | | - |
228 | | -// SetStatusCondition utility function which replaces and wraps meta.SetStatusCondition calls |
229 | | -func (r *MyReconciler) SetStatusCondition(cr *v1.MyCR, cond metav1.Condition) bool { |
230 | | - changed := meta.SetStatusCondition(&cr.Status.Conditions, cond) |
231 | | - if changed { |
232 | | - // refetch the condition to get the updated version |
233 | | - updated := meta.FindStatusCondition(cr.Status.Conditions, cond.Type) |
234 | | - if updated != nil { |
235 | | - r.Recorder.RecordConditionFor( |
236 | | - kind, cr, updated.Type, string(updated.Status), updated.Reason, updated.LastTransitionTime, |
237 | | - ) |
238 | | - } |
239 | | - } |
240 | | - return changed |
241 | | -} |
242 | | - |
243 | | -func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { |
244 | | - // Get the resource we're reconciling |
245 | | - cr := new(v1.MyCR) |
246 | | - if err = r.Get(ctx, req.NamespacedName, cr); err != nil { |
247 | | - return ctrl.Result{}, client.IgnoreNotFound(err) |
248 | | - } |
249 | | - |
250 | | - // Remove the metrics when the CR is deleted |
251 | | - if cr.DeletionTimestamp != nil { |
252 | | - r.Recorder.RemoveConditionsFor(kind, cr) |
253 | | - } |
254 | | - |
255 | | - // ... |
256 | | - |
257 | | - // Update the status conditions using the recorder (it records the metric if changed) |
258 | | - if r.SetStatusCondition(cr, condition) { |
259 | | - if err = r.Status().Update(ctx, cr); err != nil { |
260 | | - return ctrl.Result{}, err |
261 | | - } |
262 | | - } |
263 | | - |
264 | | - return ctrl.Result{}, nil |
265 | | -} |
266 | | -``` |
| 113 | +``` |
0 commit comments