Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 0fc2674

Browse files
Parse dropped timeseries from CreateTimeseriesResponse (#276)
1 parent cde3c10 commit 0fc2674

2 files changed

Lines changed: 107 additions & 5 deletions

File tree

metrics_batcher.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package stackdriver
1717
import (
1818
"context"
1919
"fmt"
20+
"regexp"
21+
"strconv"
2022
"strings"
2123
"sync"
2224
"time"
@@ -131,16 +133,45 @@ func (mb *metricsBatcher) sendReqToChan() {
131133
mb.reqsChan <- req
132134
}
133135

136+
// regex to extract min-max ranges from error response strings in the format "timeSeries[(min-max,...)] ..." (max is optional)
137+
var timeSeriesErrRegex = regexp.MustCompile(`: timeSeries\[([0-9]+(?:-[0-9]+)?(?:,[0-9]+(?:-[0-9]+)?)*)\]`)
138+
134139
// sendReq sends create time series requests to Stackdriver,
135140
// and returns the count of dropped time series and error.
136141
func sendReq(ctx context.Context, c *monitoring.MetricClient, req *monitoringpb.CreateTimeSeriesRequest) (int, error) {
137-
if c != nil { // c==nil only happens in unit tests where we don't make real calls to Stackdriver server
138-
err := createTimeSeries(ctx, c, req)
139-
if err != nil {
140-
return len(req.TimeSeries), err
142+
// c == nil only happens in unit tests where we don't make real calls to Stackdriver server
143+
if c == nil {
144+
return 0, nil
145+
}
146+
147+
err := createTimeSeries(ctx, c, req)
148+
if err == nil {
149+
return 0, nil
150+
}
151+
152+
droppedTimeSeriesRangeMatches := timeSeriesErrRegex.FindAllStringSubmatch(err.Error(), -1)
153+
if !strings.HasPrefix(err.Error(), "One or more TimeSeries could not be written:") || len(droppedTimeSeriesRangeMatches) == 0 {
154+
return len(req.TimeSeries), err
155+
}
156+
157+
dropped := 0
158+
for _, submatches := range droppedTimeSeriesRangeMatches {
159+
for i := 1; i < len(submatches); i++ {
160+
for _, rng := range strings.Split(submatches[i], ",") {
161+
rngSlice := strings.Split(rng, "-")
162+
163+
// strconv errors not possible due to regex above
164+
min, _ := strconv.Atoi(rngSlice[0])
165+
max := min
166+
if len(rngSlice) > 1 {
167+
max, _ = strconv.Atoi(rngSlice[1])
168+
}
169+
170+
dropped += max - min + 1
171+
}
141172
}
142173
}
143-
return 0, nil
174+
return dropped, err
144175
}
145176

146177
type worker struct {

metrics_batcher_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package stackdriver
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"testing"
2122

@@ -109,3 +110,73 @@ func makeTs(i int) *monitoringpb.TimeSeries {
109110
},
110111
}
111112
}
113+
114+
func TestSendReqAndParseDropped(t *testing.T) {
115+
type testCase struct {
116+
name string
117+
timeseriesCount int
118+
createTimeSeriesFunc func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error
119+
expectedErr bool
120+
expectedDropped int
121+
}
122+
123+
testCases := []testCase{
124+
{
125+
name: "No error",
126+
timeseriesCount: 75,
127+
createTimeSeriesFunc: func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error {
128+
return nil
129+
},
130+
expectedErr: false,
131+
expectedDropped: 0,
132+
},
133+
{
134+
name: "Partial error",
135+
timeseriesCount: 75,
136+
createTimeSeriesFunc: func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error {
137+
return errors.New("One or more TimeSeries could not be written: Internal error encountered. Please retry after a few seconds. If internal errors persist, contact support at https://cloud.google.com/support/docs.: timeSeries[0-16,25-44,46-74]; Unknown metric: agent.googleapis.com/system.swap.page_faults: timeSeries[45]")
138+
},
139+
expectedErr: true,
140+
expectedDropped: 67,
141+
},
142+
{
143+
name: "Incorrectly formatted error",
144+
timeseriesCount: 75,
145+
createTimeSeriesFunc: func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error {
146+
return errors.New("One or more TimeSeries could not be written: Internal error encountered. Please retry after a few seconds. If internal errors persist, contact support at https://cloud.google.com/support/docs.: timeSeries[0-16,25-44,,46-74]; Unknown metric: agent.googleapis.com/system.swap.page_faults: timeSeries[45x]")
147+
},
148+
expectedErr: true,
149+
expectedDropped: 75,
150+
},
151+
{
152+
name: "Unexpected error format",
153+
timeseriesCount: 75,
154+
createTimeSeriesFunc: func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error {
155+
return errors.New("err1")
156+
},
157+
expectedErr: true,
158+
expectedDropped: 75,
159+
},
160+
}
161+
162+
for _, test := range testCases {
163+
t.Run(test.name, func(t *testing.T) {
164+
persistedCreateTimeSeries := createTimeSeries
165+
createTimeSeries = test.createTimeSeriesFunc
166+
167+
mc, _ := monitoring.NewMetricClient(context.Background())
168+
d, err := sendReq(context.Background(), mc, &monitoringpb.CreateTimeSeriesRequest{TimeSeries: make([]*monitoringpb.TimeSeries, test.timeseriesCount)})
169+
if !test.expectedErr && err != nil {
170+
t.Fatal("Expected no err")
171+
}
172+
if test.expectedErr && err == nil {
173+
t.Fatal("Expected noerr")
174+
}
175+
if d != test.expectedDropped {
176+
t.Fatalf("Want %v dropped, got %v", test.expectedDropped, d)
177+
}
178+
179+
createTimeSeries = persistedCreateTimeSeries
180+
})
181+
}
182+
}

0 commit comments

Comments
 (0)