Skip to content

Commit 1e12b16

Browse files
committed
review round 3 response
1 parent 8fb9793 commit 1e12b16

File tree

5 files changed

+176
-89
lines changed

5 files changed

+176
-89
lines changed

monitoring/exporter/stackdriver/mock_check_test.go

+68-41
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"
@@ -50,9 +49,6 @@ var (
5049
)
5150

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

128124
// 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 {
125+
// opts.OnError and opts.BundleCountThreshold.
126+
func newTestExp(opts Options) (*Exporter, error) {
131127
opts.OnError = testOnError
128+
// For testing convenience, we reduce the number of timeseris in one upload monitoring API
129+
// call.
130+
opts.BundleCountThreshold = 3
132131
exp, err := NewExporter(ctx, opts)
133132
if err != nil {
134-
t.Fatalf("creating exporter failed: %v", err)
133+
return nil, fmt.Errorf("creating exporter failed: %v", err)
135134
}
136135
// Expose projDataMap so that mockAddToBundler() can use it.
137136
projDataMap = exp.projDataMap
138-
return exp
137+
return exp, nil
139138
}
140139

141140
// newTestProjData creates a projectData object to test behavior of projectData.uploadRowData. Other
142141
// 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)
142+
func newTestProjData(opts Options) (*projectData, error) {
143+
exp, err := newTestExp(opts)
144+
if err != nil {
145+
return nil, err
146+
}
147+
return exp.newProjectData(project1), nil
145148
}
146149

147150
// We define a storage for all errors happened in export operation.
@@ -157,33 +160,54 @@ func testOnError(err error, rds ...*RowData) {
157160
errStorage = append(errStorage, errRowData{err, rds})
158161
}
159162

