Skip to content

Commit e76096d

Browse files
committed
Provide option to control Prometheus labels
This change generalizes the existing ContainerNameToLabelsFunc to allow the user to fully control all labels attached to exported Prometheus metrics. The existing behavior is available as DefaultContainerLabelsFunc and is used if no custom function is provided. This will allow Kubernetes to filter out its internal Docker labels.
1 parent 2ed7198 commit e76096d

File tree

3 files changed

+86
-70
lines changed

3 files changed

+86
-70
lines changed

http/handlers.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ func RegisterHandlers(mux httpmux.Mux, containerManager manager.Manager, httpAut
8989
return nil
9090
}
9191

92-
func RegisterPrometheusHandler(mux httpmux.Mux, containerManager manager.Manager, prometheusEndpoint string, containerNameToLabelsFunc metrics.ContainerNameToLabelsFunc) {
93-
collector := metrics.NewPrometheusCollector(containerManager, containerNameToLabelsFunc)
92+
// RegisterPrometheusHandler creates a new PrometheusCollector, registers it
93+
// on the global registry and configures the provided HTTP mux to handle the
94+
// given Prometheus endpoint.
95+
func RegisterPrometheusHandler(mux httpmux.Mux, containerManager manager.Manager, prometheusEndpoint string, f metrics.ContainerLabelsFunc) {
96+
collector := metrics.NewPrometheusCollector(containerManager, f)
9497
prometheus.MustRegister(collector)
9598
mux.Handle(prometheusEndpoint, prometheus.Handler())
9699
}

metrics/prometheus.go

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ import (
2525
"github.com/prometheus/client_golang/prometheus"
2626
)
2727

28-
// This will usually be manager.Manager, but can be swapped out for testing.
28+
// infoProvider will usually be manager.Manager, but can be swapped out for testing.
2929
type infoProvider interface {
30-
// Get information about all subcontainers of the specified container (includes self).
30+
// SubcontainersInfo provides information about all subcontainers of the
31+
// specified container including itself.
3132
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
32-
// Get information about the version.
33+
// GetVersionInfo provides information about the version.
3334
GetVersionInfo() (*info.VersionInfo, error)
34-
// Get information about the machine.
35+
// GetMachineInfo provides information about the machine.
3536
GetMachineInfo() (*info.MachineInfo, error)
3637
}
3738

@@ -56,8 +57,8 @@ func fsValues(fsStats []info.FsStats, valueFn func(*info.FsStats) float64) metri
5657
return values
5758
}
5859

