Skip to content

Commit

Permalink
Sanitize metric names before creation (flyteorg#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
EngHabu authored Feb 9, 2022
1 parent df5879c commit 63db7a6
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 27 deletions.
5 changes: 3 additions & 2 deletions promutils/labeled/counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

// Represents a counter labeled with values from the context. See labeled.SetMetricsKeys for information about how to
// Counter represents a counter labeled with values from the context. See labeled.SetMetricsKeys for information about to
// configure that.
type Counter struct {
*prometheus.CounterVec
Expand Down Expand Up @@ -45,7 +45,7 @@ func (c Counter) Add(ctx context.Context, v float64) {
}
}

// Creates a new labeled counter. Label keys must be set before instantiating a counter. See labeled.SetMetricsKeys for
// NewCounter creates a new labeled counter. Label keys must be set before instantiating a counter. See labeled.SetMetricsKeys for
// information about to configure that.
func NewCounter(name, description string, scope promutils.Scope, opts ...MetricOption) Counter {
if len(metricKeys) == 0 {
Expand All @@ -54,6 +54,7 @@ func NewCounter(name, description string, scope promutils.Scope, opts ...MetricO

c := Counter{}

name = promutils.SanitizeMetricName(name)
for _, opt := range opts {
if _, emitUnlabeledMetric := opt.(EmitUnlabeledMetricOption); emitUnlabeledMetric {
c.Counter = scope.MustNewCounter(GetUnlabeledMetricName(name), description)
Expand Down
5 changes: 3 additions & 2 deletions promutils/labeled/gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

// Represents a gauge labeled with values from the context. See labeled.SetMetricsKeys for more information
// Gauge represents a gauge labeled with values from the context. See labeled.SetMetricsKeys for more information
type Gauge struct {
*prometheus.GaugeVec

Expand Down Expand Up @@ -99,7 +99,7 @@ func (g Gauge) SetToCurrentTime(ctx context.Context) {
}
}

// Creates a new labeled gauge. Label keys must be set before instantiating. If the unlabeled option is given,
// NewGauge creates a new labeled gauge. Label keys must be set before instantiating. If the unlabeled option is given,
// this object will also instantiate and emit another gauge with the given name with an _unlabeled suffix.
// See labeled.SetMetricsKeys for information about how to configure that.
func NewGauge(name, description string, scope promutils.Scope, opts ...MetricOption) Gauge {
Expand All @@ -108,6 +108,7 @@ func NewGauge(name, description string, scope promutils.Scope, opts ...MetricOpt
}

g := Gauge{}
name = promutils.SanitizeMetricName(name)
for _, opt := range opts {
if _, emitUnlabeledMetric := opt.(EmitUnlabeledMetricOption); emitUnlabeledMetric {
g.Gauge = scope.MustNewGauge(GetUnlabeledMetricName(name), description)
Expand Down
7 changes: 4 additions & 3 deletions promutils/labeled/stopwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (c StopWatch) Start(ctx context.Context) Timer {
}
}

// Observes specified duration between the start and end time. The data point will be labeled with values from context.
// Observe observes specified duration between the start and end time. The data point will be labeled with values from context.
// See labeled.SetMetricsKeys for information about how to configure that.
func (c StopWatch) Observe(ctx context.Context, start, end time.Time) {
w, err := c.StopWatchVec.GetMetricWith(contextutils.Values(ctx, append(metricKeys, c.additionalLabels...)...))
Expand All @@ -59,7 +59,7 @@ func (c StopWatch) Observe(ctx context.Context, start, end time.Time) {
}
}

// This method observes the elapsed duration since the creation of the timer. The timer is created using a StopWatch.
// Time observes the elapsed duration since the creation of the timer. The timer is created using a StopWatch.
// The data point will be labeled with values from context. See labeled.SetMetricsKeys for information about to
// configure that.
func (c StopWatch) Time(ctx context.Context, f func()) {
Expand All @@ -68,7 +68,7 @@ func (c StopWatch) Time(ctx context.Context, f func()) {
t.Stop()
}

// Creates a new labeled stopwatch. Label keys must be set before instantiating a counter. See labeled.SetMetricsKeys
// NewStopWatch creates a new labeled stopwatch. Label keys must be set before instantiating a counter. See labeled.SetMetricsKeys
// for information about how to configure that.
func NewStopWatch(name, description string, scale time.Duration, scope promutils.Scope, opts ...MetricOption) StopWatch {
if len(metricKeys) == 0 {
Expand All @@ -77,6 +77,7 @@ func NewStopWatch(name, description string, scale time.Duration, scope promutils

sw := StopWatch{}

name = promutils.SanitizeMetricName(name)
for _, opt := range opts {
if _, emitUnableMetric := opt.(EmitUnlabeledMetricOption); emitUnableMetric {
sw.StopWatch = scope.MustNewStopWatch(GetUnlabeledMetricName(name), description, scale)
Expand Down
55 changes: 35 additions & 20 deletions promutils/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ func (s StopWatchVec) GetMetricWith(labels prometheus.Labels) (StopWatch, error)
}, nil
}

// This is a stoppable instance of a StopWatch or a Timer
// Timer is a stoppable instance of a StopWatch or a Timer
// A Timer can only be stopped. On stopping it will output the elapsed duration to prometheus
type Timer struct {
start time.Time
outputScale time.Duration
timer prometheus.Observer
}

// This method observes the elapsed duration since the creation of the timer. The timer is created using a StopWatch
// Stop observes the elapsed duration since the creation of the timer. The timer is created using a StopWatch
func (s Timer) Stop() float64 {
observed := time.Since(s.start).Nanoseconds()
outputScaleDuration := s.outputScale.Nanoseconds()
Expand All @@ -124,78 +124,78 @@ type SummaryOptions struct {
// provide a prefix, but just the name of the metric. As long as the Scope is used to create a new instance of the metric
// The prefix (or scope) is automatically set.
type Scope interface {
// Creates new prometheus.Gauge metric with the prefix as the CurrentScope
// NewGauge creates new prometheus.Gauge metric with the prefix as the CurrentScope
// Name is a string that follows prometheus conventions (mostly [_a-z])
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewGauge(name, description string) (prometheus.Gauge, error)
MustNewGauge(name, description string) prometheus.Gauge

// Creates new prometheus.GaugeVec metric with the prefix as the CurrentScope
// NewGaugeVec creates new prometheus.GaugeVec metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewGaugeVec(name, description string, labelNames ...string) (*prometheus.GaugeVec, error)
MustNewGaugeVec(name, description string, labelNames ...string) *prometheus.GaugeVec

// Creates new prometheus.Summary metric with the prefix as the CurrentScope
// NewSummary creates new prometheus.Summary metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewSummary(name, description string) (prometheus.Summary, error)
MustNewSummary(name, description string) prometheus.Summary

// Creates new prometheus.Summary metric with custom options, such as a custom set of objectives (i.e., target quantiles).
// NewSummaryWithOptions creates new prometheus.Summary metric with custom options, such as a custom set of objectives (i.e., target quantiles).
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewSummaryWithOptions(name, description string, options SummaryOptions) (prometheus.Summary, error)
MustNewSummaryWithOptions(name, description string, options SummaryOptions) prometheus.Summary

// Creates new prometheus.SummaryVec metric with the prefix as the CurrentScope
// NewSummaryVec creates new prometheus.SummaryVec metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewSummaryVec(name, description string, labelNames ...string) (*prometheus.SummaryVec, error)
MustNewSummaryVec(name, description string, labelNames ...string) *prometheus.SummaryVec

// Creates new prometheus.Histogram metric with the prefix as the CurrentScope
// NewHistogram creates new prometheus.Histogram metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewHistogram(name, description string) (prometheus.Histogram, error)
MustNewHistogram(name, description string) prometheus.Histogram

// Creates new prometheus.HistogramVec metric with the prefix as the CurrentScope
// NewHistogramVec creates new prometheus.HistogramVec metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewHistogramVec(name, description string, labelNames ...string) (*prometheus.HistogramVec, error)
MustNewHistogramVec(name, description string, labelNames ...string) *prometheus.HistogramVec

// Creates new prometheus.Counter metric with the prefix as the CurrentScope
// NewCounter creates new prometheus.Counter metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
// Important to note, counters are not like typical counters. These are ever increasing and cumulative.
// So if you want to observe counters within buckets use Summary/Histogram
NewCounter(name, description string) (prometheus.Counter, error)
MustNewCounter(name, description string) prometheus.Counter

// Creates new prometheus.GaugeVec metric with the prefix as the CurrentScope
// NewCounterVec creates new prometheus.GaugeVec metric with the prefix as the CurrentScope
// Refer to https://prometheus.io/docs/concepts/metric_types/ for more information
NewCounterVec(name, description string, labelNames ...string) (*prometheus.CounterVec, error)
MustNewCounterVec(name, description string, labelNames ...string) *prometheus.CounterVec

// This is a custom wrapper to create a StopWatch object in the current Scope.
// NewStopWatch is a custom wrapper to create a StopWatch object in the current Scope.
// Duration is to specify the scale of the Timer. For example if you are measuring times in milliseconds
// pass scale=times.Millisecond
// https://golang.org/pkg/time/#Duration
// The metric name is auto-suffixed with the right scale. Refer to DurationToString to understand
NewStopWatch(name, description string, scale time.Duration) (StopWatch, error)
MustNewStopWatch(name, description string, scale time.Duration) StopWatch

// This is a custom wrapper to create a StopWatch object in the current Scope.
// NewStopWatchVec is a custom wrapper to create a StopWatch object in the current Scope.
// Duration is to specify the scale of the Timer. For example if you are measuring times in milliseconds
// pass scale=times.Millisecond
// https://golang.org/pkg/time/#Duration
// The metric name is auto-suffixed with the right scale. Refer to DurationToString to understand
NewStopWatchVec(name, description string, scale time.Duration, labelNames ...string) (*StopWatchVec, error)
MustNewStopWatchVec(name, description string, scale time.Duration, labelNames ...string) *StopWatchVec

// In case nesting is desired for metrics, create a new subScope. This is generally useful in creating
// NewSubScope creates a new subScope in case nesting is desired for metrics. This is generally useful in creating
// Scoped and SubScoped metrics
NewSubScope(name string) Scope

// Returns the current ScopeName. Use for creating your own metrics
// CurrentScope returns the current ScopeName. Use for creating your own metrics
CurrentScope() string

// Method that provides a scoped metric name. Can be used, if you want to directly create your own metric
// NewScopedMetricName provides a scoped metric name. Can be used, if you want to directly create your own metric
NewScopedMetricName(name string) string
}

Expand Down Expand Up @@ -398,13 +398,14 @@ func (m metricsScope) CurrentScope() string {
return m.scope
}

// Creates a metric name under the scope. Scope will always have a defaultScopeDelimiterRune as the last character
// NewScopedMetricName creates a metric name under the scope. Scope will always have a defaultScopeDelimiterRune as the
// last character
func (m metricsScope) NewScopedMetricName(name string) string {
if name == "" {
panic("metric name cannot be an empty string")
}

return m.scope + name
return SanitizeMetricName(m.scope + name)
}

func (m metricsScope) NewSubScope(subscopeName string) Scope {
Expand All @@ -421,7 +422,7 @@ func (m metricsScope) NewSubScope(subscopeName string) Scope {
return NewScope(m.scope + subscopeName)
}

// Creates a new scope in the format `name + defaultScopeDelimiterRune`
// NewScope creates a new scope in the format `name + defaultScopeDelimiterRune`
// If the last character is already a defaultScopeDelimiterRune, then it does not add it to the scope name
func NewScope(name string) Scope {
if name == "" {
Expand All @@ -438,7 +439,21 @@ func NewScope(name string) Scope {
}
}

// Returns a randomly-named scope for use in tests.
// SanitizeMetricName ensures the generates metric name is compatible with the underlying prometheus library.
func SanitizeMetricName(name string) string {
out := strings.Builder{}
for i, b := range name {
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0) {
out.WriteRune(b)
} else if b == '-' {
out.WriteRune('_')
}
}

return out.String()
}

// NewTestScope returns a randomly-named scope for use in tests.
// Prometheus requires that metric names begin with a single word, which is generated from the alphabetic testScopeNameCharset.
func NewTestScope() Scope {
return NewScope("test" + rand.String(6))
Expand Down

0 comments on commit 63db7a6

Please sign in to comment.