Skip to content

Commit b3451c8

Browse files
author
Jim Minter
committed
implement prometheus metrics for the TemplateInstance controller
1 parent e020a09 commit b3451c8

File tree

5 files changed

+227
-2
lines changed

5 files changed

+227
-2
lines changed

pkg/build/metrics/prometheus/metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var (
2424
// vs. the other "finished" builds phases, where the reason is not set
2525
terminalBuildCountDesc = prometheus.NewDesc(
2626
buildSubsystem+separator+terminalBuildCount,
27-
"Counts total teriminal builds by phase",
27+
"Counts total terminal builds by phase",
2828
[]string{"phase"},
2929
nil,
3030
)

pkg/template/controller/metrics.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package controller
2+
3+
import (
4+
templateapi "github.com/openshift/origin/pkg/template/apis/template"
5+
"github.com/prometheus/client_golang/prometheus"
6+
"k8s.io/apimachinery/pkg/labels"
7+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
8+
kapi "k8s.io/kubernetes/pkg/api"
9+
)
10+
11+
var templateInstancesTotal = prometheus.NewGaugeVec(
12+
prometheus.GaugeOpts{
13+
Name: "openshift_template_instance_total",
14+
Help: "Counts TemplateInstance objects by condition type and status",
15+
},
16+
[]string{"type", "status"},
17+
)
18+
19+
var templateInstancesActiveStartTime = prometheus.NewGaugeVec(
20+
prometheus.GaugeOpts{
21+
Name: "openshift_template_instance_active_start_time_seconds",
22+
Help: "Show the start time in unix epoch form of active TemplateInstance objects by namespace and name",
23+
},
24+
[]string{"namespace", "name"},
25+
)
26+
27+
func (c *TemplateInstanceController) Describe(ch chan<- *prometheus.Desc) {
28+
templateInstancesTotal.Describe(ch)
29+
templateInstancesActiveStartTime.Describe(ch)
30+
}
31+
32+
func (c *TemplateInstanceController) Collect(ch chan<- prometheus.Metric) {
33+
templateInstances, err := c.lister.List(labels.Everything())
34+
if err != nil {
35+
utilruntime.HandleError(err)
36+
return
37+
}
38+
39+
templateInstancesTotal.Reset()
40+
templateInstancesActiveStartTime.Reset()
41+
42+
templateInstancesTotal.WithLabelValues("", "").Set(0)
43+
44+
for _, templateInstance := range templateInstances {
45+
waiting := true
46+
47+
templateInstancesTotal.WithLabelValues("", "").Inc()
48+
49+
for _, cond := range templateInstance.Status.Conditions {
50+
templateInstancesTotal.WithLabelValues(string(cond.Type), string(cond.Status)).Inc()
51+
52+
if cond.Status == kapi.ConditionTrue &&
53+
(cond.Type == templateapi.TemplateInstanceInstantiateFailure || cond.Type == templateapi.TemplateInstanceReady) {
54+
waiting = false
55+
}
56+
}
57+
58+
if waiting {
59+
templateInstancesActiveStartTime.WithLabelValues(templateInstance.Namespace, templateInstance.Name).Set(float64(templateInstance.CreationTimestamp.Unix()))
60+
}
61+
}
62+
63+
templateInstancesTotal.Collect(ch)
64+
templateInstancesActiveStartTime.Collect(ch)
65+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package controller
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"testing"
7+
"time"
8+
9+
templateapi "github.com/openshift/origin/pkg/template/apis/template"
10+
"github.com/openshift/origin/pkg/template/generated/listers/template/internalversion"
11+
12+
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/client_golang/prometheus/promhttp"
14+
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/labels"
17+
kapi "k8s.io/kubernetes/pkg/api"
18+
)
19+
20+
type fakeLister []*templateapi.TemplateInstance
21+
22+
func (f fakeLister) List(labels.Selector) ([]*templateapi.TemplateInstance, error) {
23+
return f, nil
24+
}
25+
func (fakeLister) TemplateInstances(string) internalversion.TemplateInstanceNamespaceLister {
26+
return nil
27+
}
28+
29+
type fakeResponseWriter struct {
30+
bytes.Buffer
31+
statusCode int
32+
header http.Header
33+
}
34+
35+
func (f *fakeResponseWriter) Header() http.Header {
36+
return f.header
37+
}
38+
39+
func (f *fakeResponseWriter) WriteHeader(statusCode int) {
40+
f.statusCode = statusCode
41+
}
42+
43+
func TestMetrics(t *testing.T) {
44+
expectedResponse := `# HELP openshift_template_instance_active_start_time_seconds Show the start time in unix epoch form of active TemplateInstance objects by namespace and name
45+
# TYPE openshift_template_instance_active_start_time_seconds gauge
46+
openshift_template_instance_active_start_time_seconds{name="testname",namespace="testnamespace"} 123
47+
# HELP openshift_template_instance_total Counts TemplateInstance objects by condition type and status
48+
# TYPE openshift_template_instance_total gauge
49+
openshift_template_instance_total{status="",type=""} 2
50+
openshift_template_instance_total{status="False",type="Ready"} 1
51+
openshift_template_instance_total{status="True",type="Ready"} 1
52+
`
53+
registry := prometheus.NewRegistry()
54+
55+
c := &TemplateInstanceController{
56+
lister: &fakeLister{
57+
{
58+
Status: templateapi.TemplateInstanceStatus{
59+
Conditions: []templateapi.TemplateInstanceCondition{
60+
{
61+
Type: templateapi.TemplateInstanceReady,
62+
Status: kapi.ConditionTrue,
63+
},
64+
},
65+
},
66+
},
67+
{
68+
ObjectMeta: metav1.ObjectMeta{
69+
Namespace: "testnamespace",
70+
Name: "testname",
71+
CreationTimestamp: metav1.Time{
72+
Time: time.Unix(123, 0),
73+
},
74+
},
75+
Status: templateapi.TemplateInstanceStatus{
76+
Conditions: []templateapi.TemplateInstanceCondition{
77+
{
78+
Type: templateapi.TemplateInstanceReady,
79+
Status: kapi.ConditionFalse,
80+
},
81+
},
82+
},
83+
},
84+
},
85+
}
86+
87+
registry.MustRegister(c)
88+
89+
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.PanicOnError})
90+
rw := &fakeResponseWriter{header: http.Header{}}
91+
h.ServeHTTP(rw, &http.Request{})
92+
93+
if rw.String() != expectedResponse {
94+
t.Error(rw.String())
95+
}
96+
}

