Skip to content

Commit

Permalink
Introduce compat package to ease migration from the old armon/go-me…
Browse files Browse the repository at this point in the history
…trice module name to the current hashicorp/go-metrics name (#169)

* Introduce `compat` package to ease migration from the old armon/go-metrics module name to the current hashicorp/go-metrics name

* Update README.md to include backwards compatibility info

* Fix typos
  • Loading branch information
mkeeler authored Jan 8, 2025
1 parent c5fef7b commit e30ca64
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 3 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,46 @@ By default, both `Config.AllowedLabels` and `Config.BlockedLabels` are nil, mean
no tags are filtered at all, but it allows a user to globally block some tags with high
cardinality at the application level.

Backwards Compatibility
-----------------------
v0.5.0 of the library renamed the Go module from `github.com/armon/go-metrics` to `github.com/hashicorp/go-metrics`.
While this did not introduce any breaking changes to the API, the change did subtly break backwards compatibility.

In essence, Go treats a renamed module as entirely distinct and will happily compile both modules into the same binary.
Due to most uses of the go-metrics library involving emitting metrics via the global metrics handler, having two global
metrics handlers could cause a subset of metrics to be effectively lost. As an example, if your application configures
go-metrics exporting via the `armon` namespace, then any metrics sent to go-metrics via the `hashicorp` namespaced module
will never get exported.

Eventually all usage of `armon/go-metrics` should be replaced with usage of `hashicorp/go-metrics`. However, a single
point-in-time coordinated update across all libraries that an application may depend on isn't always feasible. To facilitate migrations,
a `github.com/hashicorp/go-metrics/compat` package has been introduced. This package and sub-packages are API compatible with
`armon/go-metrics`. Libraries should be updated to use this package for emitting metrics via the global handlers. Internally,
the package will route metrics to either `armon/go-metrics` or `hashicorp/go-metrics`. This is achieved at a global level
within an application via the use of Go build tags.

**Build Tags**
* `armonmetrics` - Using this tag will cause metrics to be routed to `armon/go-metrics`
* `hashicorpmetrics` - Using this tag will cause all metrics to be routed to `hashicorp/go-metrics`

If no build tag is specified, the default behavior is to use `armon/go-metrics`. The overall migration path would be as follows:

1. Upgrade libraries using `armon/go-metrics` to consume `hashicorp/go-metrics/compat` instead.
2. Update library dependencies of applications that use `armon/go-metrics`.
* This doesn't need to be one big atomic update but can be slower due to the default behavior remaining unaltered.
* At this point all metrics will still be emitted to `armon/go-metrics`
3. Update the application to use `hashicorp/go-metrics`
* Replace all application imports of `github.com/armon/go-metrics` with `github.com/hashicorp/go-metrics`
* Libraries are unaltered at this stage.
* Instrument your build system to build with the `hashicorpmetrics` tag.

Your migration is effectively finished and your application is now exclusively using `hashicorp/go-metrics`. A future release of the library
will change the default behavior to use `hashicorp/go-metrics` instead of `armon/go-metrics`. At that point in time, any application that
needs more time before performing the migration must instrument their build system to include the `armonmetrics` tag. A subsequent release
after that will eventually remove the compatibility layer all together. The rough timeline for this will be mid-2025 for changing the default
behavior and then the end of 2025 for removal of the compatibility layer.


Examples
--------

Expand Down
129 changes: 129 additions & 0 deletions compat/armon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//go:build armonmetrics || ignore || !hashicorpmetrics
// +build armonmetrics ignore !hashicorpmetrics

package metrics

import (
"io"
"net/url"
"syscall"
"time"

"github.com/armon/go-metrics"
)

const (
// DefaultSignal is used with DefaultInmemSignal
DefaultSignal = metrics.DefaultSignal
)

func AddSample(key []string, val float32) {
metrics.AddSample(key, val)
}
func AddSampleWithLabels(key []string, val float32, labels []Label) {
metrics.AddSampleWithLabels(key, val, labels)
}
func EmitKey(key []string, val float32) {
metrics.EmitKey(key, val)
}
func IncrCounter(key []string, val float32) {
metrics.IncrCounter(key, val)
}
func IncrCounterWithLabels(key []string, val float32, labels []Label) {
metrics.IncrCounterWithLabels(key, val, labels)
}
func MeasureSince(key []string, start time.Time) {
metrics.MeasureSince(key, start)
}
func MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
metrics.MeasureSinceWithLabels(key, start, labels)
}
func SetGauge(key []string, val float32) {
metrics.SetGauge(key, val)
}
func SetGaugeWithLabels(key []string, val float32, labels []Label) {
metrics.SetGaugeWithLabels(key, val, labels)
}
func Shutdown() {
metrics.Shutdown()
}
func UpdateFilter(allow, block []string) {
metrics.UpdateFilter(allow, block)
}
func UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) {
metrics.UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels)
}

