Skip to content

Commit 7d946c2

Browse files
Add directions to enable prom style metrics reporting (#714)
* Comments on how prom compatible metrics can be emitted * Test to make sure prom compatible metrics work
1 parent 25c98a4 commit 7d946c2

File tree

2 files changed

+119
-8
lines changed

2 files changed

+119
-8
lines changed

internal/common/metrics/service_wrapper_test.go

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,43 @@
2121
package metrics
2222

2323
import (
24+
"fmt"
2425
"io"
2526
"reflect"
27+
"strings"
2628
"testing"
2729
"time"
2830

2931
"github.com/golang/mock/gomock"
3032
"github.com/stretchr/testify/require"
33+
"github.com/uber-go/tally"
3134
"github.com/uber/tchannel-go/thrift"
3235
"go.uber.org/cadence/.gen/go/cadence/workflowserviceclient"
3336
"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
3437
s "go.uber.org/cadence/.gen/go/shared"
3538
"go.uber.org/yarpc"
3639
)
3740

41+
var (
42+
safeCharacters = []rune{'_'}
43+
44+
sanitizeOptions = tally.SanitizeOptions{
45+
NameCharacters: tally.ValidCharacters{
46+
Ranges: tally.AlphanumericRange,
47+
Characters: safeCharacters,
48+
},
49+
KeyCharacters: tally.ValidCharacters{
50+
Ranges: tally.AlphanumericRange,
51+
Characters: safeCharacters,
52+
},
53+
ValueCharacters: tally.ValidCharacters{
54+
Ranges: tally.AlphanumericRange,
55+
Characters: safeCharacters,
56+
},
57+
ReplacementCharacter: tally.DefaultReplacementCharacter,
58+
}
59+
)
60+
3861
type testCase struct {
3962
serviceMethod string
4063
callArgs []interface{}
@@ -44,7 +67,6 @@ type testCase struct {
4467

4568
func Test_Wrapper(t *testing.T) {
4669
ctx, _ := thrift.NewContext(time.Minute)
47-
callOption := yarpc.CallOption{}
4870
tests := []testCase{
4971
// one case for each service call
5072
{"DeprecateDomain", []interface{}{ctx, &s.DeprecateDomainRequest{}}, []interface{}{nil}, []string{CadenceRequest}},
@@ -77,9 +99,22 @@ func Test_Wrapper(t *testing.T) {
7799
{"RespondQueryTaskCompleted", []interface{}{ctx, &s.RespondQueryTaskCompletedRequest{}}, []interface{}{&s.InternalServiceError{}}, []string{CadenceRequest, CadenceError}},
78100
}
79101

102+
// run each test twice - once with the regular scope, once with a sanitized metrics scope
80103
for _, test := range tests {
81-
mockService, wrapperService, closer, reporter := newService(t)
104+
runTest(t, test, newService, assertMetrics, fmt.Sprintf("%v_normal", test.serviceMethod))
105+
runTest(t, test, newPromService, assertPromMetrics, fmt.Sprintf("%v_prom_sanitized", test.serviceMethod))
106+
}
107+
}
82108

109+
func runTest(
110+
t *testing.T,
111+
test testCase,
112+
serviceFunc func(*testing.T) (*workflowservicetest.MockClient, workflowserviceclient.Interface, io.Closer, *CapturingStatsReporter),
113+
validationFunc func(*testing.T, *CapturingStatsReporter, string, []string),
114+
name string,
115+
) {
116+
t.Run(name, func(t *testing.T) {
117+
mockService, wrapperService, closer, reporter := serviceFunc(t)
83118
switch test.serviceMethod {
84119
case "DeprecateDomain":
85120
mockService.EXPECT().DeprecateDomain(gomock.Any(), gomock.Any(), gomock.Any()).Return(test.mockReturns...)
@@ -135,6 +170,7 @@ func Test_Wrapper(t *testing.T) {
135170
mockService.EXPECT().RespondQueryTaskCompleted(gomock.Any(), gomock.Any(), gomock.Any()).Return(test.mockReturns...)
136171
}
137172

173+
callOption := yarpc.CallOption{}
138174
inputs := make([]reflect.Value, len(test.callArgs))
139175
for i, arg := range test.callArgs {
140176
inputs[i] = reflect.ValueOf(arg)
@@ -143,8 +179,8 @@ func Test_Wrapper(t *testing.T) {
143179
method := reflect.ValueOf(wrapperService).MethodByName(test.serviceMethod)
144180
method.Call(inputs)
145181
closer.Close()
146-
assertMetrics(t, reporter, test.serviceMethod, test.expectedCounters)
147-
}
182+
validationFunc(t, reporter, test.serviceMethod, test.expectedCounters)
183+
})
148184
}
149185

150186
func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
@@ -165,7 +201,33 @@ func assertMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName st
165201
require.Equal(t, CadenceMetricsPrefix+methodName+"."+CadenceLatency, reporter.timers[0].name)
166202
}
167203

168-
func newService(t *testing.T) (mockService *workflowservicetest.MockClient,
204+
func assertPromMetrics(t *testing.T, reporter *CapturingStatsReporter, methodName string, counterNames []string) {
205+
require.Equal(t, len(counterNames), len(reporter.counts))
206+
for _, name := range counterNames {
207+
counterName := makePromCompatible(CadenceMetricsPrefix + methodName + "." + name)
208+
find := false
209+
// counters are not in order
210+
for _, counter := range reporter.counts {
211+
if counterName == counter.name {
212+
find = true
213+
break
214+
}
215+
}
216+
require.True(t, find)
217+
}
218+
require.Equal(t, 1, len(reporter.timers))
219+
expected := makePromCompatible(CadenceMetricsPrefix + methodName + "." + CadenceLatency)
220+
require.Equal(t, expected, reporter.timers[0].name)
221+
}
222+
223+
func makePromCompatible(name string) string {
224+
name = strings.Replace(name, "-", "_", -1)
225+
name = strings.Replace(name, ".", "_", -1)
226+
return name
227+
}
228+
229+
func newService(t *testing.T) (
230+
mockService *workflowservicetest.MockClient,
169231
wrapperService workflowserviceclient.Interface,
170232
closer io.Closer,
171233
reporter *CapturingStatsReporter,
@@ -177,3 +239,27 @@ func newService(t *testing.T) (mockService *workflowservicetest.MockClient,
177239
wrapperService = NewWorkflowServiceWrapper(mockService, scope)
178240
return
179241
}
242+
243+
func newPromService(t *testing.T) (
244+
mockService *workflowservicetest.MockClient,
245+
wrapperService workflowserviceclient.Interface,
246+
closer io.Closer,
247+
reporter *CapturingStatsReporter,
248+
) {
249+
mockCtrl := gomock.NewController(t)
250+
mockService = workflowservicetest.NewMockClient(mockCtrl)
251+
isReplay := false
252+
scope, closer, reporter := newPromScope(&isReplay)
253+
wrapperService = NewWorkflowServiceWrapper(mockService, scope)
254+
return
255+
}
256+
257+
func newPromScope(isReplay *bool) (tally.Scope, io.Closer, *CapturingStatsReporter) {
258+
reporter := &CapturingStatsReporter{}
259+
opts := tally.ScopeOptions{
260+
Reporter: reporter,
261+
SanitizeOptions: &sanitizeOptions,
262+
}
263+
scope, closer := tally.NewRootScope(opts, time.Second)
264+
return WrapScope(isReplay, scope, &realClock{}), closer, reporter
265+
}

internal/worker.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ package internal
2222

2323
import (
2424
"context"
25+
"encoding/json"
2526
"errors"
27+
"io/ioutil"
2628
"math"
2729
"time"
2830

29-
"encoding/json"
3031
"github.com/golang/mock/gomock"
3132
"github.com/pborman/uuid"
3233
"github.com/uber-go/tally"
@@ -36,7 +37,6 @@ import (
3637
"go.uber.org/cadence/encoded"
3738
"go.uber.org/cadence/internal/common"
3839
"go.uber.org/zap"
39-
"io/ioutil"
4040
)
4141

4242
type (
@@ -109,7 +109,32 @@ type (
109109
// default: default identity that include hostname, groupName and process ID.
110110
Identity string
111111

112-
// Optional: Metrics to be reported.
112+
// Optional: Metrics to be reported. Metrics emitted by the cadence client are not prometheus compatible by
113+
// default. To ensure metrics are compatible with prometheus make sure to create tally scope with sanitizer
114+
// options set.
115+
// var (
116+
// _safeCharacters = []rune{'_'}
117+
// _sanitizeOptions = tally.SanitizeOptions{
118+
// NameCharacters: tally.ValidCharacters{
119+
// Ranges: tally.AlphanumericRange,
120+
// Characters: _safeCharacters,
121+
// },
122+
// KeyCharacters: tally.ValidCharacters{
123+
// Ranges: tally.AlphanumericRange,
124+
// Characters: _safeCharacters,
125+
// },
126+
// ValueCharacters: tally.ValidCharacters{
127+
// Ranges: tally.AlphanumericRange,
128+
// Characters: _safeCharacters,
129+
// },
130+
// ReplacementCharacter: tally.DefaultReplacementCharacter,
131+
// }
132+
// )
133+
// opts := tally.ScopeOptions{
134+
// Reporter: reporter,
135+
// SanitizeOptions: &_sanitizeOptions,
136+
// }
137+
// scope, _ := tally.NewRootScope(opts, time.Second)
113138
// default: no metrics.
114139
MetricsScope tally.Scope
115140

0 commit comments

Comments
 (0)