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

Commit 9366d36

Browse files
authored
Add option to provide resource based on metric descriptor. (#231)
* Add option to provide resource based on metric descriptor. * change option to accomodate removing labels that are used for the resource * fix doc and review comments. * fix review comment.
1 parent 8033da9 commit 9366d36

3 files changed

Lines changed: 345 additions & 1 deletion

File tree

metrics.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
3535
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
3636

37+
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
3738
"go.opencensus.io/metric/metricdata"
3839
"go.opencensus.io/resource"
3940
)
@@ -154,12 +155,26 @@ func (se *statsExporter) metricToMpbTs(ctx context.Context, metric *metricdata.M
154155
// TODO: (@rghetia) perhaps log this error from labels extraction, if non-nil.
155156
continue
156157
}
158+
159+
var rsc *monitoredrespb.MonitoredResource
160+
var mr monitoredresource.Interface
161+
if se.o.ResourceByDescriptor != nil {
162+
labels, mr = se.o.ResourceByDescriptor(&metric.Descriptor, labels)
163+
// TODO(rghetia): optimize this. It is inefficient to convert this for all metrics.
164+
rsc = convertMonitoredResourceToPB(mr)
165+
if rsc.Type == "" {
166+
rsc.Type = "global"
167+
rsc.Labels = nil
168+
}
169+
} else {
170+
rsc = resource
171+
}
157172
timeSeries = append(timeSeries, &monitoringpb.TimeSeries{
158173
Metric: &googlemetricpb.Metric{
159174
Type: metricType,
160175
Labels: labels,
161176
},
162-
Resource: resource,
177+
Resource: rsc,
163178
Points: sdPoints,
164179
})
165180
}

metrics_test.go

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
3232
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
3333