type AggregateSample = metrics.AggregateSample
type BlackholeSink = metrics.BlackholeSink
type Config = metrics.Config
type Encoder = metrics.Encoder
type FanoutSink = metrics.FanoutSink
type GaugeValue = metrics.GaugeValue
type InmemSignal = metrics.InmemSignal
type InmemSink = metrics.InmemSink
type IntervalMetrics = metrics.IntervalMetrics
type Label = metrics.Label
type MetricSink = metrics.MetricSink
type Metrics = metrics.Metrics
type MetricsSummary = metrics.MetricsSummary
type PointValue = metrics.PointValue
type SampledValue = metrics.SampledValue
type ShutdownSink = metrics.ShutdownSink
type StatsdSink = metrics.StatsdSink
type StatsiteSink = metrics.StatsiteSink

func DefaultConfig(serviceName string) *Config {
return metrics.DefaultConfig(serviceName)
}

func DefaultInmemSignal(inmem *InmemSink) *InmemSignal {
return metrics.DefaultInmemSignal(inmem)
}
func NewInmemSignal(inmem *InmemSink, sig syscall.Signal, w io.Writer) *InmemSignal {
return metrics.NewInmemSignal(inmem, sig, w)
}

func NewInmemSink(interval, retain time.Duration) *InmemSink {
return metrics.NewInmemSink(interval, retain)
}

func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
return metrics.NewIntervalMetrics(intv)
}

func NewInmemSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewInmemSinkFromURL(u)
}

func NewMetricSinkFromURL(urlStr string) (MetricSink, error) {
return metrics.NewMetricSinkFromURL(urlStr)
}

func NewStatsdSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewStatsdSinkFromURL(u)
}

func NewStatsiteSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewStatsiteSinkFromURL(u)
}

func Default() *Metrics {
return metrics.Default()
}

func New(conf *Config, sink MetricSink) (*Metrics, error) {
return metrics.New(conf, sink)
}

func NewGlobal(conf *Config, sink MetricSink) (*Metrics, error) {
return metrics.NewGlobal(conf, sink)
}

func NewStatsdSink(addr string) (*StatsdSink, error) {
return metrics.NewStatsdSink(addr)
}

func NewStatsiteSink(addr string) (*StatsiteSink, error) {
return metrics.NewStatsiteSink(addr)
}
15 changes: 15 additions & 0 deletions compat/circonus/armon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build armonmetrics || ignore || !hashicorpmetrics
// +build armonmetrics ignore !hashicorpmetrics

package circonus

import (
"github.com/armon/go-metrics/circonus"
)

type CirconusSink = circonus.CirconusSink
type Config = circonus.Config

func NewCirconusSink(cc *Config) (*CirconusSink, error) {
return circonus.NewCirconusSink(cc)
}
15 changes: 15 additions & 0 deletions compat/circonus/hashicorp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build hashicorpmetrics
// +build hashicorpmetrics

package circonus

import (
"github.com/hashicorp/go-metrics/circonus"
)

type CirconusSink = circonus.CirconusSink
type Config = circonus.Config

func NewCirconusSink(cc *Config) (*CirconusSink, error) {
return circonus.NewCirconusSink(cc)
}
14 changes: 14 additions & 0 deletions compat/datadog/armon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build armonmetrics || ignore || !hashicorpmetrics
// +build armonmetrics ignore !hashicorpmetrics

package datadog

import (
"github.com/armon/go-metrics/datadog"
)

type DogStatsdSink = datadog.DogStatsdSink

func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) {
return datadog.NewDogStatsdSink(addr, hostName)
}
14 changes: 14 additions & 0 deletions compat/datadog/hashicorp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build hashicorpmetrics
// +build hashicorpmetrics

