Skip to content

Commit bf4e62e

Browse files
committed
review round 3 fix
1 parent 8fb9793 commit bf4e62e

File tree

5 files changed

+178
-90
lines changed

5 files changed

+178
-90
lines changed

monitoring/exporter/stackdriver/mock_check_test.go

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"context"
1919
"fmt"
2020
"strings"
21-
"testing"
2221

2322
monitoring "cloud.google.com/go/monitoring/apiv3"
2423
gax "github.com/googleapis/gax-go"
@@ -28,7 +27,8 @@ import (
2827
)
2928

3029
// This file defines various mocks for testing, and checking functions for mocked data. We mock
31-
// metric client and bundler because their actions involves RPC calls or non-deterministic behavior.
30+
// metric client and bundler because their actions involves monitoring API calls or
31+
// behavior of bundler controlled by its internal timer.
3232

3333
// Following data are used to store various data generated by exporters' activity. They are used by
3434
// each test to verify intended behavior. Each test should call testDataInit() to clear these data.
@@ -50,9 +50,6 @@ var (
5050
)
5151

5252
func init() {
53-
// For testing convenience, we reduce maximum time series that metric client accepts.
54-
MaxTimeSeriesPerUpload = 3
55-
5653
// Mock functions.
5754
newMetricClient = mockNewMetricClient
5855
createTimeSeries = mockCreateTimeSeries
@@ -126,22 +123,29 @@ func mockAddToBundler(bndler *bundler.Bundler, item interface{}, _ int) error {
126123
// One of these functions once and only once, and never call NewExporter() directly.
127124

128125
// newTestExp creates an exporter which saves error to errStorage. Caller should not set
129-
// opts.OnError.
130-
func newTestExp(t *testing.T, opts Options) *Exporter {
126+
// opts.OnError and opts.BundleCountThreshold.
127+
func newTestExp(opts Options) (*Exporter, error) {
131128
opts.OnError = testOnError
129+
// For testing convenience, we reduce the number of timeseris in one upload monitoring API
130+
// call.
131+
opts.BundleCountThreshold = 3
132132
exp, err := NewExporter(ctx, opts)
133133
if err != nil {
134-
t.Fatalf("creating exporter failed: %v", err)
134+
return nil, fmt.Errorf("creating exporter failed: %v", err)
135135
}
136136
// Expose projDataMap so that mockAddToBundler() can use it.
137137
projDataMap = exp.projDataMap
138-
return exp
138+
return exp, nil
139139
}
140140

141141
// newTestProjData creates a projectData object to test behavior of projectData.uploadRowData. Other
142142
// uses are not recommended. As newTestExp, all errors are saved to errStorage.
143-
func newTestProjData(t *testing.T, opts Options) *projectData {
144-
return newTestExp(t, opts).newProjectData(project1)
143+
func newTestProjData(opts Options) (*projectData, error) {
144+
exp, err := newTestExp(opts)
145+
if err != nil {
146+
return nil, err
147+
}
148+
return exp.newProjectData(project1), nil
145149
}
146150

147151
// We define a storage for all errors happened in export operation.
@@ -157,33 +161,54 @@ func testOnError(err error, rds ...*RowData) {
157161
errStorage = append(errStorage, errRowData{err, rds})
158162
}
159163

164+
// multiError stores a sequence of errors. To convert it to an actual error, call toError().
165+
type multiError struct {
166+
errs []error
167+
}
168+
169+
func (me *multiError) addf(format string, args ...interface{}) {
170+
me.errs = append(me.errs, fmt.Errorf(format, args...))
171+
}
172+
173+
func (me *multiError) toError() error {
174+
switch len(me.errs) {
175+
case 0:
176+
return nil
177+
case 1:
178+
return me.errs[0]
179+
default:
180+
return fmt.Errorf("multiple errors: %q", me.errs)
181+
}
182+
}
183+
160184
// checkMetricClient checks all recorded requests to the metric client. We only compare int64
161185
// values of the time series. To make this work, we assigned different int64 values for all valid
162186
// rows in the test.
163-
func checkMetricClient(t *testing.T, wantReqsValues [][]int64) {
187+
func checkMetricClient(wantReqsValues [][]int64) error {
164188
reqsLen, wantReqsLen := len(timeSeriesReqs), len(wantReqsValues)
165189
if reqsLen != wantReqsLen {
166-
t.Errorf("number of requests got: %d, want %d", reqsLen, wantReqsLen)
167-
return
190+
return fmt.Errorf("number of requests got: %d, want %d", reqsLen, wantReqsLen)
168191
}
192+
var errs multiError
169193
for i := 0; i < reqsLen; i++ {
170194
prefix := fmt.Sprintf("%d-th request mismatch", i+1)
171195
tsArr := timeSeriesReqs[i].TimeSeries
172196
wantTsValues := wantReqsValues[i]
173197
tsArrLen, wantTsArrLen := len(tsArr), len(wantTsValues)
174198
if tsArrLen != wantTsArrLen {
175-
t.Errorf("%s: number of time series got: %d, want: %d", prefix, tsArrLen, wantTsArrLen)
199+
errs.addf("%s: number of time series got: %d, want: %d", prefix, tsArrLen, wantTsArrLen)
176200
continue
177201
}
178202
for j := 0; j < tsArrLen; j++ {
179203
// This is how monitoring API stores the int64 value.
180204
tsVal := tsArr[j].Points[0].Value.Value.(*mpb.TypedValue_Int64Value).Int64Value
181205
wantTsVal := wantTsValues[j]
182206
if tsVal != wantTsVal {
183-
t.Errorf("%s: Value got: %d, want: %d", prefix, tsVal, wantTsVal)
207+
errs.addf("%s: Value got: %d, want: %d", prefix, tsVal, wantTsVal)
184208
}
185209
}
186210
}
211+
return errs.toError()
187212
}
188213

189214
// errRowDataCheck contains data for checking content of error storage.
@@ -193,96 +218,99 @@ type errRowDataCheck struct {
193218
}
194219

195220
// checkErrStorage checks content of error storage. For returned errors, we check prefix and suffix.
196-
func checkErrStorage(t *testing.T, wantErrRdCheck []errRowDataCheck) {
221+
func checkErrStorage(wantErrRdCheck []errRowDataCheck) error {
197222
gotLen, wantLen := len(errStorage), len(wantErrRdCheck)
198223
if gotLen != wantLen {
199-
t.Errorf("number of reported errors: %d, want: %d", gotLen, wantLen)
200-
return
224+
return fmt.Errorf("number of reported errors: %d, want: %d", gotLen, wantLen)
201225
}
226+
var errs multiError
202227
for i := 0; i < gotLen; i++ {
203228
prefix := fmt.Sprintf("%d-th reported error mismatch", i+1)
204229
errRd, wantErrRd := errStorage[i], wantErrRdCheck[i]
205230
errStr := errRd.err.Error()
206231
if errPrefix := wantErrRd.errPrefix; !strings.HasPrefix(errStr, errPrefix) {
207-
t.Errorf("%s: error got: %q, want: prefixed by %q", prefix, errStr, errPrefix)
232+
errs.addf("%s: error got: %q, want: prefixed by %q", prefix, errStr, errPrefix)
208233
}
209234
if errSuffix := wantErrRd.errSuffix; !strings.HasSuffix(errStr, errSuffix) {
210-
t.Errorf("%s: error got: %q, want: suffiexd by %q", prefix, errStr, errSuffix)
235+
errs.addf("%s: error got: %q, want: suffiexd by %q", prefix, errStr, errSuffix)
211236
}
212237
if err := checkRowDataArr(errRd.rds, wantErrRd.rds); err != nil {
213-
t.Errorf("%s: RowData array mismatch: %v", prefix, err)
238+
errs.addf("%s: RowData array mismatch: %v", prefix, err)
214239
}
215240
}
241+
return errs.toError()
216242
}
217243

218244
func checkRowDataArr(rds, wantRds []*RowData) error {
219245
rdLen, wantRdLen := len(rds), len(wantRds)
220246
if rdLen != wantRdLen {
221247
return fmt.Errorf("number row data got: %d, want: %d", rdLen, wantRdLen)
222248
}
249+
var errs multiError
223250
for i := 0; i < rdLen; i++ {
224251
if err := checkRowData(rds[i], wantRds[i]); err != nil {
225-
return fmt.Errorf("%d-th row data mismatch: %v", i+1, err)
252+
errs.addf("%d-th row data mismatch: %v", i+1, err)
226253
}
227254
}
228-
return nil
255+
return errs.toError()
229256
}
230257

231258
func checkRowData(rd, wantRd *RowData) error {
259+
var errs multiError
232260
if rd.View != wantRd.View {
233-
return fmt.Errorf("View got: %s, want: %s", rd.View.Name, wantRd.View.Name)
261+
errs.addf("View got: %s, want: %s", rd.View.Name, wantRd.View.Name)
234262
}
235263
if rd.Start != wantRd.Start {
236-
return fmt.Errorf("Start got: %v, want: %v", rd.Start, wantRd.Start)
264+
errs.addf("Start got: %v, want: %v", rd.Start, wantRd.Start)
237265
}
238266
if rd.End != wantRd.End {
239-
return fmt.Errorf("End got: %v, want: %v", rd.End, wantRd.End)
267+
errs.addf("End got: %v, want: %v", rd.End, wantRd.End)
240268
}
241269
if rd.Row != wantRd.Row {
242-
return fmt.Errorf("Row got: %v, want: %v", rd.Row, wantRd.Row)
270+
errs.addf("Row got: %v, want: %v", rd.Row, wantRd.Row)
243271
}
244-
return nil
272+
return errs.toError()
245273
}
246274

247275
// checkProjData checks all data passed to the bundler by bundler.Add().
248-
func checkProjData(t *testing.T, wantProjData map[string][]*RowData) {
249-
wantProj := map[string]bool{}
250-
for proj := range wantProjData {
251-
wantProj[proj] = true
252-
}
276+
func checkProjData(wantProjData map[string][]*RowData) error {
277+
var errs multiError
253278
for proj := range projRds {
254-
if !wantProj[proj] {
255-
t.Errorf("project in exporter's project data not wanted: %s", proj)
279+
if _, ok := wantProjData[proj]; !ok {
280+
errs.addf("project in exporter's project data not wanted: %s", proj)
256281
}
257282
}
258283

259284
for proj, wantRds := range wantProjData {
260285
rds, ok := projRds[proj]
261286
if !ok {
262-
t.Errorf("wanted project not found in exporter's project data: %v", proj)
287+
errs.addf("wanted project not found in exporter's project data: %v", proj)
263288
continue
264289
}
265290
if err := checkRowDataArr(*rds, wantRds); err != nil {
266-
t.Errorf("RowData array mismatch for project %s: %v", proj, err)
291+
errs.addf("RowData array mismatch for project %s: %v", proj, err)
267292
}
268293
}
294+
return errs.toError()
269295
}
270296

271297
// checkLabels checks data in labels.
272-
func checkLabels(t *testing.T, prefix string, labels, wantLabels map[string]string) {
298+
func checkLabels(prefix string, labels, wantLabels map[string]string) error {
299+
var errs multiError
273300
for labelName, value := range labels {
274301
wantValue, ok := wantLabels[labelName]
275302
if !ok {
276-
t.Errorf("%s: label name in time series not wanted: %s", prefix, labelName)
303+
errs.addf("%s: label name in time series not wanted: %s", prefix, labelName)
277304
continue
278305
}
279306
if value != wantValue {
280-
t.Errorf("%s: value for label name %s got: %s, want: %s", prefix, labelName, value, wantValue)
307+
errs.addf("%s: value for label name %s got: %s, want: %s", prefix, labelName, value, wantValue)
281308
}
282309
}
283310
for wantLabelName := range wantLabels {
284311
if _, ok := labels[wantLabelName]; !ok {
285-
t.Errorf("%s: wanted label name not found in time series: %s", prefix, wantLabelName)
312+
errs.addf("%s: wanted label name not found in time series: %s", prefix, wantLabelName)
286313
}
287314
}
315+
return errs.toError()
288316
}

monitoring/exporter/stackdriver/project_data.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,11 @@ import (
2121
mpb "google.golang.org/genproto/googleapis/monitoring/v3"
2222
)
2323

24-
// MaxTimeSeriePerUpload is the maximum number of time series that's uploaded to the stackdriver
25-
// at once. Consumer may change this value, but note that stackdriver may reject upload request if
26-
// the number of time series is too large.
27-
var MaxTimeSeriesPerUpload = 100
28-
2924
// projectData contain per-project data in exporter. It should be created by newProjectData()
3025
type projectData struct {
3126
parent *Exporter
3227
projectID string
33-
// We make bundler for each project because call to monitoring RPC can be grouped only in
28+
// We make bundler for each project because call to monitoring API can be grouped only in
3429
// project level
3530
bndler *bundler.Bundler
3631
}
@@ -55,7 +50,7 @@ func (pd *projectData) uploadRowData(bundle interface{}) {
5550
// Time series created. We update both uploadTs and uploadRds.
5651
uploadTs = append(uploadTs, ts)
5752
uploadRds = append(uploadRds, rd)
58-
if len(uploadTs) == MaxTimeSeriesPerUpload {
53+
if len(uploadTs) == exp.opts.BundleCountThreshold {
5954
pd.uploadTimeSeries(uploadTs, uploadRds)
6055
uploadTs = nil
6156
uploadRds = nil
@@ -77,7 +72,7 @@ func (pd *projectData) uploadTimeSeries(ts []*mpb.TimeSeries, rds []*RowData) {
7772
TimeSeries: ts,
7873
}
7974
if err := createTimeSeries(exp.client, exp.ctx, req); err != nil {
80-
newErr := fmt.Errorf("RPC call to create time series failed for project %s: %v", pd.projectID, err)
75+
newErr := fmt.Errorf("monitoring API call to create time series failed for project %s: %v", pd.projectID, err)
8176
// We pass all row data not successfully uploaded.
8277
exp.opts.OnError(newErr, rds...)
8378
}

monitoring/exporter/stackdriver/row_data_to_point.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
)
2626

2727
// Functions in this file is used to convert RowData to monitoring point that are used by uploading
28-
// RPC calls of monitoring client. All functions in this file are copied from
28+
// monitoring API calls. All functions in this file are copied from
2929
// contrib.go.opencensus.io/exporter/stackdriver.
3030

3131
func newPoint(v *view.View, row *view.Row, start, end time.Time) *mpb.Point {

monitoring/exporter/stackdriver/stackdriver.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import (
4444
mpb "google.golang.org/genproto/googleapis/monitoring/v3"
4545
)
4646

47+
// MaxTimeSeriesPerUpload is the maximum number of timeseries objects that will be uploaded to
48+
// Stackdriver in one API call.
49+
const MaxTimeSeriesPerUpload = 100
50+
4751
// Exporter is the exporter that can be registered to opencensus. An Exporter object must be
4852
// created by NewExporter().
4953
type Exporter struct {
@@ -62,17 +66,19 @@ type Exporter struct {
6266
// are valid for use.
6367
type Options struct {
6468
// ClientOptions designates options for creating metric client, especially credentials for
65-
// RPC calls.
69+
// monitoring API calls.
6670
ClientOptions []option.ClientOption
6771

6872
// Options for bundles amortizing export requests. Note that a bundle is created for each
69-
// project. When not provided, default values in bundle package are used.
73+
// project.
7074

7175
// BundleDelayThreshold determines the max amount of time the exporter can wait before
72-
// uploading data to the stackdriver.
76+
// uploading data to the stackdriver. If this value is not positive, the default value in
77+
// the bundle package is used.
7378
BundleDelayThreshold time.Duration
7479
// BundleCountThreshold determines how many RowData objects can be buffered before batch
75-
// uploading them to the backend.
80+
// uploading them to the backend. If this value is not between 1 and MaxTimeSeriesPerUpload,
81+
// MaxTimeSeriesPerUpload is used.
7682
BundleCountThreshold int
7783

7884
// Callback functions provided by user.
@@ -143,7 +149,7 @@ var (
143149

144150
// NewExporter creates an Exporter object. Once a call to NewExporter is made, any fields in opts
145151
// must not be modified at all. ctx will also be used throughout entire exporter operation when
146-
// making RPC call.
152+
// making monitoring API call.
147153
func NewExporter(ctx context.Context, opts Options) (*Exporter, error) {
148154
client, err := newMetricClient(ctx, opts.ClientOptions...)
149155
if err != nil {
@@ -157,6 +163,12 @@ func NewExporter(ctx context.Context, opts Options) (*Exporter, error) {
157163
projDataMap: make(map[string]*projectData),
158164
}
159165

166+
if !(0 < e.opts.BundleDelayThreshold) {
167+
e.opts.BundleDelayThreshold = bundler.DefaultDelayThreshold
168+
}
169+
if !(0 < e.opts.BundleCountThreshold && e.opts.BundleCountThreshold <= MaxTimeSeriesPerUpload) {
170+
e.opts.BundleCountThreshold = MaxTimeSeriesPerUpload
171+
}
160172
if e.opts.GetProjectID == nil {
161173
e.opts.GetProjectID = defaultGetProjectID
162174
}
@@ -238,13 +250,8 @@ func (e *Exporter) newProjectData(projectID string) *projectData {
238250
}
239251

240252
pd.bndler = newBundler((*RowData)(nil), pd.uploadRowData)
241-
// Set options for bundler if they are provided by users.
242-
if 0 < e.opts.BundleDelayThreshold {
243-
pd.bndler.DelayThreshold = e.opts.BundleDelayThreshold
244-
}
245-
if 0 < e.opts.BundleCountThreshold {
246-
pd.bndler.BundleCountThreshold = e.opts.BundleCountThreshold
247-
}
253+
pd.bndler.DelayThreshold = e.opts.BundleDelayThreshold
254+
pd.bndler.BundleCountThreshold = e.opts.BundleCountThreshold
248255
return pd
249256
}
250257

0 commit comments

Comments
 (0)