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

Commit 5d4eb99

Browse files
authored
transform resources for metrics exported via ExportMetricsProto api (#158)
* transform resources for metrics exported via ExportMetricsProto interface. * added test case for per metric resource.
1 parent 69e294b commit 5d4eb99

5 files changed

Lines changed: 256 additions & 76 deletions

File tree

equivalence_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func TestStatsAndMetricsEquivalence(t *testing.T) {
9090
// Now perform some exporting.
9191
for i, vd := range vdl {
9292
se := &statsExporter{
93-
o: Options{ProjectID: "equivalence"},
93+
o: Options{ProjectID: "equivalence", MapResource: defaultMapResource},
9494
}
9595

9696
ctx := context.Background()
@@ -143,6 +143,7 @@ func TestEquivalenceStatsVsMetricsUploads(t *testing.T) {
143143
// so that batching is performed deterministically and flushing is
144144
// fully controlled by us.
145145
BundleDelayThreshold: 2 * time.Hour,
146+
MapResource: defaultMapResource,
146147
}
147148
se, err := newStatsExporter(exporterOptions)
148149
if err != nil {

metrics_proto.go

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ import (
3535
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
3636
labelpb "google.golang.org/genproto/googleapis/api/label"
3737
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
38-
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
3938
monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
4039

4140
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
4241
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
4342
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
43+
"go.opencensus.io/resource"
4444
)
4545

4646
var errNilMetric = errors.New("expecting a non-nil metric")
@@ -328,6 +328,23 @@ func (se *statsExporter) combineTimeSeriesToCreateTimeSeriesRequest(ts []*monito
328328
return ctsreql
329329
}
330330

331+
func resourcepbToResource(rsc *resourcepb.Resource) *resource.Resource {
332+
if rsc == nil {
333+
return &resource.Resource{
334+
Type: "global",
335+
}
336+
}
337+
res := &resource.Resource{
338+
Type: rsc.Type,
339+
Labels: make(map[string]string, len(rsc.Labels)),
340+
}
341+
342+
for k, v := range rsc.Labels {
343+
res.Labels[k] = v
344+
}
345+
return res
346+
}
347+
331348
// protoMetricToTimeSeries converts a metric into a Stackdriver Monitoring v3 API CreateTimeSeriesRequest
332349
// but it doesn't invoke any remote API.
333350
func (se *statsExporter) protoMetricToTimeSeries(ctx context.Context, node *commonpb.Node, rsc *resourcepb.Resource, metric *metricspb.Metric, additionalLabels map[string]labelValue) ([]*monitoringpb.TimeSeries, error) {
@@ -340,6 +357,8 @@ func (se *statsExporter) protoMetricToTimeSeries(ctx context.Context, node *comm
340357
resource = metric.Resource
341358
}
342359

360+
mappedRes := se.o.MapResource(resourcepbToResource(resource))
361+
343362
metricName, _, _, err := metricProseFromProto(metric)
344363
if err != nil {
345364
return nil, err
@@ -367,7 +386,7 @@ func (se *statsExporter) protoMetricToTimeSeries(ctx context.Context, node *comm
367386
Type: metricType,
368387
Labels: labels,
369388
},
370-
Resource: protoResourceToMonitoredResource(resource),
389+
Resource: mappedRes,
371390
Points: sdPoints,
372391
})
373392
}
@@ -686,28 +705,6 @@ func protoMetricDescriptorTypeToMetricKind(m *metricspb.Metric) (googlemetricpb.
686705
}
687706
}
688707

689-
func protoResourceToMonitoredResource(rsp *resourcepb.Resource) *monitoredrespb.MonitoredResource {
690-
if rsp == nil {
691-
return &monitoredrespb.MonitoredResource{
692-
Type: "global",
693-
}
694-
}
695-
typ := rsp.Type
696-
if typ == "" {
697-
typ = "global"
698-
}
699-
mrsp := &monitoredrespb.MonitoredResource{
700-
Type: typ,
701-
}
702-
if rsp.Labels != nil {
703-
mrsp.Labels = make(map[string]string, len(rsp.Labels))
704-
for k, v := range rsp.Labels {
705-
mrsp.Labels[k] = v
706-
}
707-
}
708-
return mrsp
709-
}
710-
711708
func getDefaultLabelsFromNode(node *commonpb.Node) map[string]labelValue {
712709
taskValue := fmt.Sprintf("%s-%d@%s", strings.ToLower(node.LibraryInfo.GetLanguage().String()), node.Identifier.Pid, node.Identifier.HostName)
713710
return map[string]labelValue{

metrics_proto_test.go

Lines changed: 184 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"testing"
2222

2323
"cloud.google.com/go/monitoring/apiv3"
24+
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
2425
"github.com/golang/protobuf/ptypes/timestamp"
2526
distributionpb "google.golang.org/genproto/googleapis/api/distribution"
2627
labelpb "google.golang.org/genproto/googleapis/api/label"
@@ -30,56 +31,11 @@ import (
3031

3132
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
3233
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
33-
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
3434
"github.com/golang/protobuf/ptypes/wrappers"
3535
"github.com/google/go-cmp/cmp"
36+
"go.opencensus.io/resource/resourcekeys"
3637
)
3738

38-
func TestProtoResourceToMonitoringResource(t *testing.T) {
39-
tests := []struct {
40-
in *resourcepb.Resource
41-
want *monitoredrespb.MonitoredResource
42-
}{
43-
{in: nil, want: &monitoredrespb.MonitoredResource{Type: "global"}},
44-
{in: &resourcepb.Resource{}, want: &monitoredrespb.MonitoredResource{Type: "global"}},
45-
{
46-
in: &resourcepb.Resource{
47-
Type: "foo",
48-
},
49-
want: &monitoredrespb.MonitoredResource{
50-
Type: "foo",
51-
},
52-
},
53-
{
54-
in: &resourcepb.Resource{
55-
Type: "foo",
56-
Labels: map[string]string{},
57-
},
58-
want: &monitoredrespb.MonitoredResource{
59-
Type: "foo",
60-
Labels: map[string]string{},
61-
},
62-
},
63-
{
64-
in: &resourcepb.Resource{
65-
Type: "foo",
66-
Labels: map[string]string{"a": "A"},
67-
},
68-
want: &monitoredrespb.MonitoredResource{
69-
Type: "foo",
70-
Labels: map[string]string{"a": "A"},
71-
},
72-
},
73-
}
74-
75-
for i, tt := range tests {
76-
got := protoResourceToMonitoredResource(tt.in)
77-
if diff := cmpResource(got, tt.want); diff != "" {
78-
t.Fatalf("Test %d failed. Unexpected Resource -got +want: %s", i, diff)
79-
}
80-
}
81-
}
82-
8339
func TestProtoMetricToCreateTimeSeriesRequest(t *testing.T) {
8440
startTimestamp := &timestamp.Timestamp{
8541
Seconds: 1543160298,
@@ -133,7 +89,7 @@ func TestProtoMetricToCreateTimeSeriesRequest(t *testing.T) {
13389
},
13490
},
13591
statsExporter: &statsExporter{
136-
o: Options{ProjectID: "foo"},
92+
o: Options{ProjectID: "foo", MapResource: defaultMapResource},
13793
},
13894
want: []*monitoringpb.CreateTimeSeriesRequest{
13995
{
@@ -205,6 +161,187 @@ func TestProtoMetricToCreateTimeSeriesRequest(t *testing.T) {
205161
}
206162
}
207163

164+
func TestProtoMetricWithDifferentResource(t *testing.T) {
165+
startTimestamp := &timestamp.Timestamp{
166+
Seconds: 1543160298,
167+
Nanos: 100000090,
168+
}
169+
endTimestamp := &timestamp.Timestamp{
170+
Seconds: 1543160298,
171+
Nanos: 100000997,
172+
}
173+
174+
tests := []struct {
175+
in *metricspb.Metric
176+
want []*monitoringpb.CreateTimeSeriesRequest
177+
wantErr string
178+
statsExporter *statsExporter
179+
}{
180+
{
181+
in: &metricspb.Metric{
182+
MetricDescriptor: &metricspb.MetricDescriptor{
183+
Name: "with_k8s_resource",
184+
Description: "This is a test",
185+
Unit: "By",
186+
},
187+
Resource: &resourcepb.Resource{
188+
Type: resourcekeys.K8SType,
189+
Labels: map[string]string{
190+
resourcekeys.K8SKeyClusterName: "cluster1",
191+
resourcekeys.K8SKeyPodName: "pod1",
192+
resourcekeys.K8SKeyNamespaceName: "namespace1",
193+
resourcekeys.ContainerKeyName: "container-name1",
194+
resourcekeys.CloudKeyZone: "zone1",
195+
},
196+
},
197+
Timeseries: []*metricspb.TimeSeries{
198+
{
199+
StartTimestamp: startTimestamp,
200+
Points: []*metricspb.Point{
201+
{
202+
Timestamp: endTimestamp,
203+
Value: &metricspb.Point_Int64Value{
204+
Int64Value: 1,
205+
},
206+
},
207+
},
208+
},
209+
},
210+
},
211+
statsExporter: &statsExporter{
212+
o: Options{ProjectID: "foo", MapResource: defaultMapResource},
213+
},
214+
want: []*monitoringpb.CreateTimeSeriesRequest{
215+
{
216+
Name: "projects/foo",
217+
TimeSeries: []*monitoringpb.TimeSeries{
218+
{
219+
Metric: &googlemetricpb.Metric{
220+
Type: "custom.googleapis.com/opencensus/with_k8s_resource",
221+
Labels: map[string]string{},
222+
},
223+
Resource: &monitoredrespb.MonitoredResource{
224+
Type: "k8s_container",
225+
Labels: map[string]string{
226+
"location": "zone1",
227+
"cluster_name": "cluster1",
228+
"namespace_name": "namespace1",
229+
"pod_name": "pod1",
230+
"container_name": "container-name1",
231+
},
232+
},
233+
Points: []*monitoringpb.Point{
234+
{
235+
Interval: &monitoringpb.TimeInterval{
236+
StartTime: startTimestamp,
237+
EndTime: endTimestamp,
238+
},
239+
Value: &monitoringpb.TypedValue{
240+
Value: &monitoringpb.TypedValue_Int64Value{
241+
Int64Value: 1,
242+
},
243+
},
244+
},
245+
},
246+
},
247+
},
248+
},
249+
},
250+
},
251+
{
252+
in: &metricspb.Metric{
253+
MetricDescriptor: &metricspb.MetricDescriptor{
254+
Name: "with_gce_resource",
255+
Description: "This is a test",
256+
Unit: "By",
257+
},
258+
Resource: &resourcepb.Resource{
259+
Type: resourcekeys.CloudType,
260+
Labels: map[string]string{
261+
resourcekeys.CloudKeyProvider: resourcekeys.CloudProviderGCP,
262+
resourcekeys.HostKeyID: "inst1",
263+
resourcekeys.CloudKeyZone: "zone1",
264+
},
265+
},
266+
Timeseries: []*metricspb.TimeSeries{
267+
{
268+
StartTimestamp: startTimestamp,
269+
Points: []*metricspb.Point{
270+
{
271+
Timestamp: endTimestamp,
272+
Value: &metricspb.Point_Int64Value{
273+
Int64Value: 1,
274+
},
275+
},
276+
},
277+
},
278+
},
279+
},
280+
statsExporter: &statsExporter{
281+
o: Options{ProjectID: "foo", MapResource: defaultMapResource},
282+
},
283+
want: []*monitoringpb.CreateTimeSeriesRequest{
284+
{
285+
Name: "projects/foo",
286+
TimeSeries: []*monitoringpb.TimeSeries{
287+
{
288+
Metric: &googlemetricpb.Metric{
289+
Type: "custom.googleapis.com/opencensus/with_gce_resource",
290+
Labels: map[string]string{},
291+
},
292+
Resource: &monitoredrespb.MonitoredResource{
293+
Type: "gce_instance",
294+
Labels: map[string]string{
295+
"instance_id": "inst1",
296+
"zone": "zone1",
297+
},
298+
},
299+
Points: []*monitoringpb.Point{
300+
{
301+
Interval: &monitoringpb.TimeInterval{
302+
StartTime: startTimestamp,
303+
EndTime: endTimestamp,
304+
},
305+
Value: &monitoringpb.TypedValue{
306+
Value: &monitoringpb.TypedValue_Int64Value{
307+
Int64Value: 1,
308+
},
309+
},
310+
},
311+
},
312+
},
313+
},
314+
},
315+
},
316+
},
317+
}
318+
319+
for i, tt := range tests {
320+
se := tt.statsExporter
321+
if se == nil {
322+
se = new(statsExporter)
323+
}
324+
tsl, err := se.protoMetricToTimeSeries(context.Background(), nil, nil, tt.in, nil)
325+
if tt.wantErr != "" {
326+
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
327+
t.Errorf("#%d: unmatched error. Got\n\t%v\nWant\n\t%v", i, err, tt.wantErr)
328+
}
329+
continue
330+
}
331+
if err != nil {
332+
t.Errorf("#%d: unexpected error: %v", i, err)
333+
continue
334+
}
335+
336+
got := se.combineTimeSeriesToCreateTimeSeriesRequest(tsl)
337+
// Our saving grace is serialization equality since some
338+
// unexported fields could be present in the various values.
339+
if diff := cmpTSReqs(got, tt.want); diff != "" {
340+
t.Fatalf("Test %d failed. Unexpected CreateTimeSeriesRequests -got +want: %s", i, diff)
341+
}
342+
}
343+
}
344+
208345
func TestProtoToMonitoringMetricDescriptor(t *testing.T) {
209346
tests := []struct {
210347
in *metricspb.Metric

0 commit comments

Comments
 (0)