diff --git a/go.mod b/go.mod index 5de892e..47b9849 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + github.com/prometheus/client_golang v1.0.0 github.com/stretchr/testify v1.5.1 k8s.io/api v0.18.4 k8s.io/apimachinery v0.18.4 diff --git a/go.sum b/go.sum index cb497ce..0193bad 100644 --- a/go.sum +++ b/go.sum @@ -457,19 +457,13 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8= -k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= k8s.io/apiextensions-apiserver v0.18.4 h1:Y3HGERmS8t9u12YNUFoOISqefaoGRuTc43AYCLzWmWE= k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= -k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA= -k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA= k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= -k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc= k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= @@ -482,8 +476,6 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= diff --git a/handler/instrumented_enqueue_object.go b/handler/instrumented_enqueue_object.go new file mode 100644 index 0000000..9305a01 --- /dev/null +++ b/handler/instrumented_enqueue_object.go @@ -0,0 +1,83 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed 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 handler + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + + "github.com/operator-framework/operator-lib/handler/internal/metrics" +) + +// InstrumentedEnqueueRequestForObject wraps controller-runtime handler for +// "EnqueueRequestForObject", and sets up primary resource metrics on event +// handlers. The main objective of this handler is to set prometheues metrics +// when create/update/delete events occur. These metrics contain below +// information on resource. +// +// resource_created_at_seconds{"name", "namespace", "group", "version", "kind"} +// +// '&handler.InstrumentedEnqueueRequestForObject{}' is used to call the handler. +type InstrumentedEnqueueRequestForObject struct { + handler.EnqueueRequestForObject +} + +// Create implements EventHandler, and creates the metrics. +func (h InstrumentedEnqueueRequestForObject) Create(e event.CreateEvent, q workqueue.RateLimitingInterface) { + setResourceMetric(e.Meta, e.Object) + h.EnqueueRequestForObject.Create(e, q) +} + +// Update implements EventHandler, and updates the metrics. +func (h InstrumentedEnqueueRequestForObject) Update(e event.UpdateEvent, q workqueue.RateLimitingInterface) { + setResourceMetric(e.MetaOld, e.ObjectOld) + setResourceMetric(e.MetaNew, e.ObjectNew) + + h.EnqueueRequestForObject.Update(e, q) +} + +// Delete implements EventHandler, and deletes metrics. +func (h InstrumentedEnqueueRequestForObject) Delete(e event.DeleteEvent, q workqueue.RateLimitingInterface) { + deleteResourceMetric(e.Meta, e.Object) + h.EnqueueRequestForObject.Delete(e, q) +} + +func setResourceMetric(metadata metav1.Object, obj runtime.Object) { + if metadata != nil && obj != nil { + labels := getResourceLabels(metadata, obj) + m, _ := metrics.ResourceCreatedAt.GetMetricWith(labels) + m.Set(float64(metadata.GetCreationTimestamp().UTC().Unix())) + } +} + +func deleteResourceMetric(metadata metav1.Object, obj runtime.Object) { + if metadata != nil && obj != nil { + labels := getResourceLabels(metadata, obj) + _ = metrics.ResourceCreatedAt.Delete(labels) + } +} + +func getResourceLabels(metadata metav1.Object, obj runtime.Object) map[string]string { + return map[string]string{ + "name": metadata.GetName(), + "namespace": metadata.GetNamespace(), + "group": obj.GetObjectKind().GroupVersionKind().Group, + "version": obj.GetObjectKind().GroupVersionKind().Version, + "kind": obj.GetObjectKind().GroupVersionKind().Kind, + } +} diff --git a/handler/internal/metrics/metrics.go b/handler/internal/metrics/metrics.go new file mode 100644 index 0000000..07d19ea --- /dev/null +++ b/handler/internal/metrics/metrics.go @@ -0,0 +1,33 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed 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 metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +// ResourceCreatedAt creates new prometheus metrics for primary resource, +// with information {"name", "namespace", "group", "version", "kind"} +var ResourceCreatedAt = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "resource_created_at_seconds", + Help: "Timestamp at which a resource was created", +}, []string{"name", "namespace", "group", "version", "kind"}) + +func init() { + metrics.Registry.MustRegister( + ResourceCreatedAt, + ) +}