package datadog

import (
"github.com/hashicorp/go-metrics/datadog"
)

type DogStatsdSink = datadog.DogStatsdSink

func NewDogStatsdSink(addr string, hostName string) (*DogStatsdSink, error) {
return datadog.NewDogStatsdSink(addr, hostName)
}
129 changes: 129 additions & 0 deletions compat/hashicorp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//go:build hashicorpmetrics
// +build hashicorpmetrics

package metrics

import (
"io"
"net/url"
"syscall"
"time"

"github.com/hashicorp/go-metrics"
)

const (
// DefaultSignal is used with DefaultInmemSignal
DefaultSignal = metrics.DefaultSignal
)

func AddSample(key []string, val float32) {
metrics.AddSample(key, val)
}
func AddSampleWithLabels(key []string, val float32, labels []Label) {
metrics.AddSampleWithLabels(key, val, labels)
}
func EmitKey(key []string, val float32) {
metrics.EmitKey(key, val)
}
func IncrCounter(key []string, val float32) {
metrics.IncrCounter(key, val)
}
func IncrCounterWithLabels(key []string, val float32, labels []Label) {
metrics.IncrCounterWithLabels(key, val, labels)
}
func MeasureSince(key []string, start time.Time) {
metrics.MeasureSince(key, start)
}
func MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
metrics.MeasureSinceWithLabels(key, start, labels)
}
func SetGauge(key []string, val float32) {
metrics.SetGauge(key, val)
}
func SetGaugeWithLabels(key []string, val float32, labels []Label) {
metrics.SetGaugeWithLabels(key, val, labels)
}
func Shutdown() {
metrics.Shutdown()
}
func UpdateFilter(allow, block []string) {
metrics.UpdateFilter(allow, block)
}
func UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) {
metrics.UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels)
}

type AggregateSample = metrics.AggregateSample
type BlackholeSink = metrics.BlackholeSink
type Config = metrics.Config
type Encoder = metrics.Encoder
type FanoutSink = metrics.FanoutSink
type GaugeValue = metrics.GaugeValue
type InmemSignal = metrics.InmemSignal
type InmemSink = metrics.InmemSink
type IntervalMetrics = metrics.IntervalMetrics
type Label = metrics.Label
type MetricSink = metrics.MetricSink
type Metrics = metrics.Metrics
type MetricsSummary = metrics.MetricsSummary
type PointValue = metrics.PointValue
type SampledValue = metrics.SampledValue
type ShutdownSink = metrics.ShutdownSink
type StatsdSink = metrics.StatsdSink
type StatsiteSink = metrics.StatsiteSink

func DefaultConfig(serviceName string) *Config {
return metrics.DefaultConfig(serviceName)
}

func DefaultInmemSignal(inmem *InmemSink) *InmemSignal {
return metrics.DefaultInmemSignal(inmem)
}
func NewInmemSignal(inmem *InmemSink, sig syscall.Signal, w io.Writer) *InmemSignal {
return metrics.NewInmemSignal(inmem, sig, w)
}

func NewInmemSink(interval, retain time.Duration) *InmemSink {
return metrics.NewInmemSink(interval, retain)
}

func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
return metrics.NewIntervalMetrics(intv)
}

func NewInmemSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewInmemSinkFromURL(u)
}

func NewMetricSinkFromURL(urlStr string) (MetricSink, error) {
return metrics.NewMetricSinkFromURL(urlStr)
}

func NewStatsdSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewStatsdSinkFromURL(u)
}

func NewStatsiteSinkFromURL(u *url.URL) (MetricSink, error) {
return metrics.NewStatsiteSinkFromURL(u)
}

func Default() *Metrics {
return metrics.Default()
}

func New(conf *Config, sink MetricSink) (*Metrics, error) {
return metrics.New(conf, sink)
}

func NewGlobal(conf *Config, sink MetricSink) (*Metrics, error) {
return metrics.NewGlobal(conf, sink)
}

func NewStatsdSink(addr string) (*StatsdSink, error) {
return metrics.NewStatsdSink(addr)
}

func NewStatsiteSink(addr string) (*StatsiteSink, error) {
return metrics.NewStatsiteSink(addr)
}
Loading

0 comments on commit e30ca64

Please sign in to comment.