59-
// A containerMetric describes a multi-dimensional metric used for exposing
60-
// a certain type of container statistic.
60+
// containerMetric describes a multi-dimensional metric used for exposing a
61+
// certain type of container statistic.
6162
type containerMetric struct {
6263
name string
6364
help string
@@ -71,21 +72,29 @@ func (cm *containerMetric) desc(baseLabels []string) *prometheus.Desc {
7172
return prometheus.NewDesc(cm.name, cm.help, append(baseLabels, cm.extraLabels...), nil)
7273
}
7374

74-
type ContainerNameToLabelsFunc func(containerName string) map[string]string
75+
// ContainerLabelsFunc defines all base labels and their values attached to
76+
// each metric exported by cAdvisor.
77+
type ContainerLabelsFunc func(*info.ContainerInfo) map[string]string
7578

7679
// PrometheusCollector implements prometheus.Collector.
7780
type PrometheusCollector struct {
78-
infoProvider infoProvider
79-
errors prometheus.Gauge
80-
containerMetrics []containerMetric
81-
containerNameToLabels ContainerNameToLabelsFunc
81+
infoProvider infoProvider
82+
errors prometheus.Gauge
83+
containerMetrics []containerMetric
84+
containerLabelsFunc ContainerLabelsFunc
8285
}
8386

84-
// NewPrometheusCollector returns a new PrometheusCollector.
85-
func NewPrometheusCollector(infoProvider infoProvider, f ContainerNameToLabelsFunc) *PrometheusCollector {
87+
// NewPrometheusCollector returns a new PrometheusCollector. The passed
88+
// ContainerLabelsFunc specifies which base labels will be attached to all
89+
// exported metrics. If left to nil, the DefaultContainerLabels function
90+
// will be used instead.
91+
func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc) *PrometheusCollector {
92+
if f == nil {
93+
f = DefaultContainerLabels
94+
}
8695
c := &PrometheusCollector{
87-
infoProvider: infoProvider,
88-
containerNameToLabels: f,
96+
infoProvider: i,
97+
containerLabelsFunc: f,
8998
errors: prometheus.NewGauge(prometheus.GaugeOpts{
9099
Namespace: "container",
91100
Name: "scrape_error",
@@ -533,10 +542,38 @@ func (c *PrometheusCollector) Collect(ch chan<- prometheus.Metric) {
533542
}
534543

535544
const (
536-
containerLabelPrefix = "container_label_"
537-
containerEnvPrefix = "container_env_"
545+
// ContainerLabelPrefix is the prefix added to all container labels.
546+
ContainerLabelPrefix = "container_label_"
547+
// ContainerEnvPrefix is the prefix added to all env variable labels.
548+
ContainerEnvPrefix = "container_env_"
549+
// LabelID is the name of the id label.
550+
LabelID = "id"
551+
// LabelName is the name of the name label.
552+
LabelName = "name"
553+
// LabelImage is the name of the image label.
554+
LabelImage = "image"
538555
)
539556

557+
// DefaultContainerLabels implements ContainerLabelsFunc. It exports the
558+
// container name, first alias, image name as well as all its env and label
559+
// values.
560+
func DefaultContainerLabels(container *info.ContainerInfo) map[string]string {
561+
set := map[string]string{LabelID: container.Name}
562+
if len(container.Aliases) > 0 {
563+
set[LabelName] = container.Aliases[0]
564+
}
565+
if image := container.Spec.Image; len(image) > 0 {
566+
set[LabelImage] = image
567+
}
568+
for k, v := range container.Spec.Labels {
569+
set[ContainerLabelPrefix+k] = v
570+
}
571+
for k, v := range container.Spec.Envs {
572+
set[ContainerEnvPrefix+k] = v
573+
}
574+
return set
575+
}
576+
540577
func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric) {
541578
containers, err := c.infoProvider.SubcontainersInfo("/", &info.ContainerInfoRequest{NumStats: 1})
542579
if err != nil {
@@ -545,56 +582,32 @@ func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric)
545582
return
546583
}
547584
for _, container := range containers {
548-
baseLabels := []string{"id"}
549-
id := container.Name
550-
name := id
551-
if len(container.Aliases) > 0 {
552-
name = container.Aliases[0]
553-
baseLabels = append(baseLabels, "name")
554-
}
555-
image := container.Spec.Image
556-
if len(image) > 0 {
557-
baseLabels = append(baseLabels, "image")
558-
}
559-
baseLabelValues := []string{id, name, image}[:len(baseLabels)]
560-
561-
if c.containerNameToLabels != nil {
562-
newLabels := c.containerNameToLabels(name)
563-
for k, v := range newLabels {
564-
baseLabels = append(baseLabels, sanitizeLabelName(k))
565-
baseLabelValues = append(baseLabelValues, v)
566-
}
567-
}
568-
569-
for k, v := range container.Spec.Labels {
570-
baseLabels = append(baseLabels, sanitizeLabelName(containerLabelPrefix+k))
571-
baseLabelValues = append(baseLabelValues, v)
572-
}
573-
for k, v := range container.Spec.Envs {
574-
baseLabels = append(baseLabels, sanitizeLabelName(containerEnvPrefix+k))
575-
baseLabelValues = append(baseLabelValues, v)
585+
labels, values := []string{}, []string{}
586+
for l, v := range c.containerLabelsFunc(container) {
587+
labels = append(labels, sanitizeLabelName(l))
588+
values = append(values, v)
576589
}
577590

578591
// Container spec
579-
desc := prometheus.NewDesc("container_start_time_seconds", "Start time of the container since unix epoch in seconds.", baseLabels, nil)
580-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.CreationTime.Unix()), baseLabelValues...)
592+
desc := prometheus.NewDesc("container_start_time_seconds", "Start time of the container since unix epoch in seconds.", labels, nil)
593+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.CreationTime.Unix()), values...)
581594

582595
if container.Spec.HasCpu {
583-
desc = prometheus.NewDesc("container_spec_cpu_period", "CPU period of the container.", baseLabels, nil)
584-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Period), baseLabelValues...)
596+
desc = prometheus.NewDesc("container_spec_cpu_period", "CPU period of the container.", labels, nil)
597+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Period), values...)
585598
if container.Spec.Cpu.Quota != 0 {
586-
desc = prometheus.NewDesc("container_spec_cpu_quota", "CPU quota of the container.", baseLabels, nil)
587-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Quota), baseLabelValues...)
599+
desc = prometheus.NewDesc("container_spec_cpu_quota", "CPU quota of the container.", labels, nil)
600+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Quota), values...)
588601
}
589-
desc := prometheus.NewDesc("container_spec_cpu_shares", "CPU share of the container.", baseLabels, nil)
590-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Limit), baseLabelValues...)
602+
desc := prometheus.NewDesc("container_spec_cpu_shares", "CPU share of the container.", labels, nil)
603+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.Cpu.Limit), values...)
591604

