Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch Metrics Handler for Mutable State Metrics #6603

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions common/metrics/batchmetrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination batchmetrics_mock.go

package metrics

import "time"

// The BatchMetrics interfaces provide a way to emit "wide events" within the codebase. If a wide event
// implementation is not provided we default to the implementation provided below which will maintain
// backwards compatibility by emitting each field as an individual metric. In custom implementations, fields
// can be labeled using the metricDefinition.Name() accessor within the respective With() methods.

type (
BatchMetricsHandler interface {
CreateBatch(string, ...Tag) BatchMetric
}

BatchMetric interface {
WithHistogram(histogramDefinition, int64) BatchMetric
WithTimer(timerDefinition, time.Duration) BatchMetric
WithCounter(counterDefinition, int64) BatchMetric
WithGauge(gaugeDefinition, float64) BatchMetric
Emit()
}

BatchHandlerImpl struct {
metricsHandler Handler
}
BatchMetricImpl struct {
emitter Handler
}
)

func NewBatchMetricsHandler(metricHandler Handler) BatchHandlerImpl {
return BatchHandlerImpl{metricHandler}
}

func (bh BatchHandlerImpl) CreateBatch(_ string, tags ...Tag) BatchMetric {
emitter := bh.metricsHandler
if len(tags) > 0 {
emitter = emitter.WithTags(tags...)
}
return &BatchMetricImpl{
emitter: emitter,
}
}

func (bm *BatchMetricImpl) WithHistogram(def histogramDefinition, value int64) BatchMetric {
def.With(bm.emitter).Record(value)
return bm
}

func (bm *BatchMetricImpl) WithTimer(def timerDefinition, value time.Duration) BatchMetric {
def.With(bm.emitter).Record(value)
return bm
}

func (bm *BatchMetricImpl) WithCounter(def counterDefinition, value int64) BatchMetric {
def.With(bm.emitter).Record(value)
return bm
}

func (bm *BatchMetricImpl) WithGauge(def gaugeDefinition, value float64) BatchMetric {
def.With(bm.emitter).Record(value)
return bm
}

// Emit is a no-op in this implementation since we don't actually send wide events.
func (bm *BatchMetricImpl) Emit() {}
174 changes: 174 additions & 0 deletions common/metrics/batchmetrics_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions service/history/history_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type (
eventNotifier events.Notifier
tokenSerializer common.TaskTokenSerializer
metricsHandler metrics.Handler
batchMetricsHandler metrics.BatchMetricsHandler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already pretty annoying to pass around a bunch of these "context objects" like metrics.Handler and log.Logger (sometimes two loggers), so I hate to add more. Is there some way we could do this in the existing metrics.Handler? I guess we don't want to break existing implementations, so maybe the interface extension pattern? (though I know that has problems)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick look @dnr!

Sure, I agree plumbing this around the place is a pain. As you said, I don't want to break the existing interface so extending could work. Alternatively we could wrap the various observability handlers within some unified wrapper so we just have the one thing to pass around and we keep the batch handler distinct from the standard handler.

For the former I'd have BatchMetricsHandler extend metricsHandler and remove the basic metricsHandler from the context objects that were touched in this PR. Over time if and when other batch metrics are added in the codebase the batch handler can replace the old handler.

For the latter we'd have some kind of ObservabilityHandler containing the 3 types which replaces the Log / Metrics / BatchMetrics handlers in the relevant contexts. This would ultimately mean less things to pass around, the downside being the coupling of Logging with Metrics (though in practice, I'm not sure how much of an issue this would be).

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I discussed the approach with @njo originally, we had a different approach. metrics.Handler had a StartBatch() method that returns a BatchHandler, the BatchHandler is just a metrics.Handler and an io.Closer.

With that approach, you'd have:

batch := metricsHandler.StartBatch()
defer batch.Close()

// Record metrics on the batch
metrics.MutableStateSize.With(batch).Record(int64(stats.TotalSize))
metrics.ExecutionInfoSize.With(batch).Record(int64(stats.ExecutionInfoSize))
metrics.ExecutionStateSize.With(batch).Record(int64(stats.ExecutionStateSize))

