1// Copyright 2017, OpenCensus Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package stackdriver 16 17import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 monitoring "cloud.google.com/go/monitoring/apiv3/v2" 24 "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" 25 26 "github.com/golang/protobuf/ptypes" 27 "github.com/golang/protobuf/ptypes/timestamp" 28 "github.com/google/go-cmp/cmp" 29 "go.opencensus.io/stats" 30 "go.opencensus.io/stats/view" 31 "go.opencensus.io/tag" 32 "google.golang.org/api/option" 33 "google.golang.org/genproto/googleapis/api/distribution" 34 metricpb "google.golang.org/genproto/googleapis/api/metric" 35 monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" 36 monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3" 37 "google.golang.org/grpc" 38 "google.golang.org/protobuf/testing/protocmp" 39) 40 41var authOptions = []option.ClientOption{option.WithGRPCConn(&grpc.ClientConn{})} 42 43var testOptions = Options{ProjectID: "opencensus-test", MonitoringClientOptions: authOptions} 44 45func TestRejectBlankProjectID(t *testing.T) { 46 ids := []string{"", " ", " "} 47 for _, projectID := range ids { 48 opts := Options{ProjectID: projectID, MonitoringClientOptions: authOptions} 49 exp, err := newStatsExporter(opts) 50 if err == nil || exp != nil { 51 t.Errorf("%q ProjectID must be rejected: NewExporter() = %v err = %q", projectID, exp, err) 52 } 53 } 54} 55 56func TestExporter_makeReq(t *testing.T) { 57 m := stats.Float64("test-measure", "measure desc", "unit") 58 59 key, err := tag.NewKey("test_key") 60 if err != nil { 61 t.Fatal(err) 62 } 63 64 v := &view.View{ 65 Name: "example.com/views/testview", 66 Description: "desc", 67 TagKeys: []tag.Key{key}, 68 Measure: m, 69 Aggregation: view.Count(), 70 } 71 72 lastValueView := &view.View{ 73 Name: "lasttestview", 74 Description: "desc", 75 TagKeys: []tag.Key{key}, 76 Measure: m, 77 Aggregation: view.LastValue(), 78 } 79 80 distView := &view.View{ 81 Name: "distview", 82 Description: "desc", 83 Measure: m, 84 Aggregation: view.Distribution(2, 4, 7), 85 } 86 87 start := time.Now() 88 end := start.Add(time.Minute) 89 count1 := &view.CountData{Value: 10} 90 count2 := &view.CountData{Value: 16} 91 sum1 := &view.SumData{Value: 5.5} 92 sum2 := &view.SumData{Value: -11.1} 93 last1 := view.LastValueData{Value: 100} 94 last2 := view.LastValueData{Value: 200} 95 taskValue := getTaskValue() 96 97 tests := []struct { 98 name string 99 projID string 100 vd *view.Data 101 want []*monitoringpb.CreateTimeSeriesRequest 102 opts Options 103 }{ 104 { 105 name: "count agg + timeline", 106 projID: "proj-id", 107 vd: newTestViewData(v, start, end, count1, count2), 108 want: []*monitoringpb.CreateTimeSeriesRequest{ 109 { 110 Name: fmt.Sprintf("projects/%s", "proj-id"), 111 TimeSeries: []*monitoringpb.TimeSeries{ 112 { 113 Metric: &metricpb.Metric{ 114 Type: "custom.googleapis.com/opencensus/example.com/views/testview", 115 Labels: map[string]string{ 116 "test_key": "test-value-1", 117 opencensusTaskKey: taskValue, 118 }, 119 }, 120 Resource: &monitoredrespb.MonitoredResource{ 121 Type: "global", 122 }, 123 Points: []*monitoringpb.Point{ 124 { 125 Interval: &monitoringpb.TimeInterval{ 126 StartTime: ×tamp.Timestamp{ 127 Seconds: start.Unix(), 128 Nanos: int32(start.Nanosecond()), 129 }, 130 EndTime: ×tamp.Timestamp{ 131 Seconds: end.Unix(), 132 Nanos: int32(end.Nanosecond()), 133 }, 134 }, 135 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 136 Int64Value: 10, 137 }}, 138 }, 139 }, 140 }, 141 { 142 Metric: &metricpb.Metric{ 143 Type: "custom.googleapis.com/opencensus/example.com/views/testview", 144 Labels: map[string]string{ 145 "test_key": "test-value-2", 146 opencensusTaskKey: taskValue, 147 }, 148 }, 149 Resource: &monitoredrespb.MonitoredResource{ 150 Type: "global", 151 }, 152 Points: []*monitoringpb.Point{ 153 { 154 Interval: &monitoringpb.TimeInterval{ 155 StartTime: ×tamp.Timestamp{ 156 Seconds: start.Unix(), 157 Nanos: int32(start.Nanosecond()), 158 }, 159 EndTime: ×tamp.Timestamp{ 160 Seconds: end.Unix(), 161 Nanos: int32(end.Nanosecond()), 162 }, 163 }, 164 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 165 Int64Value: 16, 166 }}, 167 }, 168 }, 169 }, 170 }, 171 }, 172 }, 173 }, 174 { 175 name: "metric type formatter", 176 projID: "proj-id", 177 vd: newTestViewData(v, start, end, sum1, sum2), 178 opts: Options{ 179 GetMetricType: func(v *view.View) string { 180 return fmt.Sprintf("external.googleapis.com/%s", v.Name) 181 }, 182 }, 183 want: []*monitoringpb.CreateTimeSeriesRequest{ 184 { 185 Name: fmt.Sprintf("projects/%s", "proj-id"), 186 TimeSeries: []*monitoringpb.TimeSeries{ 187 { 188 Metric: &metricpb.Metric{ 189 Type: "external.googleapis.com/example.com/views/testview", 190 Labels: map[string]string{ 191 "test_key": "test-value-1", 192 opencensusTaskKey: taskValue, 193 }, 194 }, 195 Resource: &monitoredrespb.MonitoredResource{ 196 Type: "global", 197 }, 198 Points: []*monitoringpb.Point{ 199 { 200 Interval: &monitoringpb.TimeInterval{ 201 StartTime: ×tamp.Timestamp{ 202 Seconds: start.Unix(), 203 Nanos: int32(start.Nanosecond()), 204 }, 205 EndTime: ×tamp.Timestamp{ 206 Seconds: end.Unix(), 207 Nanos: int32(end.Nanosecond()), 208 }, 209 }, 210 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 211 DoubleValue: 5.5, 212 }}, 213 }, 214 }, 215 }, 216 { 217 Metric: &metricpb.Metric{ 218 Type: "external.googleapis.com/example.com/views/testview", 219 Labels: map[string]string{ 220 "test_key": "test-value-2", 221 opencensusTaskKey: taskValue, 222 }, 223 }, 224 Resource: &monitoredrespb.MonitoredResource{ 225 Type: "global", 226 }, 227 Points: []*monitoringpb.Point{ 228 { 229 Interval: &monitoringpb.TimeInterval{ 230 StartTime: ×tamp.Timestamp{ 231 Seconds: start.Unix(), 232 Nanos: int32(start.Nanosecond()), 233 }, 234 EndTime: ×tamp.Timestamp{ 235 Seconds: end.Unix(), 236 Nanos: int32(end.Nanosecond()), 237 }, 238 }, 239 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 240 DoubleValue: -11.1, 241 }}, 242 }, 243 }, 244 }, 245 }, 246 }, 247 }, 248 }, 249 { 250 name: "sum agg + timeline", 251 projID: "proj-id", 252 vd: newTestViewData(v, start, end, sum1, sum2), 253 want: []*monitoringpb.CreateTimeSeriesRequest{ 254 { 255 Name: fmt.Sprintf("projects/%s", "proj-id"), 256 TimeSeries: []*monitoringpb.TimeSeries{ 257 { 258 Metric: &metricpb.Metric{ 259 Type: "custom.googleapis.com/opencensus/example.com/views/testview", 260 Labels: map[string]string{ 261 "test_key": "test-value-1", 262 opencensusTaskKey: taskValue, 263 }, 264 }, 265 Resource: &monitoredrespb.MonitoredResource{ 266 Type: "global", 267 }, 268 Points: []*monitoringpb.Point{ 269 { 270 Interval: &monitoringpb.TimeInterval{ 271 StartTime: ×tamp.Timestamp{ 272 Seconds: start.Unix(), 273 Nanos: int32(start.Nanosecond()), 274 }, 275 EndTime: ×tamp.Timestamp{ 276 Seconds: end.Unix(), 277 Nanos: int32(end.Nanosecond()), 278 }, 279 }, 280 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 281 DoubleValue: 5.5, 282 }}, 283 }, 284 }, 285 }, 286 { 287 Metric: &metricpb.Metric{ 288 Type: "custom.googleapis.com/opencensus/example.com/views/testview", 289 Labels: map[string]string{ 290 "test_key": "test-value-2", 291 opencensusTaskKey: taskValue, 292 }, 293 }, 294 Resource: &monitoredrespb.MonitoredResource{ 295 Type: "global", 296 }, 297 Points: []*monitoringpb.Point{ 298 { 299 Interval: &monitoringpb.TimeInterval{ 300 StartTime: ×tamp.Timestamp{ 301 Seconds: start.Unix(), 302 Nanos: int32(start.Nanosecond()), 303 }, 304 EndTime: ×tamp.Timestamp{ 305 Seconds: end.Unix(), 306 Nanos: int32(end.Nanosecond()), 307 }, 308 }, 309 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 310 DoubleValue: -11.1, 311 }}, 312 }, 313 }, 314 }, 315 }, 316 }, 317 }, 318 }, 319 { 320 name: "last value agg", 321 projID: "proj-id", 322 vd: newTestViewData(lastValueView, start, end, &last1, &last2), 323 want: []*monitoringpb.CreateTimeSeriesRequest{ 324 { 325 Name: fmt.Sprintf("projects/%s", "proj-id"), 326 TimeSeries: []*monitoringpb.TimeSeries{ 327 { 328 Metric: &metricpb.Metric{ 329 Type: "custom.googleapis.com/opencensus/lasttestview", 330 Labels: map[string]string{ 331 "test_key": "test-value-1", 332 opencensusTaskKey: taskValue, 333 }, 334 }, 335 Resource: &monitoredrespb.MonitoredResource{ 336 Type: "global", 337 }, 338 Points: []*monitoringpb.Point{ 339 { 340 Interval: &monitoringpb.TimeInterval{ 341 EndTime: ×tamp.Timestamp{ 342 Seconds: end.Unix(), 343 Nanos: int32(end.Nanosecond()), 344 }, 345 }, 346 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 347 DoubleValue: 100, 348 }}, 349 }, 350 }, 351 }, 352 { 353 Metric: &metricpb.Metric{ 354 Type: "custom.googleapis.com/opencensus/lasttestview", 355 Labels: map[string]string{ 356 "test_key": "test-value-2", 357 opencensusTaskKey: taskValue, 358 }, 359 }, 360 Resource: &monitoredrespb.MonitoredResource{ 361 Type: "global", 362 }, 363 Points: []*monitoringpb.Point{ 364 { 365 Interval: &monitoringpb.TimeInterval{ 366 EndTime: ×tamp.Timestamp{ 367 Seconds: end.Unix(), 368 Nanos: int32(end.Nanosecond()), 369 }, 370 }, 371 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{ 372 DoubleValue: 200, 373 }}, 374 }, 375 }, 376 }, 377 }, 378 }, 379 }, 380 }, 381 { 382 name: "dist agg + time window - without zero bucket", 383 projID: "proj-id", 384 vd: newTestDistViewData(distView, start, end), 385 want: []*monitoringpb.CreateTimeSeriesRequest{{ 386 Name: fmt.Sprintf("projects/%s", "proj-id"), 387 TimeSeries: []*monitoringpb.TimeSeries{ 388 { 389 Metric: &metricpb.Metric{ 390 Type: "custom.googleapis.com/opencensus/distview", 391 Labels: map[string]string{ 392 opencensusTaskKey: taskValue, 393 }, 394 }, 395 Resource: &monitoredrespb.MonitoredResource{ 396 Type: "global", 397 }, 398 Points: []*monitoringpb.Point{ 399 { 400 Interval: &monitoringpb.TimeInterval{ 401 StartTime: ×tamp.Timestamp{ 402 Seconds: start.Unix(), 403 Nanos: int32(start.Nanosecond()), 404 }, 405 EndTime: ×tamp.Timestamp{ 406 Seconds: end.Unix(), 407 Nanos: int32(end.Nanosecond()), 408 }, 409 }, 410 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DistributionValue{ 411 DistributionValue: &distribution.Distribution{ 412 Count: 5, 413 Mean: 3.0, 414 SumOfSquaredDeviation: 1.5, 415 BucketOptions: &distribution.Distribution_BucketOptions{ 416 Options: &distribution.Distribution_BucketOptions_ExplicitBuckets{ 417 ExplicitBuckets: &distribution.Distribution_BucketOptions_Explicit{ 418 Bounds: []float64{0.0, 2.0, 4.0, 7.0}}}}, 419 BucketCounts: []int64{0, 2, 2, 1}}, 420 }}, 421 }, 422 }, 423 }, 424 }, 425 }}, 426 }, 427 { 428 name: "dist agg + time window + zero bucket", 429 projID: "proj-id", 430 vd: newTestDistViewData(distView, start, end), 431 want: []*monitoringpb.CreateTimeSeriesRequest{{ 432 Name: fmt.Sprintf("projects/%s", "proj-id"), 433 TimeSeries: []*monitoringpb.TimeSeries{ 434 { 435 Metric: &metricpb.Metric{ 436 Type: "custom.googleapis.com/opencensus/distview", 437 Labels: map[string]string{ 438 opencensusTaskKey: taskValue, 439 }, 440 }, 441 Resource: &monitoredrespb.MonitoredResource{ 442 Type: "global", 443 }, 444 Points: []*monitoringpb.Point{ 445 { 446 Interval: &monitoringpb.TimeInterval{ 447 StartTime: ×tamp.Timestamp{ 448 Seconds: start.Unix(), 449 Nanos: int32(start.Nanosecond()), 450 }, 451 EndTime: ×tamp.Timestamp{ 452 Seconds: end.Unix(), 453 Nanos: int32(end.Nanosecond()), 454 }, 455 }, 456 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DistributionValue{ 457 DistributionValue: &distribution.Distribution{ 458 Count: 5, 459 Mean: 3.0, 460 SumOfSquaredDeviation: 1.5, 461 BucketOptions: &distribution.Distribution_BucketOptions{ 462 Options: &distribution.Distribution_BucketOptions_ExplicitBuckets{ 463 ExplicitBuckets: &distribution.Distribution_BucketOptions_Explicit{ 464 Bounds: []float64{0.0, 2.0, 4.0, 7.0}}}}, 465 BucketCounts: []int64{0, 2, 2, 1}}, 466 }}, 467 }, 468 }, 469 }, 470 }, 471 }}, 472 }, 473 } 474 475 for _, tt := range tests { 476 t.Run(tt.name, func(t *testing.T) { 477 opts := tt.opts 478 opts.ProjectID = tt.projID 479 opts.MonitoringClientOptions = authOptions 480 e, err := newStatsExporter(opts) 481 if err != nil { 482 t.Fatal(err) 483 } 484 resps := e.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload) 485 if got, want := len(resps), len(tt.want); got != want { 486 t.Fatalf("%v: Exporter.makeReq() returned %d responses; want %d", tt.name, got, want) 487 } 488 if len(tt.want) == 0 { 489 return 490 } 491 if diff := cmp.Diff(resps, tt.want, protocmp.Transform()); diff != "" { 492 t.Errorf("Values differ -got +want: %s", diff) 493 } 494 }) 495 } 496} 497 498func TestTimeIntervalStaggering(t *testing.T) { 499 now := time.Now() 500 501 interval := toValidTimeIntervalpb(now, now) 502 503 start, err := ptypes.Timestamp(interval.StartTime) 504 if err != nil { 505 t.Fatalf("unable to convert start time from PB: %v", err) 506 } 507 508 end, err := ptypes.Timestamp(interval.EndTime) 509 if err != nil { 510 t.Fatalf("unable to convert end time to PB: %v", err) 511 } 512 513 if end.Before(start.Add(time.Millisecond)) { 514 t.Fatalf("expected end=%v to be at least %v after start=%v, but it wasn't", end, time.Millisecond, start) 515 } 516} 517 518func TestExporter_makeReq_batching(t *testing.T) { 519 m := stats.Float64("test-measure/makeReq_batching", "measure desc", "unit") 520 521 key, err := tag.NewKey("test_key") 522 if err != nil { 523 t.Fatal(err) 524 } 525 526 v := &view.View{ 527 Name: "view", 528 Description: "desc", 529 TagKeys: []tag.Key{key}, 530 Measure: m, 531 Aggregation: view.Count(), 532 } 533 534 tests := []struct { 535 name string 536 iter int 537 limit int 538 wantReqs int 539 wantTotal int 540 }{ 541 { 542 name: "4 vds; 3 limit", 543 iter: 2, 544 limit: 3, 545 wantReqs: 3, 546 wantTotal: 4, 547 }, 548 { 549 name: "4 vds; 4 limit", 550 iter: 2, 551 limit: 4, 552 wantReqs: 2, 553 wantTotal: 4, 554 }, 555 { 556 name: "4 vds; 5 limit", 557 iter: 2, 558 limit: 5, 559 wantReqs: 2, 560 wantTotal: 4, 561 }, 562 } 563 564 count1 := &view.CountData{Value: 10} 565 count2 := &view.CountData{Value: 16} 566 567 for _, tt := range tests { 568 var vds []*view.Data 569 for i := 0; i < tt.iter; i++ { 570 vds = append(vds, newTestViewData(v, time.Now(), time.Now(), count1, count2)) 571 } 572 573 e, err := newStatsExporter(testOptions) 574 if err != nil { 575 t.Fatal(err) 576 } 577 resps := e.makeReq(vds, tt.limit) 578 if len(resps) != tt.wantReqs { 579 t.Errorf("%v:\ngot %d:: %v;\n\nwant %d requests\n\n", tt.name, len(resps), resps, tt.wantReqs) 580 } 581 582 var total int 583 for _, resp := range resps { 584 total += len(resp.TimeSeries) 585 } 586 if got, want := total, tt.wantTotal; got != want { 587 t.Errorf("%v: len(resps[...].TimeSeries) = %d; want %d", tt.name, got, want) 588 } 589 } 590} 591 592func TestExporter_createMetricDescriptorFromView(t *testing.T) { 593 oldCreateMetricDescriptor := createMetricDescriptor 594 595 defer func() { 596 createMetricDescriptor = oldCreateMetricDescriptor 597 }() 598 599 key, _ := tag.NewKey("test-key-one") 600 m := stats.Float64("test-measure/TestExporter_createMetricDescriptorFromView", "measure desc", stats.UnitMilliseconds) 601 602 v := &view.View{ 603 Name: "test_view_sum", 604 Description: "view_description", 605 TagKeys: []tag.Key{key}, 606 Measure: m, 607 Aggregation: view.Sum(), 608 } 609 610 data := &view.CountData{Value: 0} 611 vd := newTestViewData(v, time.Now(), time.Now(), data, data) 612 613 var customLabels Labels 614 customLabels.Set("pid", "1234", "Local process identifier") 615 customLabels.Set("hostname", "test.example.com", "Local hostname") 616 customLabels.Set("a/b/c/host-name", "test.example.com", "Local hostname") 617 618 tests := []struct { 619 name string 620 opts Options 621 }{ 622 { 623 name: "default", 624 }, 625 { 626 name: "no default labels", 627 opts: Options{DefaultMonitoringLabels: &Labels{}}, 628 }, 629 { 630 name: "custom default labels", 631 opts: Options{DefaultMonitoringLabels: &customLabels}, 632 }, 633 } 634 635 for _, tt := range tests { 636 t.Run(tt.name, func(t *testing.T) { 637 opts := tt.opts 638 opts.MonitoringClientOptions = authOptions 639 opts.ProjectID = "test_project" 640 e, err := newStatsExporter(opts) 641 if err != nil { 642 t.Fatal(err) 643 } 644 645 var createCalls int 646 createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metricpb.MetricDescriptor, error) { 647 createCalls++ 648 if got, want := mdr.MetricDescriptor.Name, "projects/test_project/metricDescriptors/custom.googleapis.com/opencensus/test_view_sum"; got != want { 649 t.Errorf("MetricDescriptor.Name = %q; want %q", got, want) 650 } 651 if got, want := mdr.MetricDescriptor.Type, "custom.googleapis.com/opencensus/test_view_sum"; got != want { 652 t.Errorf("MetricDescriptor.Type = %q; want %q", got, want) 653 } 654 if got, want := mdr.MetricDescriptor.ValueType, metricpb.MetricDescriptor_DOUBLE; got != want { 655 t.Errorf("MetricDescriptor.ValueType = %q; want %q", got, want) 656 } 657 if got, want := mdr.MetricDescriptor.MetricKind, metricpb.MetricDescriptor_CUMULATIVE; got != want { 658 t.Errorf("MetricDescriptor.MetricKind = %q; want %q", got, want) 659 } 660 if got, want := mdr.MetricDescriptor.Description, "view_description"; got != want { 661 t.Errorf("MetricDescriptor.Description = %q; want %q", got, want) 662 } 663 if got, want := mdr.MetricDescriptor.DisplayName, "OpenCensus/test_view_sum"; got != want { 664 t.Errorf("MetricDescriptor.DisplayName = %q; want %q", got, want) 665 } 666 if got, want := mdr.MetricDescriptor.Unit, stats.UnitMilliseconds; got != want { 667 t.Errorf("MetricDescriptor.Unit = %q; want %q", got, want) 668 } 669 return &metricpb.MetricDescriptor{ 670 DisplayName: "OpenCensus/test_view_sum", 671 Description: "view_description", 672 Unit: stats.UnitMilliseconds, 673 Type: "custom.googleapis.com/opencensus/test_view_sum", 674 MetricKind: metricpb.MetricDescriptor_CUMULATIVE, 675 ValueType: metricpb.MetricDescriptor_DOUBLE, 676 Labels: newLabelDescriptors(e.defaultLabels, vd.View.TagKeys), 677 }, nil 678 } 679 680 ctx := context.Background() 681 if err := e.createMetricDescriptorFromView(ctx, vd.View); err != nil { 682 t.Errorf("Exporter.createMetricDescriptorFromView() error = %v", err) 683 } 684 if err := e.createMetricDescriptorFromView(ctx, vd.View); err != nil { 685 t.Errorf("Exporter.createMetricDescriptorFromView() error = %v", err) 686 } 687 if count := createCalls; count != 1 { 688 t.Errorf("createMetricDescriptor needs to be called for once; called %v times", count) 689 } 690 if count := len(e.metricDescriptors); count != 1 { 691 t.Errorf("len(e.metricDescriptors) = %v; want 1", count) 692 } 693 }) 694 } 695} 696 697func TestExporter_createMetricDescriptorFromView_CountAggregation(t *testing.T) { 698 oldCreateMetricDescriptor := createMetricDescriptor 699 700 defer func() { 701 createMetricDescriptor = oldCreateMetricDescriptor 702 }() 703 704 key, _ := tag.NewKey("test-key-one") 705 m := stats.Float64("test-measure/TestExporter_createMetricDescriptorFromView", "measure desc", stats.UnitMilliseconds) 706 707 v := &view.View{ 708 Name: "test_view_count", 709 Description: "view_description", 710 TagKeys: []tag.Key{key}, 711 Measure: m, 712 Aggregation: view.Count(), 713 } 714 715 data := &view.CountData{Value: 0} 716 vd := newTestViewData(v, time.Now(), time.Now(), data, data) 717 718 e := &statsExporter{ 719 metricDescriptors: make(map[string]bool), 720 o: Options{ProjectID: "test_project"}, 721 } 722 723 createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metricpb.MetricDescriptor, error) { 724 if got, want := mdr.MetricDescriptor.Name, "projects/test_project/metricDescriptors/custom.googleapis.com/opencensus/test_view_count"; got != want { 725 t.Errorf("MetricDescriptor.Name = %q; want %q", got, want) 726 } 727 if got, want := mdr.MetricDescriptor.Type, "custom.googleapis.com/opencensus/test_view_count"; got != want { 728 t.Errorf("MetricDescriptor.Type = %q; want %q", got, want) 729 } 730 if got, want := mdr.MetricDescriptor.ValueType, metricpb.MetricDescriptor_INT64; got != want { 731 t.Errorf("MetricDescriptor.ValueType = %q; want %q", got, want) 732 } 733 if got, want := mdr.MetricDescriptor.MetricKind, metricpb.MetricDescriptor_CUMULATIVE; got != want { 734 t.Errorf("MetricDescriptor.MetricKind = %q; want %q", got, want) 735 } 736 if got, want := mdr.MetricDescriptor.Description, "view_description"; got != want { 737 t.Errorf("MetricDescriptor.Description = %q; want %q", got, want) 738 } 739 if got, want := mdr.MetricDescriptor.DisplayName, "OpenCensus/test_view_count"; got != want { 740 t.Errorf("MetricDescriptor.DisplayName = %q; want %q", got, want) 741 } 742 if got, want := mdr.MetricDescriptor.Unit, stats.UnitDimensionless; got != want { 743 t.Errorf("MetricDescriptor.Unit = %q; want %q", got, want) 744 } 745 return &metricpb.MetricDescriptor{ 746 DisplayName: "OpenCensus/test_view_sum", 747 Description: "view_description", 748 Unit: stats.UnitDimensionless, 749 Type: "custom.googleapis.com/opencensus/test_view_count", 750 MetricKind: metricpb.MetricDescriptor_CUMULATIVE, 751 ValueType: metricpb.MetricDescriptor_INT64, 752 Labels: newLabelDescriptors(nil, vd.View.TagKeys), 753 }, nil 754 } 755 ctx := context.Background() 756 if err := e.createMetricDescriptorFromView(ctx, vd.View); err != nil { 757 t.Errorf("Exporter.createMetricDescriptorFromView() error = %v", err) 758 } 759} 760 761func TestExporter_makeReq_withCustomMonitoredResource(t *testing.T) { 762 m := stats.Float64("test-measure/TestExporter_makeReq_withCustomMonitoredResource", "measure desc", "unit") 763 764 key, err := tag.NewKey("test_key") 765 if err != nil { 766 t.Fatal(err) 767 } 768 769 v := &view.View{ 770 Name: "testview", 771 Description: "desc", 772 TagKeys: []tag.Key{key}, 773 Measure: m, 774 Aggregation: view.Count(), 775 } 776 if err := view.Register(v); err != nil { 777 t.Fatal(err) 778 } 779 defer view.Unregister(v) 780 781 start := time.Now() 782 end := start.Add(time.Minute) 783 count1 := &view.CountData{Value: 10} 784 count2 := &view.CountData{Value: 16} 785 taskValue := getTaskValue() 786 787 resource := &monitoredrespb.MonitoredResource{ 788 Type: "gce_instance", 789 Labels: map[string]string{ 790 "project_id": "proj-id", 791 "instance_id": "instance", 792 "zone": "us-west-1a", 793 }, 794 } 795 796 gceInst := &monitoredresource.GCEInstance{ 797 ProjectID: "proj-id", 798 InstanceID: "instance", 799 Zone: "us-west-1a", 800 } 801 802 tests := []struct { 803 name string 804 opts Options 805 vd *view.Data 806 want []*monitoringpb.CreateTimeSeriesRequest 807 }{ 808 { 809 name: "count agg timeline", 810 opts: Options{Resource: resource}, 811 vd: newTestViewData(v, start, end, count1, count2), 812 want: []*monitoringpb.CreateTimeSeriesRequest{ 813 { 814 Name: fmt.Sprintf("projects/%s", "proj-id"), 815 TimeSeries: []*monitoringpb.TimeSeries{ 816 { 817 Metric: &metricpb.Metric{ 818 Type: "custom.googleapis.com/opencensus/testview", 819 Labels: map[string]string{ 820 "test_key": "test-value-1", 821 opencensusTaskKey: taskValue, 822 }, 823 }, 824 Resource: resource, 825 Points: []*monitoringpb.Point{ 826 { 827 Interval: &monitoringpb.TimeInterval{ 828 StartTime: ×tamp.Timestamp{ 829 Seconds: start.Unix(), 830 Nanos: int32(start.Nanosecond()), 831 }, 832 EndTime: ×tamp.Timestamp{ 833 Seconds: end.Unix(), 834 Nanos: int32(end.Nanosecond()), 835 }, 836 }, 837 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 838 Int64Value: 10, 839 }}, 840 }, 841 }, 842 }, 843 { 844 Metric: &metricpb.Metric{ 845 Type: "custom.googleapis.com/opencensus/testview", 846 Labels: map[string]string{ 847 "test_key": "test-value-2", 848 opencensusTaskKey: taskValue, 849 }, 850 }, 851 Resource: resource, 852 Points: []*monitoringpb.Point{ 853 { 854 Interval: &monitoringpb.TimeInterval{ 855 StartTime: ×tamp.Timestamp{ 856 Seconds: start.Unix(), 857 Nanos: int32(start.Nanosecond()), 858 }, 859 EndTime: ×tamp.Timestamp{ 860 Seconds: end.Unix(), 861 Nanos: int32(end.Nanosecond()), 862 }, 863 }, 864 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 865 Int64Value: 16, 866 }}, 867 }, 868 }, 869 }, 870 }, 871 }, 872 }, 873 }, 874 { 875 name: "with MonitoredResource and labels", 876 opts: func() Options { 877 var labels Labels 878 labels.Set("pid", "1234", "Process identifier") 879 return Options{ 880 MonitoredResource: gceInst, 881 DefaultMonitoringLabels: &labels, 882 } 883 }(), 884 vd: newTestViewData(v, start, end, count1, count2), 885 want: []*monitoringpb.CreateTimeSeriesRequest{ 886 { 887 Name: fmt.Sprintf("projects/%s", "proj-id"), 888 TimeSeries: []*monitoringpb.TimeSeries{ 889 { 890 Metric: &metricpb.Metric{ 891 Type: "custom.googleapis.com/opencensus/testview", 892 Labels: map[string]string{ 893 "test_key": "test-value-1", 894 "pid": "1234", 895 }, 896 }, 897 Resource: resource, 898 Points: []*monitoringpb.Point{ 899 { 900 Interval: &monitoringpb.TimeInterval{ 901 StartTime: ×tamp.Timestamp{ 902 Seconds: start.Unix(), 903 Nanos: int32(start.Nanosecond()), 904 }, 905 EndTime: ×tamp.Timestamp{ 906 Seconds: end.Unix(), 907 Nanos: int32(end.Nanosecond()), 908 }, 909 }, 910 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 911 Int64Value: 10, 912 }}, 913 }, 914 }, 915 }, 916 { 917 Metric: &metricpb.Metric{ 918 Type: "custom.googleapis.com/opencensus/testview", 919 Labels: map[string]string{ 920 "test_key": "test-value-2", 921 "pid": "1234", 922 }, 923 }, 924 Resource: resource, 925 Points: []*monitoringpb.Point{ 926 { 927 Interval: &monitoringpb.TimeInterval{ 928 StartTime: ×tamp.Timestamp{ 929 Seconds: start.Unix(), 930 Nanos: int32(start.Nanosecond()), 931 }, 932 EndTime: ×tamp.Timestamp{ 933 Seconds: end.Unix(), 934 Nanos: int32(end.Nanosecond()), 935 }, 936 }, 937 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 938 Int64Value: 16, 939 }}, 940 }, 941 }, 942 }, 943 }, 944 }, 945 }, 946 }, 947 { 948 name: "custom default monitoring labels", 949 opts: func() Options { 950 var labels Labels 951 labels.Set("pid", "1234", "Process identifier") 952 return Options{ 953 Resource: resource, 954 DefaultMonitoringLabels: &labels, 955 } 956 }(), 957 vd: newTestViewData(v, start, end, count1, count2), 958 want: []*monitoringpb.CreateTimeSeriesRequest{ 959 { 960 Name: fmt.Sprintf("projects/%s", "proj-id"), 961 TimeSeries: []*monitoringpb.TimeSeries{ 962 { 963 Metric: &metricpb.Metric{ 964 Type: "custom.googleapis.com/opencensus/testview", 965 Labels: map[string]string{ 966 "test_key": "test-value-1", 967 "pid": "1234", 968 }, 969 }, 970 Resource: resource, 971 Points: []*monitoringpb.Point{ 972 { 973 Interval: &monitoringpb.TimeInterval{ 974 StartTime: ×tamp.Timestamp{ 975 Seconds: start.Unix(), 976 Nanos: int32(start.Nanosecond()), 977 }, 978 EndTime: ×tamp.Timestamp{ 979 Seconds: end.Unix(), 980 Nanos: int32(end.Nanosecond()), 981 }, 982 }, 983 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 984 Int64Value: 10, 985 }}, 986 }, 987 }, 988 }, 989 { 990 Metric: &metricpb.Metric{ 991 Type: "custom.googleapis.com/opencensus/testview", 992 Labels: map[string]string{ 993 "test_key": "test-value-2", 994 "pid": "1234", 995 }, 996 }, 997 Resource: resource, 998 Points: []*monitoringpb.Point{ 999 { 1000 Interval: &monitoringpb.TimeInterval{ 1001 StartTime: ×tamp.Timestamp{ 1002 Seconds: start.Unix(), 1003 Nanos: int32(start.Nanosecond()), 1004 }, 1005 EndTime: ×tamp.Timestamp{ 1006 Seconds: end.Unix(), 1007 Nanos: int32(end.Nanosecond()), 1008 }, 1009 }, 1010 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 1011 Int64Value: 16, 1012 }}, 1013 }, 1014 }, 1015 }, 1016 }, 1017 }, 1018 }, 1019 }, 1020 { 1021 name: "count agg timeline", 1022 opts: Options{Resource: resource}, 1023 vd: newTestViewData(v, start, end, count1, count2), 1024 want: []*monitoringpb.CreateTimeSeriesRequest{ 1025 { 1026 Name: fmt.Sprintf("projects/%s", "proj-id"), 1027 TimeSeries: []*monitoringpb.TimeSeries{ 1028 { 1029 Metric: &metricpb.Metric{ 1030 Type: "custom.googleapis.com/opencensus/testview", 1031 Labels: map[string]string{ 1032 "test_key": "test-value-1", 1033 opencensusTaskKey: taskValue, 1034 }, 1035 }, 1036 Resource: resource, 1037 Points: []*monitoringpb.Point{ 1038 { 1039 Interval: &monitoringpb.TimeInterval{ 1040 StartTime: ×tamp.Timestamp{ 1041 Seconds: start.Unix(), 1042 Nanos: int32(start.Nanosecond()), 1043 }, 1044 EndTime: ×tamp.Timestamp{ 1045 Seconds: end.Unix(), 1046 Nanos: int32(end.Nanosecond()), 1047 }, 1048 }, 1049 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 1050 Int64Value: 10, 1051 }}, 1052 }, 1053 }, 1054 }, 1055 { 1056 Metric: &metricpb.Metric{ 1057 Type: "custom.googleapis.com/opencensus/testview", 1058 Labels: map[string]string{ 1059 "test_key": "test-value-2", 1060 opencensusTaskKey: taskValue, 1061 }, 1062 }, 1063 Resource: resource, 1064 Points: []*monitoringpb.Point{ 1065 { 1066 Interval: &monitoringpb.TimeInterval{ 1067 StartTime: ×tamp.Timestamp{ 1068 Seconds: start.Unix(), 1069 Nanos: int32(start.Nanosecond()), 1070 }, 1071 EndTime: ×tamp.Timestamp{ 1072 Seconds: end.Unix(), 1073 Nanos: int32(end.Nanosecond()), 1074 }, 1075 }, 1076 Value: &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{ 1077 Int64Value: 16, 1078 }}, 1079 }, 1080 }, 1081 }, 1082 }, 1083 }, 1084 }, 1085 }, 1086 } 1087 for _, tt := range tests { 1088 t.Run(tt.name, func(t *testing.T) { 1089 opts := tt.opts 1090 opts.MonitoringClientOptions = authOptions 1091 opts.ProjectID = "proj-id" 1092 e, err := NewExporter(opts) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 resps := e.statsExporter.makeReq([]*view.Data{tt.vd}, maxTimeSeriesPerUpload) 1097 if got, want := len(resps), len(tt.want); got != want { 1098 t.Fatalf("%v: Exporter.makeReq() returned %d responses; want %d", tt.name, got, want) 1099 } 1100 if len(tt.want) == 0 { 1101 return 1102 } 1103 if diff := cmp.Diff(resps, tt.want, protocmp.Transform()); diff != "" { 1104 t.Errorf("Requests differ, -got +want: %s", diff) 1105 } 1106 }) 1107 } 1108} 1109 1110func TestExporter_customContext(t *testing.T) { 1111 oldCreateMetricDescriptor := createMetricDescriptor 1112 oldCreateTimeSeries := createTimeSeries 1113 1114 defer func() { 1115 createMetricDescriptor = oldCreateMetricDescriptor 1116 createTimeSeries = oldCreateTimeSeries 1117 }() 1118 1119 var timedOut = 0 1120 createMetricDescriptor = func(ctx context.Context, c *monitoring.MetricClient, mdr *monitoringpb.CreateMetricDescriptorRequest) (*metricpb.MetricDescriptor, error) { 1121 select { 1122 case <-time.After(1 * time.Second): 1123 fmt.Println("createMetricDescriptor did not time out") 1124 case <-ctx.Done(): 1125 timedOut++ 1126 } 1127 return &metricpb.MetricDescriptor{}, nil 1128 } 1129 createTimeSeries = func(ctx context.Context, c *monitoring.MetricClient, ts *monitoringpb.CreateTimeSeriesRequest) error { 1130 select { 1131 case <-time.After(1 * time.Second): 1132 fmt.Println("createTimeSeries did not time out") 1133 case <-ctx.Done(): 1134 timedOut++ 1135 } 1136 return nil 1137 } 1138 1139 v := &view.View{ 1140 Name: "test_view_count", 1141 Description: "view_description", 1142 Measure: stats.Float64("test-measure/TestExporter_createMetricDescriptorFromView", "measure desc", stats.UnitMilliseconds), 1143 Aggregation: view.Count(), 1144 } 1145 1146 data := &view.CountData{Value: 0} 1147 vd := newTestViewData(v, time.Now(), time.Now(), data, data) 1148 1149 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 1150 defer cancel() 1151 e := &statsExporter{ 1152 metricDescriptors: make(map[string]bool), 1153 o: Options{ProjectID: "test_project", Context: ctx}, 1154 } 1155 if err := e.uploadStats([]*view.Data{vd}); err != nil { 1156 t.Errorf("Exporter.uploadStats() error = %v", err) 1157 } 1158 if ctx.Err() != context.DeadlineExceeded { 1159 t.Errorf("expected context to time out; got %v", ctx.Err()) 1160 } 1161 if timedOut != 2 { 1162 t.Errorf("expected two functions to time out; got %d", timedOut) 1163 } 1164} 1165 1166func newTestViewData(v *view.View, start, end time.Time, data1, data2 view.AggregationData) *view.Data { 1167 key, _ := tag.NewKey("test-key") 1168 tag1 := tag.Tag{Key: key, Value: "test-value-1"} 1169 tag2 := tag.Tag{Key: key, Value: "test-value-2"} 1170 return &view.Data{ 1171 View: v, 1172 Rows: []*view.Row{ 1173 { 1174 Tags: []tag.Tag{tag1}, 1175 Data: data1, 1176 }, 1177 { 1178 Tags: []tag.Tag{tag2}, 1179 Data: data2, 1180 }, 1181 }, 1182 Start: start, 1183 End: end, 1184 } 1185} 1186 1187func newTestDistViewData(v *view.View, start, end time.Time) *view.Data { 1188 return &view.Data{ 1189 View: v, 1190 Rows: []*view.Row{ 1191 {Data: &view.DistributionData{ 1192 Count: 5, 1193 Min: 1, 1194 Max: 7, 1195 Mean: 3, 1196 SumOfSquaredDev: 1.5, 1197 CountPerBucket: []int64{2, 2, 1}, 1198 }}, 1199 }, 1200 Start: start, 1201 End: end, 1202 } 1203} 1204