592605
}
593606
if container.Spec.HasMemory {
594-
desc := prometheus.NewDesc("container_spec_memory_limit_bytes", "Memory limit for the container.", baseLabels, nil)
595-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.Limit), baseLabelValues...)
596-
desc = prometheus.NewDesc("container_spec_memory_swap_limit_bytes", "Memory swap limit for the container.", baseLabels, nil)
597-
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.SwapLimit), baseLabelValues...)
607+
desc := prometheus.NewDesc("container_spec_memory_limit_bytes", "Memory limit for the container.", labels, nil)
608+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.Limit), values...)
609+
desc = prometheus.NewDesc("container_spec_memory_swap_limit_bytes", "Memory swap limit for the container.", labels, nil)
610+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, specMemoryValue(container.Spec.Memory.SwapLimit), values...)
598611
}
599612

600613
// Now for the actual metrics
@@ -603,9 +616,9 @@ func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric)
603616
if cm.condition != nil && !cm.condition(container.Spec) {
604617
continue
605618
}
606-
desc := cm.desc(baseLabels)
619+
desc := cm.desc(labels)
607620
for _, metricValue := range cm.getValues(stats) {
608-
ch <- prometheus.MustNewConstMetric(desc, cm.valueType, float64(metricValue.value), append(baseLabelValues, metricValue.labels...)...)
621+
ch <- prometheus.MustNewConstMetric(desc, cm.valueType, float64(metricValue.value), append(values, metricValue.labels...)...)
609622
}
610623
}
611624
}

metrics/prometheus_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ var (
180180
)
181181

182182
func TestPrometheusCollector(t *testing.T) {
183-
c := NewPrometheusCollector(testSubcontainersInfoProvider{}, func(name string) map[string]string {
184-
return map[string]string{
185-
"zone.name": "hello",
186-
}
183+
c := NewPrometheusCollector(testSubcontainersInfoProvider{}, func(container *info.ContainerInfo) map[string]string {
184+
s := DefaultContainerLabels(container)
185+
s["zone.name"] = "hello"
186+
return s
187187
})
188188
prometheus.MustRegister(c)
189189
defer prometheus.Unregister(c)
@@ -212,7 +212,7 @@ func testPrometheusCollector(t *testing.T, c *PrometheusCollector, metricsFile s
212212
continue
213213
}
214214
if want != gotLines[i] {
215-
t.Fatalf("want %s, got %s", want, gotLines[i])
215+
t.Fatalf("unexpected metric line\nwant: %s\nhave: %s", want, gotLines[i])
216216
}
217217
}
218218
}
@@ -250,10 +250,10 @@ func TestPrometheusCollector_scrapeFailure(t *testing.T) {
250250
shouldFail: true,
251251
}
252252

253-
c := NewPrometheusCollector(provider, func(name string) map[string]string {
254-
return map[string]string{
255-
"zone.name": "hello",
256-
}
253+
c := NewPrometheusCollector(provider, func(container *info.ContainerInfo) map[string]string {
254+
s := DefaultContainerLabels(container)
255+
s["zone.name"] = "hello"
256+
return s
257257
})
258258
prometheus.MustRegister(c)
259259
defer prometheus.Unregister(c)

0 commit comments

Comments
 (0)