Skip to content

Commit

Permalink
Merge pull request #127 from launchdarkly/eb/ch81724/metrics1
Browse files Browse the repository at this point in the history
(v6 - #5) move some metrics code around for clarity
  • Loading branch information
eli-darkly authored Jul 9, 2020
2 parents 9bea6c3 + 6db47d1 commit 6aeb1fd
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 244 deletions.
29 changes: 29 additions & 0 deletions internal/metrics/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package metrics

import (
"time"

"go.opencensus.io/tag"
)

const (
defaultMetricsPrefix = "launchdarkly_relay"

browserTagValue = "browser"
mobileTagValue = "mobile"
serverTagValue = "server"

defaultFlushInterval = time.Minute
)

var (
relayIdTagKey, _ = tag.NewKey("relayId")
platformCategoryTagKey, _ = tag.NewKey("platformCategory")
userAgentTagKey, _ = tag.NewKey("userAgent")
routeTagKey, _ = tag.NewKey("route")
methodTagKey, _ = tag.NewKey("method")
envNameTagKey, _ = tag.NewKey("env")

publicTags = []tag.Key{platformCategoryTagKey, userAgentTagKey, envNameTagKey}
privateTags = []tag.Key{platformCategoryTagKey, userAgentTagKey, relayIdTagKey, envNameTagKey}
)
4 changes: 2 additions & 2 deletions internal/metrics/datadog.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

func init() {
defineExporter(datadogExporter, registerDatadogExporter)
defineExporter(datadogExporterType, registerDatadogExporter)
}

type DatadogOptions struct {
Expand All @@ -23,7 +23,7 @@ type DatadogOptions struct {
}

func (d DatadogOptions) getType() ExporterType {
return datadogExporter
return datadogExporterType
}

type DatadogConfig config.DatadogConfig
Expand Down
15 changes: 0 additions & 15 deletions internal/metrics/datadog_go19.go

This file was deleted.

77 changes: 77 additions & 0 deletions internal/metrics/exporters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package metrics

import (
"fmt"

"go.opencensus.io/stats/view"

"github.com/launchdarkly/ld-relay/v6/config"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog"
)

type ExporterType string

const (
datadogExporterType ExporterType = "Datadog"
stackdriverExporterType ExporterType = "Stackdriver"
prometheusExporterType ExporterType = "Prometheus"
)

type ExporterOptions interface {
getType() ExporterType
}

type ExporterRegisterer func(options ExporterOptions) error

// ExporterConfig is used internally to hold options for metrics integrations.
type ExporterConfig interface {
toOptions() ExporterOptions
enabled() bool
}

func ExporterOptionsFromConfig(c config.MetricsConfig) (options []ExporterOptions) {
exporterConfigs := []ExporterConfig{
DatadogConfig(c.Datadog),
StackdriverConfig(c.Stackdriver),
PrometheusConfig(c.Prometheus)}
for _, e := range exporterConfigs {
if e.enabled() {
options = append(options, e.toOptions())
}
}
return options
}

func getPrefix(c config.CommonMetricsConfig) string {
if c.Prefix != "" {
return c.Prefix
}
return defaultMetricsPrefix
}

func defineExporter(exporterType ExporterType, registerer ExporterRegisterer) {
exporters[exporterType] = registerer
}

func RegisterExporters(options []ExporterOptions, loggers ldlog.Loggers) (registrationErr error) {
registerPublicExportersOnce.Do(func() {
for _, o := range options {
exporter := exporters[o.getType()]
if exporter == nil {
registrationErr = fmt.Errorf("Got unexpected exporter type: %s", o.getType())
return
} else if err := exporter(o); err != nil {
registrationErr = fmt.Errorf("Could not register %s exporter: %s", o.getType(), err)
return
} else {
loggers.Infof("Successfully registered %s exporter.", o.getType())
}
}

err := view.Register(getPublicViews()...)
if err != nil {
registrationErr = fmt.Errorf("Error registering metrics views")
}
})
return registrationErr
}
67 changes: 67 additions & 0 deletions internal/metrics/globals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package metrics

import (
"sync"

"github.com/pborman/uuid"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)

var (
exporters = map[ExporterType]ExporterRegisterer{}
registerPublicExportersOnce sync.Once
registerPrivateViewsOnce sync.Once
metricsRelayId string

browserTags []tag.Mutator
mobileTags []tag.Mutator
serverTags []tag.Mutator

publicConnView *view.View
publicNewConnView *view.View
requestView *view.View
privateConnView *view.View
privateNewConnView *view.View
)

func init() {
metricsRelayId = uuid.New()
browserTags = append(browserTags, tag.Insert(platformCategoryTagKey, browserTagValue))
mobileTags = append(mobileTags, tag.Insert(platformCategoryTagKey, mobileTagValue))
serverTags = append(serverTags, tag.Insert(platformCategoryTagKey, serverTagValue))

publicConnView = &view.View{
Measure: connMeasure,
Aggregation: view.Sum(),
TagKeys: publicTags,
}
publicNewConnView = &view.View{
Measure: newConnMeasure,
Aggregation: view.Sum(),
TagKeys: publicTags,
}
requestView = &view.View{
Measure: requestMeasure,
Aggregation: view.Count(),
TagKeys: append(publicTags, routeTagKey, methodTagKey),
}
privateConnView = &view.View{
Measure: privateConnMeasure,
Aggregation: view.Sum(),
TagKeys: privateTags,
}
privateNewConnView = &view.View{
Measure: privateNewConnMeasure,
Aggregation: view.Sum(),
TagKeys: privateTags,
}
}

func getPublicViews() []*view.View {
return []*view.View{publicConnView, publicNewConnView, requestView}
}

func getPrivateViews() []*view.View {
return []*view.View{privateConnView, privateNewConnView}
}
79 changes: 79 additions & 0 deletions internal/metrics/measures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package metrics

import (
"context"

"go.opencensus.io/stats"
"go.opencensus.io/tag"
"go.opencensus.io/trace"

"github.com/launchdarkly/ld-relay/v6/internal/logging"
)

var (
// For internal event exporter
privateConnMeasure = stats.Int64("internal_connections", "current number of connections", stats.UnitDimensionless)
privateNewConnMeasure = stats.Int64("internal_newconnections", "total number of connections", stats.UnitDimensionless)

connMeasure = stats.Int64("connections", "current number of connections", stats.UnitDimensionless)
newConnMeasure = stats.Int64("newconnections", "total number of connections", stats.UnitDimensionless)
requestMeasure = stats.Int64("requests", "Number of hits to a route", stats.UnitDimensionless)

BrowserConns = Measure{measures: []*stats.Int64Measure{connMeasure, privateConnMeasure}, tags: &browserTags}
MobileConns = Measure{measures: []*stats.Int64Measure{connMeasure, privateConnMeasure}, tags: &mobileTags}
ServerConns = Measure{measures: []*stats.Int64Measure{connMeasure, privateConnMeasure}, tags: &serverTags}

NewBrowserConns = Measure{measures: []*stats.Int64Measure{newConnMeasure, privateNewConnMeasure}, tags: &browserTags}
NewMobileConns = Measure{measures: []*stats.Int64Measure{newConnMeasure, privateNewConnMeasure}, tags: &mobileTags}
NewServerConns = Measure{measures: []*stats.Int64Measure{newConnMeasure, privateNewConnMeasure}, tags: &serverTags}

BrowserRequests = Measure{measures: []*stats.Int64Measure{requestMeasure}, tags: &browserTags}
MobileRequests = Measure{measures: []*stats.Int64Measure{requestMeasure}, tags: &mobileTags}
ServerRequests = Measure{measures: []*stats.Int64Measure{requestMeasure}, tags: &serverTags}
)

type Measure struct {
measures []*stats.Int64Measure
tags *[]tag.Mutator
}

func WithGauge(ctx context.Context, userAgent string, f func(), measure Measure) {
ctx, err := tag.New(ctx, tag.Insert(userAgentTagKey, sanitizeTagValue(userAgent)))
if err != nil {
logging.GetGlobalContextLoggers(ctx).Errorf(`Failed to create tags: %s`, err)
} else {
for _, m := range measure.measures {
ctx, _ := tag.New(ctx, *measure.tags...)
stats.Record(ctx, m.M(1))
defer stats.Record(ctx, m.M(-1))
}
}
f()
}

func WithCount(ctx context.Context, userAgent string, f func(), measure Measure) {
ctx, err := tag.New(ctx, tag.Insert(userAgentTagKey, sanitizeTagValue(userAgent)))
if err != nil {
logging.GetGlobalContextLoggers(ctx).Errorf(`Failed to create tag for user agent : %s`, err)
} else {
for _, m := range measure.measures {
ctx, _ := tag.New(ctx, *measure.tags...)
stats.Record(ctx, m.M(1))
}
}
f()
}

// WithRouteCount Records a route hit and starts a trace. For stream connections, the duration of the stream connection is recorded
func WithRouteCount(ctx context.Context, userAgent, route, method string, f func(), measure Measure) {
tagCtx, err := tag.New(ctx, tag.Insert(routeTagKey, sanitizeTagValue(route)), tag.Insert(methodTagKey, sanitizeTagValue(method)))
if err != nil {
logging.GetGlobalContextLoggers(ctx).Errorf(`Failed to create tags for route "%s %s": %s`, method, route, err)
} else {
ctx = tagCtx
}
ctx, span := trace.StartSpan(ctx, route)
defer span.End()

WithCount(ctx, userAgent, f, measure)
}
Loading

0 comments on commit 6aeb1fd

Please sign in to comment.