163+
// multiError stores a sequence of errors. To convert it to an actual error, call toError().
164+
type multiError struct {
165+
errs []error
166+
}
167+
168+
func (me *multiError) addf(format string, args ...interface{}) {
169+
me.errs = append(me.errs, fmt.Errorf(format, args...))
170+
}
171+
172+
func (me *multiError) toError() error {
173+
switch len(me.errs) {
174+
case 0:
175+
return nil
176+
case 1:
177+
return me.errs[0]
178+
default:
179+
return fmt.Errorf("multiple errors: %q", me.errs)
180+
}
181+
}
182+
160183
// checkMetricClient checks all recorded requests to the metric client. We only compare int64
161184
// values of the time series. To make this work, we assigned different int64 values for all valid
162185
// rows in the test.
163-
func checkMetricClient(t *testing.T, wantReqsValues [][]int64) {
186+
func checkMetricClient(wantReqsValues [][]int64) error {
164187
reqsLen, wantReqsLen := len(timeSeriesReqs), len(wantReqsValues)
165188
if reqsLen != wantReqsLen {
166-
t.Errorf("number of requests got: %d, want %d", reqsLen, wantReqsLen)
167-
return
189+
return fmt.Errorf("number of requests got: %d, want %d", reqsLen, wantReqsLen)
168190
}
191+
var errs multiError
169192
for i := 0; i < reqsLen; i++ {
170193
prefix := fmt.Sprintf("%d-th request mismatch", i+1)
171194
tsArr := timeSeriesReqs[i].TimeSeries
172195
wantTsValues := wantReqsValues[i]
173196
tsArrLen, wantTsArrLen := len(tsArr), len(wantTsValues)
174197
if tsArrLen != wantTsArrLen {
175-
t.Errorf("%s: number of time series got: %d, want: %d", prefix, tsArrLen, wantTsArrLen)
198+
errs.addf("%s: number of time series got: %d, want: %d", prefix, tsArrLen, wantTsArrLen)
176199
continue
177200
}
178201
for j := 0; j < tsArrLen; j++ {
179202
// This is how monitoring API stores the int64 value.
180203
tsVal := tsArr[j].Points[0].Value.Value.(*mpb.TypedValue_Int64Value).Int64Value
181204
wantTsVal := wantTsValues[j]
182205
if tsVal != wantTsVal {
183-
t.Errorf("%s: Value got: %d, want: %d", prefix, tsVal, wantTsVal)
206+
errs.addf("%s: Value got: %d, want: %d", prefix, tsVal, wantTsVal)
184207
}
185208
}
186209
}
210+
return errs.toError()
187211
}
188212

189213
// errRowDataCheck contains data for checking content of error storage.
@@ -193,96 +217,99 @@ type errRowDataCheck struct {
193217
}
194218

195219
// checkErrStorage checks content of error storage. For returned errors, we check prefix and suffix.
196-
func checkErrStorage(t *testing.T, wantErrRdCheck []errRowDataCheck) {
220+
func checkErrStorage(wantErrRdCheck []errRowDataCheck) error {
197221
gotLen, wantLen := len(errStorage), len(wantErrRdCheck)
198222
if gotLen != wantLen {
199-
t.Errorf("number of reported errors: %d, want: %d", gotLen, wantLen)
200-
return
223+
return fmt.Errorf("number of reported errors: %d, want: %d", gotLen, wantLen)
201224
}
225+
var errs multiError
202226
for i := 0; i < gotLen; i++ {
203227
prefix := fmt.Sprintf("%d-th reported error mismatch", i+1)
204228
errRd, wantErrRd := errStorage[i], wantErrRdCheck[i]
205229
errStr := errRd.err.Error()
206230
if errPrefix := wantErrRd.errPrefix; !strings.HasPrefix(errStr, errPrefix) {
207-
t.Errorf("%s: error got: %q, want: prefixed by %q", prefix, errStr, errPrefix)
231+
errs.addf("%s: error got: %q, want: prefixed by %q", prefix, errStr, errPrefix)
208232
}
209233
if errSuffix := wantErrRd.errSuffix; !strings.HasSuffix(errStr, errSuffix) {
210-
t.Errorf("%s: error got: %q, want: suffiexd by %q", prefix, errStr, errSuffix)
234+
errs.addf("%s: error got: %q, want: suffiexd by %q", prefix, errStr, errSuffix)
211235
}
212236
if err := checkRowDataArr(errRd.rds, wantErrRd.rds); err != nil {
213-
t.Errorf("%s: RowData array mismatch: %v", prefix, err)
237+
errs.addf("%s: RowData array mismatch: %v", prefix, err)
214238
}
215239
}
240+
return errs.toError()
216241
}
217242

218243
func checkRowDataArr(rds, wantRds []*RowData) error {
219244
rdLen, wantRdLen := len(rds), len(wantRds)
220245
if rdLen != wantRdLen {
221246
return fmt.Errorf("number row data got: %d, want: %d", rdLen, wantRdLen)
222247
}
248+
var errs multiError
223249
for i := 0; i < rdLen; i++ {
224250
if err := checkRowData(rds[i], wantRds[i]); err != nil {
225-
return fmt.Errorf("%d-th row data mismatch: %v", i+1, err)
251+
errs.addf("%d-th row data mismatch: %v", i+1, err)
226252
}
227253
}
228-
return nil
254+
return errs.toError()
229255
}
230256

231257
func checkRowData(rd, wantRd *RowData) error {
258+
var errs multiError
232259
if rd.View != wantRd.View {
233-
return fmt.Errorf("View got: %s, want: %s", rd.View.Name, wantRd.View.Name)
260+
errs.addf("View got: %s, want: %s", rd.View.Name, wantRd.View.Name)
234261
}
235262
if rd.Start != wantRd.Start {
236-
return fmt.Errorf("Start got: %v, want: %v", rd.Start, wantRd.Start)
263+
errs.addf("Start got: %v, want: %v", rd.Start, wantRd.Start)
237264
}
238265
if rd.End != wantRd.End {
239-
return fmt.Errorf("End got: %v, want: %v", rd.End, wantRd.End)
266+
errs.addf("End got: %v, want: %v", rd.End, wantRd.End)
240267
}
241268
if rd.Row != wantRd.Row {
242-
return fmt.Errorf("Row got: %v, want: %v", rd.Row, wantRd.Row)
269+
errs.addf("Row got: %v, want: %v", rd.Row, wantRd.Row)
243270
}
244-
return nil
271+
return errs.toError()
245272
}
246273

247274
// 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-
}
275+
func checkProjData(wantProjData map[string][]*RowData) error {
276+
var errs multiError
253277
for proj := range projRds {
254-
if !wantProj[proj] {
255-
t.Errorf("project in exporter's project data not wanted: %s", proj)
278+
if _, ok := wantProjData[proj]; !ok {
279+
errs.addf("project in exporter's project data not wanted: %s", proj)
256280
}
257281
}
258282

259283
for proj, wantRds := range wantProjData {
260284
rds, ok := projRds[proj]
261285
if !ok {
262-
t.Errorf("wanted project not found in exporter's project data: %v", proj)
286+
errs.addf("wanted project not found in exporter's project data: %v", proj)
263287
continue
264288
}
265289
if err := checkRowDataArr(*rds, wantRds); err != nil {
266-
t.Errorf("RowData array mismatch for project %s: %v", proj, err)
290+
errs.addf("RowData array mismatch for project %s: %v", proj, err)
267291
}
268292
}
293+
return errs.toError()
269294
}
270295

271296
// checkLabels checks data in labels.
272-
func checkLabels(t *testing.T, prefix string, labels, wantLabels map[string]string) {
297+
func checkLabels(prefix string, labels, wantLabels map[string]string) error {
298+
var errs multiError
273299
for labelName, value := range labels {
274300
wantValue, ok := wantLabels[labelName]
275301
if !ok {
276-
t.Errorf("%s: label name in time series not wanted: %s", prefix, labelName)
302+
errs.addf("%s: label name in time series not wanted: %s", prefix, labelName)
277303
continue
278304
}
279305
if value != wantValue {
280-
t.Errorf("%s: value for label name %s got: %s, want: %s", prefix, labelName, value, wantValue)
306+
errs.addf("%s: value for label name %s got: %s, want: %s", prefix, labelName, value, wantValue)
281307
}
282308
}
283309
for wantLabelName := range wantLabels {
284310
if _, ok := labels[wantLabelName]; !ok {
285-
t.Errorf("%s: wanted label name not found in time series: %s", prefix, wantLabelName)
311+
errs.addf("%s: wanted label name not found in time series: %s", prefix, wantLabelName)
286312
}
287313
}
314+
return errs.toError()
288315
}

monitoring/exporter/stackdriver/project_data.go

+3-8
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

+1-1
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

+19-12
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)