Skip to content

Commit bac3a5d

Browse files
committed
Add Prometheus tests and fix metric type bug.
1 parent cf9c76c commit bac3a5d

File tree

4 files changed

+330
-23
lines changed

4 files changed

+330
-23
lines changed

info/v1/container.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -234,25 +234,28 @@ type LoadStats struct {
234234
NrIoWait uint64 `json:"nr_io_wait"`
235235
}
236236

237+
// CPU usage time statistics.
238+
type CpuUsage struct {
239+
// Total CPU usage.
240+
// Units: nanoseconds
241+
Total uint64 `json:"total"`
242+
243+
// Per CPU/core usage of the container.
244+
// Unit: nanoseconds.
245+
PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
246+
247+
// Time spent in user space.
248+
// Unit: nanoseconds
249+
User uint64 `json:"user"`
250+
251+
// Time spent in kernel space.
252+
// Unit: nanoseconds
253+
System uint64 `json:"system"`
254+
}
255+
237256
// All CPU usage metrics are cumulative from the creation of the container
238257
type CpuStats struct {
239-
Usage struct {
240-
// Total CPU usage.
241-
// Units: nanoseconds
242-
Total uint64 `json:"total"`
243-
244-
// Per CPU/core usage of the container.
245-
// Unit: nanoseconds.
246-
PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
247-
248-
// Time spent in user space.
249-
// Unit: nanoseconds
250-
User uint64 `json:"user"`
251-
252-
// Time spent in kernel space.
253-
// Unit: nanoseconds
254-
System uint64 `json:"system"`
255-
} `json:"usage"`
258+
Usage CpuUsage `json:"usage"`
256259
// Smoothed average of number of runnable threads x 1000.
257260
// We multiply by thousand to avoid using floats, but preserving precision.
258261
// Load is smoothed over the last 10 seconds. Instantaneous value can be read

metrics/prometheus.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ import (
2020

2121
"github.com/golang/glog"
2222
info "github.com/google/cadvisor/info/v1"
23-
"github.com/google/cadvisor/manager"
2423
"github.com/prometheus/client_golang/prometheus"
2524
)
2625

26+
type subcontainersInfoProvider interface {
27+
// Get information about all subcontainers of the specified container (includes self).
28+
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
29+
}
30+
2731
type prometheusMetric struct {
2832
valueType prometheus.ValueType
2933
value float64
@@ -32,7 +36,7 @@ type prometheusMetric struct {
3236

3337
// PrometheusCollector implements prometheus.Collector.
3438
type PrometheusCollector struct {
35-
manager manager.Manager
39+
infoProvider subcontainersInfoProvider
3640

3741
errors prometheus.Gauge
3842
lastSeen *prometheus.Desc
@@ -77,9 +81,9 @@ type PrometheusCollector struct {
7781
}
7882

7983
// NewPrometheusCollector returns a new PrometheusCollector.
80-
func NewPrometheusCollector(manager manager.Manager) *PrometheusCollector {
84+
func NewPrometheusCollector(infoProvider subcontainersInfoProvider) *PrometheusCollector {
8185
c := &PrometheusCollector{
82-
manager: manager,
86+
infoProvider: infoProvider,
8387
errors: prometheus.NewGauge(prometheus.GaugeOpts{
8488
Namespace: "container",
8589
Name: "scrape_error",
@@ -283,7 +287,7 @@ func (c *PrometheusCollector) Describe(ch chan<- *prometheus.Desc) {
283287
// Collect fetches the stats from all containers and delivers them as
284288
// Prometheus metrics. It implements prometheus.PrometheusCollector.
285289
func (c *PrometheusCollector) Collect(ch chan<- prometheus.Metric) {
286-
containers, err := c.manager.SubcontainersInfo("/", &info.ContainerInfoRequest{NumStats: 1})
290+
containers, err := c.infoProvider.SubcontainersInfo("/", &info.ContainerInfoRequest{NumStats: 1})
287291
if err != nil {
288292
c.errors.Set(1)
289293
glog.Warning("Couldn't get containers: %s", err)
@@ -330,7 +334,7 @@ func (c *PrometheusCollector) Collect(ch chan<- prometheus.Metric) {
330334
c.networkTxDropped: {{valueType: prometheus.CounterValue, value: float64(stats.Network.TxDropped)}},
331335
} {
332336
for _, m := range metrics {
333-
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, float64(m.value), append(m.labels, name, id)...)
337+
ch <- prometheus.MustNewConstMetric(desc, m.valueType, float64(m.value), append(m.labels, name, id)...)
334338
}
335339
}
336340

metrics/prometheus_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2014 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package metrics
16+
17+
import (
18+
"io/ioutil"
19+
"net/http"
20+
"net/http/httptest"
21+
"regexp"
22+
"strings"
23+
"testing"
24+
25+
info "github.com/google/cadvisor/info/v1"
26+
"github.com/prometheus/client_golang/prometheus"
27+
)
28+
29+
type testSubcontainersInfoProvider struct{}
30+
31+
func (p testSubcontainersInfoProvider) SubcontainersInfo(string, *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) {
32+
return []*info.ContainerInfo{
33+
{
34+
ContainerReference: info.ContainerReference{
35+
Name: "testcontainer",
36+
},
37+
Stats: []*info.ContainerStats{
38+
{
39+
Cpu: info.CpuStats{
40+
Usage: info.CpuUsage{
41+
Total: 1,
42+
PerCpu: []uint64{2, 3, 4, 5},
43+
User: 6,
44+
System: 7,
45+
},
46+
},
47+
Memory: info.MemoryStats{
48+
Usage: 8,
49+
WorkingSet: 9,
50+
ContainerData: info.MemoryStatsMemoryData{
51+
Pgfault: 10,
52+
Pgmajfault: 11,
53+
},
54+
HierarchicalData: info.MemoryStatsMemoryData{
55+
Pgfault: 12,
56+
Pgmajfault: 13,
57+
},
58+
},
59+
Network: info.NetworkStats{
60+
RxBytes: 14,
61+
RxPackets: 15,
62+
RxErrors: 16,
63+
RxDropped: 17,
64+
TxBytes: 18,
65+
TxPackets: 19,
66+
TxErrors: 20,
67+
TxDropped: 21,
68+
},
69+
Filesystem: []info.FsStats{
70+
{
71+
Device: "sda1",
72+
Limit: 22,
73+
Usage: 23,
74+
ReadsCompleted: 24,
75+
ReadsMerged: 25,
76+
SectorsRead: 26,
77+
ReadTime: 27,
78+
WritesCompleted: 28,
79+
WritesMerged: 39,
80+
SectorsWritten: 40,
81+
WriteTime: 41,
82+
IoInProgress: 42,
83+
IoTime: 43,
84+
WeightedIoTime: 44,
85+
},
86+
{
87+
Device: "sda2",
88+
Limit: 37,
89+
Usage: 38,
90+
ReadsCompleted: 39,
91+
ReadsMerged: 40,
92+
SectorsRead: 41,
93+
ReadTime: 42,
94+
WritesCompleted: 43,
95+
WritesMerged: 44,
96+
SectorsWritten: 45,
97+
WriteTime: 46,
98+
IoInProgress: 47,
99+
IoTime: 48,
100+
WeightedIoTime: 49,
101+
},
102+
},
103+
TaskStats: info.LoadStats{
104+
NrSleeping: 50,
105+
NrRunning: 51,
106+
NrStopped: 52,
107+
NrUninterruptible: 53,
108+
NrIoWait: 54,
109+
},
110+
},
111+
},
112+
},
113+
}, nil
114+
}
115+
116+
func TestPrometheusCollector(t *testing.T) {
117+
prometheus.MustRegister(NewPrometheusCollector(testSubcontainersInfoProvider{}))
118+
119+
rw := httptest.NewRecorder()
120+
prometheus.Handler().ServeHTTP(rw, &http.Request{})
121+
122+
metricsFile := "testdata/prometheus_metrics"
123+
wantMetrics, err := ioutil.ReadFile(metricsFile)
124+
if err != nil {
125+
t.Fatalf("unable to read input test file %s", metricsFile)
126+
}
127+
128+
wantLines := strings.Split(string(wantMetrics), "\n")
129+
gotLines := strings.Split(string(rw.Body.String()), "\n")
130+
131+
// Until the Prometheus Go client library offers better testability
132+
// (https://github.com/prometheus/client_golang/issues/58), we simply compare
133+
// verbatim text-format metrics outputs, but ignore certain metric lines
134+
// whose value depends on the current time or local circumstances.
135+
includeRe := regexp.MustCompile("^(# HELP |# TYPE |)container_")
136+
ignoreRe := regexp.MustCompile("^container_last_seen{")
137+
for i, want := range wantLines {
138+
if !includeRe.MatchString(want) || ignoreRe.MatchString(want) {
139+
continue
140+
}
141+
if want != gotLines[i] {
142+
t.Fatalf("want %s, got %s", want, gotLines[i])
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)