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

Commit f6033e3

Browse files
author
Ramon Nogueira
authored
Add GetMonitoredResource option (#46)
This allows customizing the MonitoredResource based on tag values.
1 parent 857ff68 commit f6033e3

4 files changed

Lines changed: 210 additions & 31 deletions

File tree

monitoredresource/monitored_resources_test.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,3 @@ func TestAWSEC2InstanceMonitoredResources(t *testing.T) {
101101
t.Errorf("AWSEC2InstanceMonitoredResource Failed: %v", autoDetected)
102102
}
103103
}
104-
105-
func TestNullMonitoredResources(t *testing.T) {
106-
os.Setenv("KUBERNETES_SERVICE_HOST", "")
107-
mr := Autodetect()
108-
if mr != nil {
109-
t.Errorf("Expected nil MonitoredResource but found %v", mr)
110-
}
111-
}

stackdriver.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import (
5757
traceapi "cloud.google.com/go/trace/apiv2"
5858
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
5959
"go.opencensus.io/stats/view"
60+
"go.opencensus.io/tag"
6061
"go.opencensus.io/trace"
6162
"golang.org/x/oauth2/google"
6263
"google.golang.org/api/option"
@@ -183,18 +184,36 @@ type Options struct {
183184
// the Resource you set uniquely identifies this Go process.
184185
DefaultMonitoringLabels *Labels
185186

186-
// Context allows users to provide a custom context for API calls.
187+
// Context allows you to provide a custom context for API calls.
187188
//
188189
// This context will be used several times: first, to create Stackdriver
189190
// trace and metric clients, and then every time a new batch of traces or
190191
// stats needs to be uploaded.
191192
//
192193
// If unset, context.Background() will be used.
193194
Context context.Context
195+
196+
// GetMonitoredResource may be provided to supply the details of the
197+
// monitored resource dynamically based on the tags associated with each
198+
// data point. Most users will not need to set this, but should instead
199+
// set the MonitoredResource field.
200+
//
201+
// GetMonitoredResource may add or remove tags by returning a new set of
202+
// tags. It is safe for the function to mutate its argument and return it.
203+
//
204+
// See the documentation on the MonitoredResource field for guidance on the
205+
// interaction between monitored resources and labels.
206+
//
207+
// The MonitoredResource field is ignored if this field is set to a non-nil
208+
// value.
209+
GetMonitoredResource func(*view.View, []tag.Tag) ([]tag.Tag, monitoredresource.Interface)
194210
}
195211

196-
// Exporter is a stats.Exporter and trace.Exporter
197-
// implementation that uploads data to Stackdriver.
212+
// Exporter is a stats and trace exporter that uploads data to Stackdriver.
213+
//
214+
// You can create a single Exporter and register it as both a trace exporter
215+
// (to export to Stackdriver Trace) and a stats exporter (to integrate with
216+
// Stackdriver Monitoring).
198217
type Exporter struct {
199218
traceExporter *traceExporter
200219
statsExporter *statsExporter

stats.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func newStatsExporter(o Options) (*statsExporter, error) {
8787
o: o,
8888
createdViews: make(map[string]*metricpb.MetricDescriptor),
8989
}
90+
9091
if o.DefaultMonitoringLabels != nil {
9192
e.defaultLabels = o.DefaultMonitoringLabels.m
9293
} else {
@@ -107,6 +108,21 @@ func newStatsExporter(o Options) (*statsExporter, error) {
107108
return e, nil
108109
}
109110

111+
func (e *statsExporter) getMonitoredResource(v *view.View, tags []tag.Tag) ([]tag.Tag, *monitoredrespb.MonitoredResource) {
112+
if get := e.o.GetMonitoredResource; get != nil {
113+
newTags, mr := get(v, tags)
114+
return newTags, convertMonitoredResourceToPB(mr)
115+
} else {
116+
resource := e.o.Resource
117+
if resource == nil {
118+
resource = &monitoredrespb.MonitoredResource{
119+
Type: "global",
120+
}
121+
}
122+
return tags, resource
123+
}
124+
}
125+
110126
// ExportView exports to the Stackdriver Monitoring if view data
111127
// has one or more rows.
112128
func (e *statsExporter) ExportView(vd *view.Data) {
@@ -177,20 +193,13 @@ func (e *statsExporter) uploadStats(vds []*view.Data) error {
177193
func (e *statsExporter) makeReq(vds []*view.Data, limit int) []*monitoringpb.CreateTimeSeriesRequest {
178194
var reqs []*monitoringpb.CreateTimeSeriesRequest
179195
var timeSeries []*monitoringpb.TimeSeries
180-
181-
resource := e.o.Resource
182-
if resource == nil {
183-
resource = &monitoredrespb.MonitoredResource{
184-
Type: "global",
185-
}
186-
}
187-
188196
for _, vd := range vds {
189197
for _, row := range vd.Rows {
198+
tags, resource := e.getMonitoredResource(vd.View, append([]tag.Tag(nil), row.Tags...))
190199
ts := &monitoringpb.TimeSeries{
191200
Metric: &metricpb.Metric{
192201
Type: e.metricType(vd.View),
193-
Labels: newLabels(e.defaultLabels, row.Tags),
202+
Labels: newLabels(e.defaultLabels, tags),
194203
},
195204
Resource: resource,
196205
Points: []*monitoringpb.Point{newPoint(vd.View, row, vd.Start, vd.End)},

stats_test.go

Lines changed: 170 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ package stackdriver
1616

1717
import (
1818
"context"
19+
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
1920
"fmt"
20-
"reflect"
2121
"testing"
2222
"time"
2323

@@ -39,6 +39,8 @@ import (
3939

4040
var authOptions = []option.ClientOption{option.WithGRPCConn(&grpc.ClientConn{})}
4141

42+
var testOptions = Options{ProjectID: "opencensus-test", MonitoringClientOptions: authOptions}
43+
4244
func TestRejectBlankProjectID(t *testing.T) {
4345
ids := []string{"", " ", " "}
4446
for _, projectID := range ids {
@@ -492,7 +494,10 @@ func TestExporter_makeReq_batching(t *testing.T) {
492494
vds = append(vds, newTestViewData(v, time.Now(), time.Now(), count1, count2))
493495
}
494496

495-
e := &statsExporter{}
497+
e, err := newStatsExporter(testOptions)
498+
if err != nil {
499+
t.Fatal(err)
500+
}
496501
resps := e.makeReq(vds, tt.limit)
497502
if len(resps) != tt.wantReqs {
498503
t.Errorf("%v: got %v; want %d requests", tt.name, resps, tt.wantReqs)
@@ -646,7 +651,7 @@ func TestEqualAggWindowTagKeys(t *testing.T) {
646651
wantErr: false,
647652
},
648653
}
649-
e, err := newStatsExporter(Options{ProjectID: "opencensus-test", MonitoringClientOptions: authOptions})
654+
e, err := newStatsExporter(testOptions)
650655
if err != nil {
651656
t.Fatal(err)
652657
}
@@ -860,8 +865,18 @@ func TestExporter_makeReq_withCustomMonitoredResource(t *testing.T) {
860865
taskValue := getTaskValue()
861866

862867
resource := &monitoredrespb.MonitoredResource{
863-
Type: "gce_instance",
864-
Labels: map[string]string{"instance_id": "instance", "zone": "us-west-1a"},
868+
Type: "gce_instance",
869+
Labels: map[string]string{
870+
"project_id": "proj-id",
871+
"instance_id": "instance",
872+
"zone": "us-west-1a",
873+
},
874+
}
875+
876+
gceInst := &monitoredresource.GCEInstance{
877+
ProjectID: "proj-id",
878+
InstanceID: "instance",
879+
Zone: "us-west-1a",
865880
}
866881

867882
tests := []struct {
@@ -934,6 +949,150 @@ func TestExporter_makeReq_withCustomMonitoredResource(t *testing.T) {
934949
},
935950
}},
936951
},
952+
{
953+
name: "with MonitoredResource and labels",
954+
opts: func() Options {
955+
var labels Labels
956+
labels.Set("pid", "1234", "Process identifier")
957+
return Options{
958+
MonitoredResource: gceInst,
959+
DefaultMonitoringLabels: &labels,
960+
}
961+
}(),
962+
vd: newTestViewData(v, start, end, count1, count2),
963+
want: []*monitoringpb.CreateTimeSeriesRequest{{
964+
Name: monitoring.MetricProjectPath("proj-id"),
965+
TimeSeries: []*monitoringpb.TimeSeries{
966+
{
967+
Metric: &metricpb.Metric{
968+
Type: "custom.googleapis.com/opencensus/testview",
969+
Labels: map[string]string{
970+
"test_key": "test-value-1",
971+
"pid": "1234",
972+
},
973+
},
974+
Resource: resource,
975+
Points: []*monitoringpb.Point{
976+
{
977+
Interval: &monitoringpb.TimeInterval{
978+
StartTime: &timestamp.Timestamp{
979+
Seconds: start.Unix(),
980+
Nanos: int32(start.Nanosecond()),
981+
},
982+
EndTime: &timestamp.Timestamp{
983+
Seconds: end.Unix(),
984+
Nanos: int32(end.Nanosecond()),
985+
},
986+
},
987+
Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{
988+
Int64Value: 10,
989+
}},
990+
},
991+
},
992+
},
993+
{
994+
Metric: &metricpb.Metric{
995+
Type: "custom.googleapis.com/opencensus/testview",
996+
Labels: map[string]string{
997+
"test_key": "test-value-2",
998+
"pid": "1234",
999+
},
1000+
},
1001+
Resource: resource,
1002+
Points: []*monitoringpb.Point{
1003+
{
1004+
Interval: &monitoringpb.TimeInterval{
1005+
StartTime: &timestamp.Timestamp{
1006+
Seconds: start.Unix(),
1007+
Nanos: int32(start.Nanosecond()),
1008+
},
1009+
EndTime: &timestamp.Timestamp{
1010+
Seconds: end.Unix(),
1011+
Nanos: int32(end.Nanosecond()),
1012+
},
1013+
},
1014+
Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{
1015+
Int64Value: 16,
1016+
}},
1017+
},
1018+
},
1019+
},
1020+
},
1021+
}},
1022+
},
1023+
{
1024+
name: "GetMonitoredResource and labels",
1025+
opts: func() Options {
1026+
var labels Labels
1027+
labels.Set("pid", "1234", "Process identifier")
1028+
return Options{
1029+
GetMonitoredResource: func(v *view.View, t []tag.Tag) ([]tag.Tag, monitoredresource.Interface) {
1030+
return t, gceInst
1031+
},
1032+
DefaultMonitoringLabels: &labels,
1033+
}
1034+
}(),
1035+
vd: newTestViewData(v, start, end, count1, count2),
1036+
want: []*monitoringpb.CreateTimeSeriesRequest{{
1037+
Name: monitoring.MetricProjectPath("proj-id"),
1038+
TimeSeries: []*monitoringpb.TimeSeries{
1039+
{
1040+
Metric: &metricpb.Metric{
1041+
Type: "custom.googleapis.com/opencensus/testview",
1042+
Labels: map[string]string{
1043+
"test_key": "test-value-1",
1044+
"pid": "1234",
1045+
},
1046+
},
1047+
Resource: resource,
1048+
Points: []*monitoringpb.Point{
1049+
{
1050+
Interval: &monitoringpb.TimeInterval{
1051+
StartTime: &timestamp.Timestamp{
1052+
Seconds: start.Unix(),
1053+
Nanos: int32(start.Nanosecond()),
1054+
},
1055+
EndTime: &timestamp.Timestamp{
1056+
Seconds: end.Unix(),
1057+
Nanos: int32(end.Nanosecond()),
1058+
},
1059+
},
1060+
Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{
1061+
Int64Value: 10,
1062+
}},
1063+
},
1064+
},
1065+
},
1066+
{
1067+
Metric: &metricpb.Metric{
1068+
Type: "custom.googleapis.com/opencensus/testview",
1069+
Labels: map[string]string{
1070+
"test_key": "test-value-2",
1071+
"pid": "1234",
1072+
},
1073+
},
1074+
Resource: resource,
1075+
Points: []*monitoringpb.Point{
1076+
{
1077+
Interval: &monitoringpb.TimeInterval{
1078+
StartTime: &timestamp.Timestamp{
1079+
Seconds: start.Unix(),
1080+
Nanos: int32(start.Nanosecond()),
1081+
},
1082+
EndTime: &timestamp.Timestamp{
1083+
Seconds: end.Unix(),
1084+
Nanos: int32(end.Nanosecond()),
1085+
},
1086+
},
1087+
Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{
1088+
Int64Value: 16,
1089+
}},
1090+
},
1091+
},
1092+
},
1093+
},
1094+
}},
1095+
},
9371096
{
9381097
name: "custom default monitoring labels",
9391098
opts: func() Options {
@@ -1075,19 +1234,19 @@ func TestExporter_makeReq_withCustomMonitoredResource(t *testing.T) {
10751234
opts := tt.opts
10761235
opts.MonitoringClientOptions = authOptions
10771236
opts.ProjectID = "proj-id"
1078-
e, err := newStatsExporter(opts)
1237+
e, err := NewExporter(opts)
10791238
if err != nil {
10801239
t.Fatal(err)
10811240
}
1082-
resps := e.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload)
1241+
resps := e.statsExporter.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload)
10831242
if got, want := len(resps), len(tt.want); got != want {
10841243
t.Fatalf("%v: Exporter.makeReq() returned %d responses; want %d", tt.name, got, want)
10851244
}
10861245
if len(tt.want) == 0 {
10871246
return
10881247
}
1089-
if !reflect.DeepEqual(resps, tt.want) {
1090-
t.Errorf("%v: Exporter.makeReq() = \n %v\nwant: %v", tt.name, resps, tt.want)
1248+
if diff := cmp.Diff(resps, tt.want); diff != "" {
1249+
t.Errorf("Requests differ, -got +want: %s", diff)
10911250
}
10921251
})
10931252
}
@@ -1105,7 +1264,7 @@ func TestExporter_customContext(t *testing.T) {
11051264
var timedOut = 0
11061265
createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metric.MetricDescriptor, error) {
11071266
select {
1108-
case <-time.After(15 * time.Millisecond):
1267+
case <-time.After(1 * time.Second):
11091268
fmt.Println("createMetricDescriptor did not time out")
11101269
case <-ctx.Done():
11111270
timedOut++
@@ -1114,7 +1273,7 @@ func TestExporter_customContext(t *testing.T) {
11141273
}
11151274
createTimeSeries = func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error {
11161275
select {
1117-
case <-time.After(15 * time.Millisecond):
1276+
case <-time.After(1 * time.Second):
11181277
fmt.Println("createTimeSeries did not time out")
11191278
case <-ctx.Done():
11201279
timedOut++

0 commit comments

Comments
 (0)