Skip to content
Merged
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: 3 additions & 2 deletions status/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,9 @@ func (c *Controller[T]) reconcileFound(ctx context.Context, req reconcile.Reques
}
duration := condition.LastTransitionTime.Time.Sub(observedCondition.LastTransitionTime.Time).Seconds()
c.observeHistogram(c.ConditionDuration, ConditionDuration, duration, map[string]string{
pmetrics.LabelType: observedCondition.Type,
MetricLabelConditionStatus: string(observedCondition.Status),
pmetrics.LabelType: observedCondition.Type,
MetricLabelConditionStatus: string(observedCondition.Status),
MetricLabelToConditionStatus: string(condition.Status),
}, c.toAdditionalMetricLabels(o))
c.eventRecorder.Event(o, v1.EventTypeNormal, condition.Type, fmt.Sprintf("Status condition transitioned, Type: %s, Status: %s -> %s, Reason: %s%s",
condition.Type,
Expand Down
32 changes: 32 additions & 0 deletions status/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,38 @@ var _ = Describe("Controller", func() {
Expect(metric).ToNot(BeNil())
Expect(metric.GetHistogram().GetSampleCount()).To(BeNumerically(">", 0))
})
It("should label transition_seconds with the destination status a condition transitioned to", func() {
testObject := test.Object(&test.CustomObject{})
gvk := object.GVK(testObject)
ExpectApplied(ctx, kubeClient, testObject)
ExpectReconciled(ctx, controller, testObject) // observe Foo=Unknown

// Recover Foo: Unknown -> True
time.Sleep(time.Second * 1)
testObject.StatusConditions().SetTrue(ConditionTypeFoo)
ExpectApplied(ctx, kubeClient, testObject)
ExpectReconciled(ctx, controller, testObject)

// The dwell spent in Unknown is recorded labeled with the state left
// (status=Unknown) AND the destination (to_status=True).
recovered := map[string]string{
pmetrics.LabelType: string(ConditionTypeFoo),
status.MetricLabelConditionStatus: string(metav1.ConditionUnknown),
status.MetricLabelToConditionStatus: string(metav1.ConditionTrue),
}
Expect(GetMetric("operator_customobject_status_condition_transition_seconds", recovered).GetHistogram().GetSampleCount()).To(BeNumerically(">", 0))
// No sample is recorded for the same dwell under a different destination.
Expect(GetMetric("operator_customobject_status_condition_transition_seconds", map[string]string{
pmetrics.LabelType: string(ConditionTypeFoo),
status.MetricLabelConditionStatus: string(metav1.ConditionUnknown),
status.MetricLabelToConditionStatus: string(metav1.ConditionFalse),
})).To(BeNil())
// The deprecated (group/kind-labeled) variant carries to_status as well.
Expect(GetMetric("operator_status_condition_transition_seconds", lo.Assign(recovered, map[string]string{
pmetrics.LabelGroup: gvk.Group,
pmetrics.LabelKind: gvk.Kind,
})).GetHistogram().GetSampleCount()).To(BeNumerically(">", 0))
})
It("should emit metrics and events on a transition", func() {
testObject := test.Object(&test.CustomObject{})
gvk := object.GVK(testObject)
Expand Down
9 changes: 8 additions & 1 deletion status/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const (
MetricLabelNamespace = "namespace"
MetricLabelName = "name"
MetricLabelConditionStatus = "status"
// MetricLabelToConditionStatus labels the transition_seconds histogram with
// the status the condition transitioned TO. Combined with the existing
// "status" label (the status being left), this lets consumers distinguish
// how a dwell ended -- e.g. how long a condition was unhealthy
// (status="False") before it recovered (to_status="True").
MetricLabelToConditionStatus = "to_status"
)

const (
Expand All @@ -33,12 +39,13 @@ func conditionDurationMetric(objectName string, buckets []float64, additionalLab
Namespace: pmetrics.Namespace,
Subsystem: subsystem,
Name: "transition_seconds",
Help: "The amount of time a condition was in a given state before transitioning. e.g. Alarm := P99(Updated=False) > 5 minutes",
Help: "The amount of time a condition was in a given state (status) before transitioning to another state (to_status). e.g. Alarm := P99(Updated=False) > 5 minutes",
Buckets: buckets,
},
append([]string{
pmetrics.LabelType,
MetricLabelConditionStatus,
MetricLabelToConditionStatus,
}, additionalLabels...),
)
}
Expand Down