The default implementation for a metrics handler would return itself with a nop closer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable @bergundy. I misunderstood when we talked that we'd be using the same metrics handler interface. To clarify, I'd be extending the existing interface here rather than creating a new Handler type that wraps the old one? So going through and updating our existing handler implementations and leaving folks with a custom handler to add the new batch method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have broken source-compatibility for users who use the server as a library before, but we try pretty hard not to. I feel like this case might be worth it, though, since it's very easy to fix. We should get a team consensus on that.

I like the ObservabilityHandler idea! We might need/want to do some unification of tags

logger log.Logger
throttledLogger log.Logger
config *configs.Config
Expand Down Expand Up @@ -200,6 +201,7 @@ func NewEngineWithShardContext(
logger: log.With(logger, tag.ComponentHistoryEngine),
throttledLogger: log.With(shard.GetThrottledLogger(), tag.ComponentHistoryEngine),
metricsHandler: shard.GetMetricsHandler(),
batchMetricsHandler: shard.GetBatchMetricsHandler(),
eventNotifier: eventNotifier,
config: config,
sdkClientFactory: sdkClientFactory,
Expand Down
1 change: 1 addition & 0 deletions service/history/shard/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type (
GetLogger() log.Logger
GetThrottledLogger() log.Logger
GetMetricsHandler() metrics.Handler
GetBatchMetricsHandler() metrics.BatchMetricsHandler
GetTimeSource() clock.TimeSource

GetRemoteAdminClient(string) (adminservice.AdminServiceClient, error)
Expand Down
2 changes: 2 additions & 0 deletions service/history/shard/context_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type (
HostInfoProvider membership.HostInfoProvider
Logger log.Logger
MetricsHandler metrics.Handler
BatchMetricsHandler metrics.BatchMetricsHandler
NamespaceRegistry namespace.Registry
PayloadSerializer serialization.Serializer
PersistenceExecutionManager persistence.ExecutionManager
Expand Down Expand Up @@ -110,6 +111,7 @@ func (c *contextFactoryImpl) CreateContext(
c.ClientBean,
c.HistoryClient,
c.MetricsHandler,
c.BatchMetricsHandler,
c.PayloadSerializer,
c.TimeSource,
c.NamespaceRegistry,
Expand Down
7 changes: 7 additions & 0 deletions service/history/shard/context_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type (
stringRepr string
executionManager persistence.ExecutionManager
metricsHandler metrics.Handler
batchMetricsHandler metrics.BatchMetricsHandler
eventsCache events.Cache
closeCallback CloseCallback
config *configs.Config
Expand Down Expand Up @@ -2063,6 +2064,7 @@ func newContext(
clientBean client.Bean,
historyClient historyservice.HistoryServiceClient,
metricsHandler metrics.Handler,
batchMetricsHandler metrics.BatchMetricsHandler,
payloadSerializer serialization.Serializer,
timeSource cclock.TimeSource,
namespaceRegistry namespace.Registry,
Expand Down Expand Up @@ -2097,6 +2099,7 @@ func newContext(
stringRepr: fmt.Sprintf("Shard(%d)", shardID),
executionManager: persistenceExecutionManager,
metricsHandler: metricsHandler,
batchMetricsHandler: batchMetricsHandler,
closeCallback: closeCallback,
config: historyConfig,
finalizer: finalizer.New(taggedLogger, metricsHandler),
Expand Down Expand Up @@ -2196,6 +2199,10 @@ func (s *ContextImpl) GetMetricsHandler() metrics.Handler {
return s.metricsHandler
}

func (s *ContextImpl) GetBatchMetricsHandler() metrics.BatchMetricsHandler {
return s.batchMetricsHandler
}

func (s *ContextImpl) GetTimeSource() cclock.TimeSource {
return s.timeSource
}
Expand Down
28 changes: 28 additions & 0 deletions service/history/shard/context_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions service/history/shard/context_testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func newTestContext(t *resourcetest.Test, eventsCache events.Cache, config Conte
stringRepr: fmt.Sprintf("Shard(%d)", config.ShardInfo.GetShardId()),
executionManager: executionManager,
metricsHandler: t.MetricsHandler,
batchMetricsHandler: metrics.NewBatchMetricsHandler(t.MetricsHandler),
eventsCache: eventsCache,
config: config.Config,
contextTaggedLogger: t.GetLogger(),
Expand Down
Loading
Loading