You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by as...@apache.org on 2020/11/10 10:20:19 UTC

[camel-k] 19/25: feat: Add time to first integration readiness metric

This is an automated email from the ASF dual-hosted git repository.

astefanutti pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 4cca10c96fc34961278eb6405c23a90b92badfaf
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Tue Oct 27 12:22:28 2020 +0100

    feat: Add time to first integration readiness metric
---
 deploy/crd-integration.yaml                        |  9 ++++
 .../integrations.camel.apache.org.crd.yaml         |  9 ++++
 helm/camel-k/crds/crd-integration.yaml             |  9 ++++
 pkg/apis/camel/v1/integration_types.go             |  4 ++
 pkg/apis/camel/v1/integration_types_support.go     | 52 +++++++++++++---------
 pkg/apis/camel/v1/zz_generated.deepcopy.go         |  8 ++++
 pkg/controller/integration/error.go                |  1 +
 pkg/controller/integration/initialize.go           |  6 +++
 pkg/controller/integration/metrics.go              | 47 +++++++++++++++++++
 pkg/controller/integration/monitor.go              | 18 ++++++--
 10 files changed, 139 insertions(+), 24 deletions(-)

diff --git a/deploy/crd-integration.yaml b/deploy/crd-integration.yaml
index 73753c7..a6ebe04 100644
--- a/deploy/crd-integration.yaml
+++ b/deploy/crd-integration.yaml
@@ -194,6 +194,10 @@ spec:
                 description: IntegrationCondition describes the state of a resource
                   at a certain point.
                 properties:
+                  firstTruthyTime:
+                    description: First time the condition status transitioned to True.
+                    format: date-time
+                    type: string
                   lastTransitionTime:
                     description: Last time the condition transitioned from one status
                       to another.
@@ -331,6 +335,11 @@ spec:
               type: string
             kit:
               type: string
+            lastInitTimestamp:
+              description: The timestamp representing the last time when this integration
+                was initialized.
+              format: date-time
+              type: string
             phase:
               description: IntegrationPhase --
               type: string
diff --git a/deploy/olm-catalog/camel-k-dev/1.3.0-snapshot/integrations.camel.apache.org.crd.yaml b/deploy/olm-catalog/camel-k-dev/1.3.0-snapshot/integrations.camel.apache.org.crd.yaml
index 73753c7..a6ebe04 100644
--- a/deploy/olm-catalog/camel-k-dev/1.3.0-snapshot/integrations.camel.apache.org.crd.yaml
+++ b/deploy/olm-catalog/camel-k-dev/1.3.0-snapshot/integrations.camel.apache.org.crd.yaml
@@ -194,6 +194,10 @@ spec:
                 description: IntegrationCondition describes the state of a resource
                   at a certain point.
                 properties:
+                  firstTruthyTime:
+                    description: First time the condition status transitioned to True.
+                    format: date-time
+                    type: string
                   lastTransitionTime:
                     description: Last time the condition transitioned from one status
                       to another.
@@ -331,6 +335,11 @@ spec:
               type: string
             kit:
               type: string
+            lastInitTimestamp:
+              description: The timestamp representing the last time when this integration
+                was initialized.
+              format: date-time
+              type: string
             phase:
               description: IntegrationPhase --
               type: string
diff --git a/helm/camel-k/crds/crd-integration.yaml b/helm/camel-k/crds/crd-integration.yaml
index 73753c7..a6ebe04 100644
--- a/helm/camel-k/crds/crd-integration.yaml
+++ b/helm/camel-k/crds/crd-integration.yaml
@@ -194,6 +194,10 @@ spec:
                 description: IntegrationCondition describes the state of a resource
                   at a certain point.
                 properties:
+                  firstTruthyTime:
+                    description: First time the condition status transitioned to True.
+                    format: date-time
+                    type: string
                   lastTransitionTime:
                     description: Last time the condition transitioned from one status
                       to another.
@@ -331,6 +335,11 @@ spec:
               type: string
             kit:
               type: string
+            lastInitTimestamp:
+              description: The timestamp representing the last time when this integration
+                was initialized.
+              format: date-time
+              type: string
             phase:
               description: IntegrationPhase --
               type: string
diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go
index 91d8c57..b5a57cd 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -60,6 +60,8 @@ type IntegrationStatus struct {
 	Replicas           *int32                 `json:"replicas,omitempty"`
 	Selector           string                 `json:"selector,omitempty"`
 	Capabilities       []string               `json:"capabilities,omitempty"`
+	// The timestamp representing the last time when this integration was initialized.
+	InitializationTimestamp *metav1.Time `json:"lastInitTimestamp,omitempty"`
 }
 
 // +genclient