pkg/template/controller/templateinstance_controller.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/kubernetes/pkg/kubectl/resource"
2727

2828
"github.com/golang/glog"
29+
"github.com/prometheus/client_golang/prometheus"
2930

3031
"github.com/openshift/origin/pkg/authorization/util"
3132
buildclient "github.com/openshift/origin/pkg/build/generated/internalclientset"
@@ -75,7 +76,7 @@ func NewTemplateInstanceController(config *rest.Config, kc kclientsetinternal.In
7576
buildClient: buildClient,
7677
lister: informer.Lister(),
7778
informer: informer.Informer(),
78-
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "TemplateInstanceController"),
79+
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "openshift_template_instance_controller"),
7980
readinessLimiter: workqueue.NewItemFastSlowRateLimiter(5*time.Second, 20*time.Second, 200),
8081
}
8182

@@ -90,6 +91,8 @@ func NewTemplateInstanceController(config *rest.Config, kc kclientsetinternal.In
9091
},
9192
})
9293

94+
prometheus.MustRegister(c)
95+
9396
return c
9497
}
9598

test/integration/metrics_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package integration
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
"time"
7+
8+
"k8s.io/apimachinery/pkg/util/wait"
9+
10+
testutil "github.com/openshift/origin/test/util"
11+
testserver "github.com/openshift/origin/test/util/server"
12+
)
13+
14+
var metricsRegexp = regexp.MustCompile("(?m)^# HELP ([^ ]*)")
15+
16+
func TestMetrics(t *testing.T) {
17+
expectedMetrics := []string{
18+
"openshift_build_terminal_phase_total",
19+
"openshift_template_instance_total",
20+
}
21+
22+
masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMaster()
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
defer testserver.CleanupMasterEtcd(t, masterConfig)
27+
28+
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
33+
var missingMetrics []string
34+
err = wait.Poll(time.Second, 30*time.Second, func() (bool, error) {
35+
missingMetrics = []string{}
36+
37+
b, err := clusterAdminClient.Get().RequestURI("/metrics").DoRaw()
38+
if err != nil {
39+
return false, err
40+
}
41+
42+
metrics := map[string]struct{}{}
43+
for _, match := range metricsRegexp.FindAllStringSubmatch(string(b), -1) {
44+
metrics[match[1]] = struct{}{}
45+
}
46+
47+
for _, metric := range expectedMetrics {
48+
if _, ok := metrics[metric]; !ok {
49+
missingMetrics = append(missingMetrics, metric)
50+
}
51+
}
52+
53+
return len(missingMetrics) == 0, nil
54+
})
55+
if len(missingMetrics) > 0 {
56+
t.Error(missingMetrics)
57+
}
58+
if err != nil {
59+
t.Error(err)
60+
}
61+
}

0 commit comments

Comments
 (0)