34+
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
3435
"go.opencensus.io/metric/metricdata"
3536
"go.opencensus.io/resource"
3637
"go.opencensus.io/trace"
@@ -559,3 +560,313 @@ func TestMetricsToMonitoringMetrics_fromProtoPoint(t *testing.T) {
559560
}
560561
}
561562
}
563+
564+
func TestResourceByDescriptor(t *testing.T) {
565+
startTimestamp := &timestamp.Timestamp{
566+
Seconds: 1543160298,
567+
Nanos: 100000090,
568+
}
569+
startTime, _ := ptypes.Timestamp(startTimestamp)
570+
endTimestamp := &timestamp.Timestamp{
571+
Seconds: 1543160298,
572+
Nanos: 100000997,
573+
}
574+
endTime, _ := ptypes.Timestamp(endTimestamp)
575+
576+
tests := []struct {
577+
in *metricdata.Metric
578+
want []*monitoringpb.CreateTimeSeriesRequest
579+
wantErr string
580+
}{
581+
{
582+
in: &metricdata.Metric{
583+
Descriptor: metricdata.Descriptor{
584+
Name: "custom_resource_one",
585+
Description: "This is a test",
586+
Unit: metricdata.UnitBytes,
587+
Type: metricdata.TypeCumulativeInt64,
588+
LabelKeys: []metricdata.LabelKey{
589+
{
590+
Key: "k11",
591+
},
592+
{
593+
Key: "k12",
594+
},
595+
},
596+
},
597+
Resource: nil,
598+
TimeSeries: []*metricdata.TimeSeries{
599+
{
600+
StartTime: startTime,
601+
Points: []metricdata.Point{
602+
{
603+
Time: endTime,
604+
Value: int64(5),
605+
},
606+
},
607+
LabelValues: []metricdata.LabelValue{
608+
{
609+
Value: "v11",
610+
},
611+
{
612+
Value: "v12",
613+
},
614+
},
615+
},
616+
},
617+
},
618+
want: []*monitoringpb.CreateTimeSeriesRequest{
619+
{
620+
Name: "projects/foo",
621+
TimeSeries: []*monitoringpb.TimeSeries{
622+
{
623+
Metric: &googlemetricpb.Metric{
624+
Type: "custom.googleapis.com/opencensus/custom_resource_one",
625+
Labels: map[string]string{
626+
"k12": "v12",
627+
},
628+
},
629+
Resource: &monitoredrespb.MonitoredResource{
630+
Type: "one",
631+
Labels: map[string]string{
632+
"k11": "v11",
633+
},
634+
},
635+
Points: []*monitoringpb.Point{
636+
{
637+
Interval: &monitoringpb.TimeInterval{
638+
StartTime: startTimestamp,
639+
EndTime: endTimestamp,
640+
},
641+
Value: &monitoringpb.TypedValue{
642+
Value: &monitoringpb.TypedValue_Int64Value{
643+
Int64Value: 5,
644+
},
645+
},
646+
},
647+
},
648+
},
649+
},
650+
},
651+
},
652+
},
653+
{
654+
in: &metricdata.Metric{
655+
Descriptor: metricdata.Descriptor{
656+
Name: "custom_resource_two",
657+
Description: "This is a test",
658+
Unit: metricdata.UnitBytes,
659+
Type: metricdata.TypeCumulativeInt64,
660+
LabelKeys: []metricdata.LabelKey{
661+
{
662+
Key: "k21",
663+
},
664+
{
665+
Key: "k22",
666+
},
667+
},
668+
},
669+
Resource: nil,
670+
TimeSeries: []*metricdata.TimeSeries{
671+
{
672+
StartTime: startTime,
673+
Points: []metricdata.Point{
674+
{
675+
Time: endTime,
676+
Value: int64(5),
677+
},
678+
},
679+
LabelValues: []metricdata.LabelValue{
680+
{
681+
Value: "v21",
682+
},
683+
{
684+
Value: "v22",
685+
},
686+
},
687+
},
688+
},
689+
},
690+
want: []*monitoringpb.CreateTimeSeriesRequest{
691+
{
692+
Name: "projects/foo",
693+
TimeSeries: []*monitoringpb.TimeSeries{
694+
{
695+
Metric: &googlemetricpb.Metric{
696+
Type: "custom.googleapis.com/opencensus/custom_resource_two",
697+
Labels: map[string]string{
698+
"k21": "v21",
699+
},
700+
},
701+
Resource: &monitoredrespb.MonitoredResource{
702+
Type: "two",
703+
Labels: map[string]string{
704+
"k22": "v22",
705+
},
706+
},
707+
Points: []*monitoringpb.Point{
708+
{
709+
Interval: &monitoringpb.TimeInterval{
710+
StartTime: startTimestamp,
711+
EndTime: endTimestamp,
712+
},
713+
Value: &monitoringpb.TypedValue{
714+
Value: &monitoringpb.TypedValue_Int64Value{
715+
Int64Value: 5,
716+
},
717+
},
718+
},
719+
},
720+
},
721+
},
722+
},
723+
},
724+
},
725+
{
726+
in: &metricdata.Metric{
727+
Descriptor: metricdata.Descriptor{
728+
Name: "custom_resource_other",
729+
Description: "This is a test",
730+
Unit: metricdata.UnitBytes,
731+
Type: metricdata.TypeCumulativeInt64,
732+
LabelKeys: []metricdata.LabelKey{
733+
{
734+
Key: "k31",
735+
},
736+
{
737+
Key: "k32",
738+
},
739+
},
740+
},
741+
Resource: nil,
742+
TimeSeries: []*metricdata.TimeSeries{
743+
{
744+
StartTime: startTime,
745+
Points: []metricdata.Point{
746+
{
747+
Time: endTime,
748+
Value: int64(5),
749+
},
750+
},
751+
LabelValues: []metricdata.LabelValue{
752+
{
753+
Value: "v31",
754+
},
755+
{
756+
Value: "v32",
757+
},
758+
},
759+
},
760+
},
761+
},
762+
want: []*monitoringpb.CreateTimeSeriesRequest{
763+
{
764+
Name: "projects/foo",
765+
TimeSeries: []*monitoringpb.TimeSeries{
766+
{
767+
Metric: &googlemetricpb.Metric{
768+
Type: "custom.googleapis.com/opencensus/custom_resource_other",
769+
Labels: map[string]string{
770+
"k31": "v31",
771+
"k32": "v32",
772+
},
773+
},
774+
Resource: &monitoredrespb.MonitoredResource{
775+
Type: "global",
776+
},
777+
Points: []*monitoringpb.Point{
778+
{
779+
Interval: &monitoringpb.TimeInterval{
780+
StartTime: startTimestamp,
781+
EndTime: endTimestamp,
782+
},
783+
Value: &monitoringpb.TypedValue{
784+
Value: &monitoringpb.TypedValue_Int64Value{
785+
Int64Value: 5,
786+
},
787+
},
788+
},
789+
},
790+
},
791+
},
792+
},
793+
},
794+
},
795+
}
796+
797+
var se = &statsExporter{
798+
o: Options{
799+
ProjectID: "foo",
800+
ResourceByDescriptor: getResourceByDescriptor,
801+
},
802+
}
803+
804+
for i, tt := range tests {
805+
tsl, err := se.metricToMpbTs(context.Background(), tt.in)
806+
if tt.wantErr != "" {
807+
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
808+
t.Errorf("#%d: unmatched error. Got\n\t%v\nWant\n\t%v", i, err, tt.wantErr)
809+
}
810+
continue
811+
}
812+
if err != nil {
813+
t.Errorf("#%d: unexpected error: %v", i, err)
814+
continue
815+
}
816+
817+
got := se.combineTimeSeriesToCreateTimeSeriesRequest(tsl)
818+
// Our saving grace is serialization equality since some
819+
// unexported fields could be present in the various values.
820+
if diff := cmpTSReqs(got, tt.want); diff != "" {
821+
t.Fatalf("Test %d failed. Unexpected CreateTimeSeriesRequests -got +want: %s", i, diff)
822+
}
823+
}
824+
}
825+
826+
type customResource struct {
827+
rt string
828+
rm map[string]string
829+
}
830+
831+
var _ monitoredresource.Interface = (*customResource)(nil)
832+
833+
func (cr *customResource) MonitoredResource() (resType string, labels map[string]string) {
834+
return cr.rt, cr.rm
835+
}
836+
837+
var crEmpty = &customResource{rt: ""}
838+
839+
func getResourceByDescriptor(md *metricdata.Descriptor, labels map[string]string) (map[string]string, monitoredresource.Interface) {
840+
switch md.Name {
841+
case "custom_resource_one":
842+
cr := &customResource{
843+
rt: "one",
844+
rm: map[string]string{
845+
"k11": labels["k11"],
846+
},
847+
}
848+
newLabels := removeLabel(labels, cr.rm)
849+
return newLabels, cr
850+
case "custom_resource_two":
851+
cr := &customResource{
852+
rt: "two",
853+
rm: map[string]string{
854+
"k22": labels["k22"],
855+
},
856+
}
857+
newLabels := removeLabel(labels, cr.rm)
858+
return newLabels, cr
859+
default:
860+
return labels, crEmpty
861+
}
862+
}
863+
864+
func removeLabel(m map[string]string, remove map[string]string) map[string]string {
865+
newM := make(map[string]string)
866+
for k, v := range m {
867+
if _, ok := remove[k]; !ok {
868+
newM[k] = v
869+
}
870+
}
871+
return newM
872+
}

stackdriver.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,24 @@ type Options struct {
255255
// to Stackdriver Monitoring. This is only used for Proto metrics export
256256
// for now. The minimum number of workers is 1.
257257
NumberOfWorkers int
258+
259+
// ResourceByDescriptor may be provided to supply monitored resource dynamically
260+
// based on the metric Descriptor. Most users will not need to set this,
261+
// but should instead set ResourceDetector.
262+
//
263+
// The MonitoredResource and ResourceDetector fields are ignored if this
264+
// field is set to a non-nil value.
265+
//
266+
// The ResourceByDescriptor is called to derive monitored resources from
267+
// metric.Descriptor and the label map associated with the time-series.
268+
// If any label is used for the derived resource then it will be removed
269+
// from the label map. The remaining labels in the map are returned to
270+
// be used with the time-series.
271+
//
272+
// If the func set to this field does not return valid resource even for one
273+
// time-series then it will result into an error for the entire CreateTimeSeries request
274+
// which may contain more than one time-series.
275+
ResourceByDescriptor func(*metricdata.Descriptor, map[string]string) (map[string]string, monitoredresource.Interface)
258276
}
259277

260278
const defaultTimeout = 5 * time.Second

0 commit comments

Comments
 (0)