@@ -274,6 +276,8 @@ type IntegrationCondition struct {
 	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
 	// Last time the condition transitioned from one status to another.
 	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+	// First time the condition status transitioned to True.
+	FirstTruthyTime *metav1.Time `json:"firstTruthyTime,omitempty"`
 	// The reason for the condition's last transition.
 	Reason string `json:"reason,omitempty"`
 	// A human readable message indicating details about the transition.
diff --git a/pkg/apis/camel/v1/integration_types_support.go b/pkg/apis/camel/v1/integration_types_support.go
index 2e807d3..591afda 100644
--- a/pkg/apis/camel/v1/integration_types_support.go
+++ b/pkg/apis/camel/v1/integration_types_support.go
@@ -282,24 +282,20 @@ func (in *IntegrationStatus) GetCondition(condType IntegrationConditionType) *In
 // SetCondition --
 func (in *IntegrationStatus) SetCondition(condType IntegrationConditionType, status corev1.ConditionStatus, reason string, message string) {
 	in.SetConditions(IntegrationCondition{
-		Type:               condType,
-		Status:             status,
-		LastUpdateTime:     metav1.Now(),
-		LastTransitionTime: metav1.Now(),
-		Reason:             reason,
-		Message:            message,
+		Type:    condType,
+		Status:  status,
+		Reason:  reason,
+		Message: message,
 	})
 }
 
 // SetErrorCondition --
 func (in *IntegrationStatus) SetErrorCondition(condType IntegrationConditionType, reason string, err error) {
 	in.SetConditions(IntegrationCondition{
-		Type:               condType,
-		Status:             corev1.ConditionFalse,
-		LastUpdateTime:     metav1.Now(),
-		LastTransitionTime: metav1.Now(),
-		Reason:             reason,
-		Message:            err.Error(),
+		Type:    condType,
+		Status:  corev1.ConditionFalse,
+		Reason:  reason,
+		Message: err.Error(),
 	})
 }
 
@@ -308,22 +304,36 @@ func (in *IntegrationStatus) SetErrorCondition(condType IntegrationConditionType
 // If a condition that we are about to add already exists and has the same status and
 // reason then we are not going to update.
 func (in *IntegrationStatus) SetConditions(conditions ...IntegrationCondition) {
+	now := metav1.Now()
 	for _, condition := range conditions {
+		currentCond := in.GetCondition(condition.Type)
+
+		if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason {
+			return
+		}
+
 		if condition.LastUpdateTime.IsZero() {
-			condition.LastUpdateTime = metav1.Now()
+			condition.LastUpdateTime = now
 		}
+
 		if condition.LastTransitionTime.IsZero() {
-			condition.LastTransitionTime = metav1.Now()
+			// We may want not to set it when the current condition is nil
+			condition.LastTransitionTime = now
 		}
 
-		currentCond := in.GetCondition(condition.Type)
-
-		if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason {
-			return
+		if (condition.FirstTruthyTime == nil || condition.FirstTruthyTime.IsZero()) && condition.Status == corev1.ConditionTrue {
+			condition.FirstTruthyTime = &now
 		}
-		// Do not update lastTransitionTime if the status of the condition doesn't change.
-		if currentCond != nil && currentCond.Status == condition.Status {
-			condition.LastTransitionTime = currentCond.LastTransitionTime
+
+		if currentCond != nil {
+			if currentCond.Status == condition.Status {
+				// Do not update LastTransitionTime if the status of the condition doesn't change
+				condition.LastTransitionTime = currentCond.LastTransitionTime
+			}
+			if !(currentCond.FirstTruthyTime != nil || currentCond.FirstTruthyTime.IsZero()) {
+				// Preserve FirstTruthyTime
+				condition.FirstTruthyTime = currentCond.FirstTruthyTime.DeepCopy()
+			}
 		}
 
 		in.RemoveCondition(condition.Type)
diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go
index e734bde..a38ad74 100644
--- a/pkg/apis/camel/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go
@@ -663,6 +663,10 @@ func (in *IntegrationCondition) DeepCopyInto(out *IntegrationCondition) {
 	*out = *in
 	in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
 	in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+	if in.FirstTruthyTime != nil {
+		in, out := &in.FirstTruthyTime, &out.FirstTruthyTime
+		*out = (*in).DeepCopy()
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationCondition.
@@ -1152,6 +1156,10 @@ func (in *IntegrationStatus) DeepCopyInto(out *IntegrationStatus) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.InitializationTimestamp != nil {
+		in, out := &in.InitializationTimestamp, &out.InitializationTimestamp
+		*out = (*in).DeepCopy()
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationStatus.
diff --git a/pkg/controller/integration/error.go b/pkg/controller/integration/error.go
index 0e18538..9e3ad90 100644
--- a/pkg/controller/integration/error.go
+++ b/pkg/controller/integration/error.go
@@ -52,6 +52,7 @@ func (action *errorAction) Handle(ctx context.Context, integration *v1.Integrati
 
 		integration.Status.Digest = hash
 		integration.Status.Phase = v1.IntegrationPhaseInitialization
+		integration.Status.InitializationTimestamp = nil
 
 		return integration, nil
 	}
diff --git a/pkg/controller/integration/initialize.go b/pkg/controller/integration/initialize.go
index ed98d88..4698b71 100644
--- a/pkg/controller/integration/initialize.go
+++ b/pkg/controller/integration/initialize.go
@@ -20,6 +20,8 @@ package integration
 import (
 	"context"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/trait"
 	"github.com/apache/camel-k/pkg/util/defaults"
@@ -55,6 +57,10 @@ func (action *initializeAction) Handle(ctx context.Context, integration *v1.Inte
 	integration.Status.Phase = v1.IntegrationPhaseBuildingKit
 	integration.SetIntegrationKit(&kit)
 	integration.Status.Version = defaults.Version
+	if timestamp := integration.Status.InitializationTimestamp; timestamp == nil || timestamp.IsZero() {
+		now := metav1.Now()
+		integration.Status.InitializationTimestamp = &now
+	}
 
 	return integration, nil
 }
diff --git a/pkg/controller/integration/metrics.go b/pkg/controller/integration/metrics.go
new file mode 100644
index 0000000..fb6fff3
--- /dev/null
+++ b/pkg/controller/integration/metrics.go
@@ -0,0 +1,47 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package integration
+
+import (
+	"time"
+
+	"sigs.k8s.io/controller-runtime/pkg/metrics"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+	timeToFirstReadiness = prometheus.NewHistogram(
+		prometheus.HistogramOpts{
+			Name: "camel_k_integration_first_readiness_seconds",
+			Help: "Camel K integration time to first readiness",
+			Buckets: []float64{
+				5 * time.Second.Seconds(),
+				10 * time.Second.Seconds(),
+				30 * time.Second.Seconds(),
+				1 * time.Minute.Seconds(),
+				2 * time.Minute.Seconds(),
+			},
+		},
+	)
+)
+
+func init() {
+	// Register custom metrics with the global prometheus registry
+	metrics.Registry.MustRegister(timeToFirstReadiness)
+}
diff --git a/pkg/controller/integration/monitor.go b/pkg/controller/integration/monitor.go
index 2b55f59..654d78b 100644
--- a/pkg/controller/integration/monitor.go
+++ b/pkg/controller/integration/monitor.go
@@ -21,6 +21,7 @@ import (
 	"context"
 
 	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
 	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -62,6 +63,7 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra
 			integration.Status.Profile = integration.Spec.Profile
 		}
 		integration.Status.Version = defaults.Version
+		integration.Status.InitializationTimestamp = nil
 
 		return integration, nil
 	}
@@ -72,8 +74,9 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra
 		return nil, err
 	}
 
-	// Enforce the scale sub-resource label selector
-	// It is used by the HPA that queries the scale sub-resource endpoint to list the pods owned by the integration
+	// Enforce the scale sub-resource label selector.
+	// It is used by the HPA that queries the scale sub-resource endpoint,
+	// to list the pods owned by the integration.
 	integration.Status.Selector = v1.IntegrationLabel + "=" + integration.Name
 
 	// Check replicas
@@ -96,9 +99,18 @@ func (action *monitorAction) Handle(ctx context.Context, integration *v1.Integra
 		}
 	}
 
-	// Mirror ready condition from the sub resource (e.g.ReplicaSet, Deployment, CronJob, ...) to the integration
+	// Mirror ready condition from the owned resource (e.g.ReplicaSet, Deployment, CronJob, ...)
+	// into the owning integration
+	previous := integration.Status.GetCondition(v1.IntegrationConditionReady)
 	kubernetes.MirrorReadyCondition(ctx, action.client, integration)
 
+	if next := integration.Status.GetCondition(v1.IntegrationConditionReady);
+		(previous == nil || previous.FirstTruthyTime == nil || previous.FirstTruthyTime.IsZero()) &&
+			next != nil && next.Status == corev1.ConditionTrue && !(next.FirstTruthyTime == nil || next.FirstTruthyTime.IsZero()) {
+		// Observe the time to first readiness metric
+		timeToFirstReadiness.Observe(next.FirstTruthyTime.Time.Sub(integration.Status.InitializationTimestamp.Time).Seconds())
+	}
+
 	return integration, nil
 }