From 0776a6d55799c1f3d983178dc2f364a776057206 Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Mon, 3 Apr 2023 16:41:59 -0700 Subject: [PATCH] Add a secondary label map to `prometheus.Data`. This is useful for metrics where some labels are specific to the `Data` struct in question, while others are shared. It is wasteful to create unique maps for each `*Data` when they contain mostly the same labels. There used to only be one such metric that requires data labels, but an upcoming change will change that, making this change worthwhile to avoid wasting memory. PiperOrigin-RevId: 521596697 --- pkg/prometheus/prometheus.go | 20 ++++++++++++++++++-- runsc/cmd/metric_server.go | 21 +++++---------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/prometheus/prometheus.go b/pkg/prometheus/prometheus.go index f645065b7..3d503219f 100644 --- a/pkg/prometheus/prometheus.go +++ b/pkg/prometheus/prometheus.go @@ -314,6 +314,15 @@ type Data struct { // This may be merged with other labels during export. Labels map[string]string `json:"labels,omitempty"` + // ExternalLabels are more labels merged together with `Labels`. + // They can be set using SetExternalLabels. + // They are useful in the case where a single Data needs labels from two sources: + // labels specific to this data point (which should be in `Labels`), and labels + // that are shared between multiple data points (stored in `ExternalLabels`). + // This avoids allocating unique `Labels` maps for each Data struct, when + // most of the actual labels would be shared between them. + ExternalLabels map[string]string `json:"external_labels,omitempty"` + // At most one of the fields below may be set. // Which one depends on the type of the metric. @@ -344,6 +353,13 @@ func LabeledFloatData(metric *Metric, labels map[string]string, val float64) *Da return &Data{Metric: metric, Labels: labels, Number: NewFloat(val)} } +// SetExternalLabels sets d.ExternalLabels. See its docstring for more information. +// Returns `d` for chainability. +func (d *Data) SetExternalLabels(externalLabels map[string]string) *Data { + d.ExternalLabels = externalLabels + return d +} + // ExportOptions contains options that control how metric data is exported in Prometheus format. type ExportOptions struct { // CommentHeader is prepended as a comment before any metric data is exported. @@ -650,9 +666,9 @@ func (d *Data) writeLabelsTo(w io.Writer, extraLabels map[string]string, leLabel } var orderedLabels <-chan LabelOrError if leLabel != nil { - orderedLabels = OrderedLabels(d.Labels, extraLabels, map[string]string{"le": leLabel.String()}) + orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels, map[string]string{"le": leLabel.String()}) } else { - orderedLabels = OrderedLabels(d.Labels, extraLabels) + orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels) } firstLabel := true var foundError error diff --git a/runsc/cmd/metric_server.go b/runsc/cmd/metric_server.go index 08673a69c..16337820d 100644 --- a/runsc/cmd/metric_server.go +++ b/runsc/cmd/metric_server.go @@ -89,11 +89,6 @@ type servedSandbox struct { // Once set, it is immutable. createdAt time.Time - // labelsWithMetadata is the union of `extraLabels` and `sandbox.MetricMetadata`. - // This is exported as the set of labels for the `sandbox_metadata` metric. - // Once set, it is immutable. - labelsWithMetadata map[string]string - // verifier allows verifying the data integrity of the metrics we get from this sandbox. // It is not always initialized when the sandbox is discovered, but rather upon first metrics // access to the sandbox. Metric registration data is loaded from the root container's @@ -197,13 +192,6 @@ func (s *servedSandbox) load() (*sandbox.Sandbox, *prometheus.Verifier, error) { s.verifier = verifier s.cleanupVerifier = cleanup } - s.labelsWithMetadata = make(map[string]string, len(s.extraLabels)+len(s.sandbox.MetricMetadata)) - for k, v := range s.extraLabels { - s.labelsWithMetadata[k] = v - } - for k, v := range s.sandbox.MetricMetadata { - s.labelsWithMetadata[k] = v - } return s.sandbox, s.verifier, nil } @@ -691,16 +679,17 @@ func (m *MetricServer) serveMetrics(w http.ResponseWriter, req *http.Request) ht func() { metricsMu.Lock() defer metricsMu.Unlock() - selfMetrics.Add(prometheus.LabeledIntData(&SandboxPresenceMetric, served.extraLabels, 1)) + selfMetrics.Add(prometheus.LabeledIntData(&SandboxPresenceMetric, nil, 1).SetExternalLabels(served.extraLabels)) sandboxRunning := int64(0) if isRunning { sandboxRunning = 1 meta.numRunningSandboxes++ } - selfMetrics.Add(prometheus.LabeledIntData(&SandboxRunningMetric, served.extraLabels, sandboxRunning)) + selfMetrics.Add(prometheus.LabeledIntData(&SandboxRunningMetric, nil, sandboxRunning).SetExternalLabels(served.extraLabels)) if loadErr == nil { - selfMetrics.Add(prometheus.LabeledIntData(&SandboxMetadataMetric, served.labelsWithMetadata, 1)) - selfMetrics.Add(prometheus.LabeledFloatData(&SandboxCreationMetric, served.extraLabels, float64(served.createdAt.Unix())+(float64(served.createdAt.Nanosecond())/1e9))) + selfMetrics.Add(prometheus.LabeledIntData(&SandboxMetadataMetric, sand.MetricMetadata, 1).SetExternalLabels(served.extraLabels)) + createdAt := float64(served.createdAt.Unix()) + (float64(served.createdAt.Nanosecond()) / 1e9) + selfMetrics.Add(prometheus.LabeledFloatData(&SandboxCreationMetric, nil, createdAt).SetExternalLabels(served.extraLabels)) } if sandboxErr != nil { // If the sandbox isn't running, it is normal that metrics are not exported for it, so