devtools: update tools to use monitoring v3 APIs.

MultiPart: 1/2
Change-Id: I6570b48beed533ba212bd5eb649cb8e7c6af050a
diff --git a/internal/monitoring/monitoring.go b/internal/monitoring/monitoring.go
index bc863e8..a5e096c 100644
--- a/internal/monitoring/monitoring.go
+++ b/internal/monitoring/monitoring.go
@@ -8,14 +8,15 @@
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"sort"
 
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/google"
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 )
 
 const (
-	customMetricPrefix = "custom.cloudmonitoring.googleapis.com"
+	customMetricPrefix = "custom.googleapis.com"
 )
 
 type ServiceLocation struct {
@@ -54,9 +55,9 @@
 	},
 }
 
-// CustomMetricDescriptors is a map from metric's short names to their
+// customMetricDescriptors is a map from metric's short names to their
 // MetricDescriptor definitions.
-var CustomMetricDescriptors = map[string]*cloudmonitoring.MetricDescriptor{
+var customMetricDescriptors = map[string]*cloudmonitoring.MetricDescriptor{
 	// Custom metrics for recording check latency and its aggregation
 	// of vanadium production services.
 	"service-latency":     createMetric("service/latency", "The check latency (ms) of vanadium production services.", "double", true, nil),
@@ -66,13 +67,13 @@
 	// for a service.
 	"service-permethod-latency": createMetric("service/latency/method", "Service latency (ms) per method.", "double", true, []labelData{
 		labelData{
-			key:         "method-name",
+			key:         "method_name",
 			description: "The method name",
 		},
 	}),
 	"service-permethod-latency-agg": createMetric("service/latency/method-agg", "Aggregated service latency (ms) per method.", "double", false, []labelData{
 		labelData{
-			key:         "method-name",
+			key:         "method_name",
 			description: "The method name",
 		},
 		aggLabelData[0],
@@ -87,13 +88,13 @@
 	// of vanadium production services.
 	"service-metadata": createMetric("service/metadata", "Various metadata of vanadium production services.", "double", true, []labelData{
 		labelData{
-			key:         "metadata-name",
+			key:         "metadata_name",
 			description: "The metadata name",
 		},
 	}),
 	"service-metadata-agg": createMetric("service/metadata-agg", "Aggregated metadata of vanadium production services.", "double", false, []labelData{
 		labelData{
-			key:         "metadata-name",
+			key:         "metadata_name",
 			description: "The metadata name",
 		},
 		aggLabelData[0],
@@ -106,13 +107,13 @@
 	// Custom metric for recording per-method rpc qps for a service.
 	"service-qps-method": createMetric("service/qps/method", "Service QPS per method.", "double", true, []labelData{
 		labelData{
-			key:         "method-name",
+			key:         "method_name",
 			description: "The method name",
 		},
 	}),
 	"service-qps-method-agg": createMetric("service/qps/method-agg", "Aggregated service QPS per method.", "double", false, []labelData{
 		labelData{
-			key:         "method-name",
+			key:         "method_name",
 			description: "The method name",
 		},
 		aggLabelData[0],
@@ -132,40 +133,62 @@
 }
 
 func createMetric(metricType, description, valueType string, includeGCELabels bool, extraLabels []labelData) *cloudmonitoring.MetricDescriptor {
-	labels := []*cloudmonitoring.MetricDescriptorLabelDescriptor{}
+	labels := []*cloudmonitoring.LabelDescriptor{}
 	if includeGCELabels {
-		labels = append(labels, &cloudmonitoring.MetricDescriptorLabelDescriptor{
-			Key:         fmt.Sprintf("%s/gce-instance", customMetricPrefix),
+		labels = append(labels, &cloudmonitoring.LabelDescriptor{
+			Key:         "gce_instance",
 			Description: "The name of the GCE instance associated with this metric.",
-		}, &cloudmonitoring.MetricDescriptorLabelDescriptor{
-			Key:         fmt.Sprintf("%s/gce-zone", customMetricPrefix),
+			ValueType:   "string",
+		}, &cloudmonitoring.LabelDescriptor{
+			Key:         "gce_zone",
 			Description: "The zone of the GCE instance associated with this metric.",
+			ValueType:   "string",
 		})
 	}
-	labels = append(labels, &cloudmonitoring.MetricDescriptorLabelDescriptor{
-		Key:         fmt.Sprintf("%s/metric-name", customMetricPrefix),
+	labels = append(labels, &cloudmonitoring.LabelDescriptor{
+		Key:         "metric_name",
 		Description: "The name of the metric.",
+		ValueType:   "string",
 	})
 	if extraLabels != nil {
 		for _, data := range extraLabels {
-			labels = append(labels, &cloudmonitoring.MetricDescriptorLabelDescriptor{
-				Key:         fmt.Sprintf("%s/%s", customMetricPrefix, data.key),
+			labels = append(labels, &cloudmonitoring.LabelDescriptor{
+				Key:         fmt.Sprintf("%s", data.key),
 				Description: data.description,
+				ValueType:   "string",
 			})
 		}
 	}
 
 	return &cloudmonitoring.MetricDescriptor{
-		Name:        fmt.Sprintf("%s/vanadium/%s", customMetricPrefix, metricType),
+		Type:        fmt.Sprintf("%s/vanadium/%s", customMetricPrefix, metricType),
 		Description: description,
-		TypeDescriptor: &cloudmonitoring.MetricDescriptorTypeDescriptor{
-			MetricType: "gauge",
-			ValueType:  valueType,
-		},
-		Labels: labels,
+		MetricKind:  "gauge",
+		ValueType:   valueType,
+		Labels:      labels,
 	}
 }
 
+// GetMetric gets the custom metric descriptor with the given name and project.
+func GetMetric(name, project string) (*cloudmonitoring.MetricDescriptor, error) {
+	md, ok := customMetricDescriptors[name]
+	if !ok {
+		return nil, fmt.Errorf("metric %q doesn't exist", name)
+	}
+	md.Name = fmt.Sprintf("projects/%s/metricDescriptors/%s", project, md.Type)
+	return md, nil
+}
+
+// GetSortedMetricNames gets the sorted metric names.
+func GetSortedMetricNames() []string {
+	names := []string{}
+	for n := range customMetricDescriptors {
+		names = append(names, n)
+	}
+	sort.Strings(names)
+	return names
+}
+
 func createClient(keyFilePath string) (*http.Client, error) {
 	if len(keyFilePath) > 0 {
 		data, err := ioutil.ReadFile(keyFilePath)
diff --git a/internal/monitoring/monitoring_test.go b/internal/monitoring/monitoring_test.go
index 9cef6b6..566cb96 100644
--- a/internal/monitoring/monitoring_test.go
+++ b/internal/monitoring/monitoring_test.go
@@ -9,7 +9,7 @@
 	"reflect"
 	"testing"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 )
 
 func TestCreateMetric(t *testing.T) {
@@ -29,16 +29,15 @@
 			includeGCELabels: false,
 			extraLabels:      nil,
 			expectedMetric: &cloudmonitoring.MetricDescriptor{
-				Name:        fmt.Sprintf("%s/vanadium/test", customMetricPrefix),
+				Type:        fmt.Sprintf("%s/vanadium/test", customMetricPrefix),
 				Description: "this is a test",
-				TypeDescriptor: &cloudmonitoring.MetricDescriptorTypeDescriptor{
-					MetricType: "gauge",
-					ValueType:  "double",
-				},
-				Labels: []*cloudmonitoring.MetricDescriptorLabelDescriptor{
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/metric-name", customMetricPrefix),
+				MetricKind:  "gauge",
+				ValueType:   "double",
+				Labels: []*cloudmonitoring.LabelDescriptor{
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "metric_name",
 						Description: "The name of the metric.",
+						ValueType:   "string",
 					},
 				},
 			},
@@ -50,24 +49,25 @@
 			includeGCELabels: true,
 			extraLabels:      nil,
 			expectedMetric: &cloudmonitoring.MetricDescriptor{
-				Name:        fmt.Sprintf("%s/vanadium/test2", customMetricPrefix),
+				Type:        fmt.Sprintf("%s/vanadium/test2", customMetricPrefix),
 				Description: "this is a test2",
-				TypeDescriptor: &cloudmonitoring.MetricDescriptorTypeDescriptor{
-					MetricType: "gauge",
-					ValueType:  "string",
-				},
-				Labels: []*cloudmonitoring.MetricDescriptorLabelDescriptor{
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/gce-instance", customMetricPrefix),
+				MetricKind:  "gauge",
+				ValueType:   "string",
+				Labels: []*cloudmonitoring.LabelDescriptor{
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "gce_instance",
 						Description: "The name of the GCE instance associated with this metric.",
+						ValueType:   "string",
 					},
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/gce-zone", customMetricPrefix),
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "gce_zone",
 						Description: "The zone of the GCE instance associated with this metric.",
+						ValueType:   "string",
 					},
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/metric-name", customMetricPrefix),
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "metric_name",
 						Description: "The name of the metric.",
+						ValueType:   "string",
 					},
 				},
 			},
@@ -84,28 +84,30 @@
 				},
 			},
 			expectedMetric: &cloudmonitoring.MetricDescriptor{
-				Name:        fmt.Sprintf("%s/vanadium/test3", customMetricPrefix),
+				Type:        fmt.Sprintf("%s/vanadium/test3", customMetricPrefix),
 				Description: "this is a test3",
-				TypeDescriptor: &cloudmonitoring.MetricDescriptorTypeDescriptor{
-					MetricType: "gauge",
-					ValueType:  "double",
-				},
-				Labels: []*cloudmonitoring.MetricDescriptorLabelDescriptor{
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/gce-instance", customMetricPrefix),
+				MetricKind:  "gauge",
+				ValueType:   "double",
+				Labels: []*cloudmonitoring.LabelDescriptor{
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "gce_instance",
 						Description: "The name of the GCE instance associated with this metric.",
+						ValueType:   "string",
 					},
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/gce-zone", customMetricPrefix),
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "gce_zone",
 						Description: "The zone of the GCE instance associated with this metric.",
+						ValueType:   "string",
 					},
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/metric-name", customMetricPrefix),
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "metric_name",
 						Description: "The name of the metric.",
+						ValueType:   "string",
 					},
-					&cloudmonitoring.MetricDescriptorLabelDescriptor{
-						Key:         fmt.Sprintf("%s/extraLabel", customMetricPrefix),
+					&cloudmonitoring.LabelDescriptor{
+						Key:         "extraLabel",
 						Description: "this is an extra label",
+						ValueType:   "string",
 					},
 				},
 			},
diff --git a/jiri-test/internal/test/go.go b/jiri-test/internal/test/go.go
index 4b739b4..f9e4ebd 100644
--- a/jiri-test/internal/test/go.go
+++ b/jiri-test/internal/test/go.go
@@ -1015,6 +1015,8 @@
 		newExclusion("github.com/howeyc/fsnotify", ".*", isDarwin()),
 		// This test relies on timing, which results in flakiness on GCE.
 		newExclusion("google.golang.org/appengine/internal", "TestDelayedLogFlushing", isCI()),
+		// This test relies on timing, which results in flakiness on GCE.
+		newExclusion("google.golang.org/cloud/bigtable", "TestClientIntegration", isCI()),
 		// The crypto/ssh TestValidTerminalMode is flakey on Jenkins and
 		// sometimes fails when getting a pty.
 		newExclusion("golang.org/x/crypto/ssh/test", "TestValidTerminalMode", isCI()),
diff --git a/oncall/collect.go b/oncall/collect.go
index b780f54..6af7299 100644
--- a/oncall/collect.go
+++ b/oncall/collect.go
@@ -17,7 +17,7 @@
 	"strings"
 	"time"
 
-	cloudmonitoring "google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/x/devtools/internal/monitoring"
@@ -33,7 +33,7 @@
 	metricNameLabelKey         = "custom.cloudmonitoring.googleapis.com/metric-name"
 	gceInstanceLabelKey        = "custom.cloudmonitoring.googleapis.com/gce-instance"
 	gceZoneLabelKey            = "custom.cloudmonitoring.googleapis.com/gce-zone"
-	historyDuration            = "1h"
+	historyDuration            = time.Hour
 	serviceStatusOK            = "serviceStatusOK"
 	serviceStatusWarning       = "serviceStatusWarning"
 	serviceStatusDown          = "serviceStatusDown"
@@ -244,19 +244,22 @@
 
 func collectServiceStatusData(ctx *tool.Context, s *cloudmonitoring.Service, now time.Time, buildInfo map[string]*buildInfoData) (*serviceStatusData, error) {
 	// Collect data for the last 8 days and aggregate data every 10 minutes.
-	resp, err := s.Timeseries.List(projectFlag, cloudServiceLatencyMetric, now.Format(time.RFC3339), &cloudmonitoring.ListTimeseriesRequest{
-		Kind: "cloudmonitoring#listTimeseriesRequest",
-	}).Window("10m").Timespan("8d").Aggregator("max").Do()
+	resp, err := s.Projects.TimeSeries.List(fmt.Sprintf("projects/%s", projectFlag)).
+		Filter(fmt.Sprintf("metric.type=%s", cloudServiceLatencyMetric)).
+		AggregationAlignmentPeriod(fmt.Sprintf("%ds", 10*60)).
+		AggregationPerSeriesAligner("ALIGN_MAX").
+		IntervalStartTime(now.AddDate(0, 0, -8).Format(time.RFC3339)).
+		IntervalEndTime(now.Format(time.RFC3339)).Do()
 	if err != nil {
 		return nil, fmt.Errorf("List failed: %v", err)
 	}
 
 	status := []statusData{}
-	for _, t := range resp.Timeseries {
-		serviceName := t.TimeseriesDesc.Labels[metricNameLabelKey]
+	for _, t := range resp.TimeSeries {
+		serviceName := t.Metric.Labels[metricNameLabelKey]
 		curStatusData := statusData{
 			Name:          serviceName,
-			CurrentStatus: statusForLatency(t.Points[0].DoubleValue), // t.Points[0] is the latest
+			CurrentStatus: statusForLatency(t.Points[0].Value.DoubleValue), // t.Points[0] is the latest
 		}
 		incidents, err := calcIncidents(t.Points)
 		if err != nil {
@@ -301,12 +304,12 @@
 	// through them backwards.
 	for i := len(points) - 1; i >= 0; i-- {
 		point := points[i]
-		value := point.DoubleValue
+		value := point.Value.DoubleValue
 		curStatus := statusForLatency(value)
 		if curStatus != lastStatus {
-			pointTime, err := time.Parse(time.RFC3339, point.Start)
+			pointTime, err := time.Parse(time.RFC3339, point.Interval.StartTime)
 			if err != nil {
-				return nil, fmt.Errorf("time.Parse(%s) failed: %v", point.Start, err)
+				return nil, fmt.Errorf("time.Parse(%s) failed: %v", point.Interval.StartTime, err)
 			}
 
 			// Set the duration of the last incident.
@@ -328,7 +331,7 @@
 	}
 	// Process the possible last incident.
 	if lastStatus != serviceStatusOK {
-		strLastPointTime := points[0].Start
+		strLastPointTime := points[0].Interval.StartTime
 		pointTime, err := time.Parse(time.RFC3339, strLastPointTime)
 		if err != nil {
 			return nil, fmt.Errorf("time.Parse(%q) failed: %v", strLastPointTime, err)
@@ -689,19 +692,20 @@
 // (metricData) organized in metricDataMap.
 func getMetricData(ctx *tool.Context, s *cloudmonitoring.Service, metric string, now time.Time, metricNameSuffix string) (metricDataMap, error) {
 	// Query the given metric.
-	resp, err := s.Timeseries.List(projectFlag, metric, now.Format(time.RFC3339), &cloudmonitoring.ListTimeseriesRequest{
-		Kind: "cloudmonitoring#listTimeseriesRequest",
-	}).Timespan(historyDuration).Do()
+	resp, err := s.Projects.TimeSeries.List(fmt.Sprintf("projects/%s", projectFlag)).
+		Filter(fmt.Sprintf("metric.type=%s", metric)).
+		IntervalStartTime(now.Add(-historyDuration).Format(time.RFC3339)).
+		IntervalEndTime(now.Format(time.RFC3339)).Do()
 	if err != nil {
 		return nil, fmt.Errorf("List() failed: %v", err)
 	}
 
 	// Populate metric items and put them into a metricDataMap.
 	data := metricDataMap{}
-	for _, t := range resp.Timeseries {
-		zone := t.TimeseriesDesc.Labels[gceZoneLabelKey]
-		instance := t.TimeseriesDesc.Labels[gceInstanceLabelKey]
-		metricName := t.TimeseriesDesc.Labels[metricNameLabelKey]
+	for _, t := range resp.TimeSeries {
+		zone := t.Metric.Labels[gceZoneLabelKey]
+		instance := t.Metric.Labels[gceInstanceLabelKey]
+		metricName := t.Metric.Labels[metricNameLabelKey]
 		if metricNameSuffix != "" {
 			metricName = fmt.Sprintf("%s %s", metricName, metricNameSuffix)
 		}
@@ -724,7 +728,7 @@
 				ZoneName:     zone,
 				InstanceName: instance,
 				Name:         metricName,
-				CurrentValue: t.Points[0].DoubleValue,
+				CurrentValue: t.Points[0].Value.DoubleValue,
 				MinTime:      math.MaxInt64,
 				MaxTime:      0,
 				MinValue:     math.MaxFloat64,
@@ -742,18 +746,18 @@
 		// latest and going back in time.
 		for i := numPoints - 1; i >= 0; i-- {
 			point := t.Points[i]
-			epochTime, err := time.Parse(time.RFC3339, point.Start)
+			epochTime, err := time.Parse(time.RFC3339, point.Interval.StartTime)
 			if err != nil {
 				fmt.Fprintf(ctx.Stderr(), "%v\n", err)
 				continue
 			}
 			timestamp := epochTime.Unix()
 			timestamps = append(timestamps, timestamp)
-			values = append(values, point.DoubleValue)
+			values = append(values, point.Value.DoubleValue)
 			curMetricData.MinTime = int64(math.Min(float64(curMetricData.MinTime), float64(timestamp)))
 			curMetricData.MaxTime = int64(math.Max(float64(curMetricData.MaxTime), float64(timestamp)))
-			curMetricData.MinValue = math.Min(curMetricData.MinValue, point.DoubleValue)
-			curMetricData.MaxValue = math.Max(curMetricData.MaxValue, point.DoubleValue)
+			curMetricData.MinValue = math.Min(curMetricData.MinValue, point.Value.DoubleValue)
+			curMetricData.MaxValue = math.Max(curMetricData.MaxValue, point.Value.DoubleValue)
 		}
 		curMetricData.HistoryTimestamps = timestamps
 		curMetricData.HistoryValues = values
diff --git a/oncall/collect_test.go b/oncall/collect_test.go
index 0ee3185..0de0e17 100644
--- a/oncall/collect_test.go
+++ b/oncall/collect_test.go
@@ -9,7 +9,7 @@
 	"testing"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 )
 
 func TestCalcIncidents(t *testing.T) {
@@ -21,16 +21,28 @@
 		{
 			points: []*cloudmonitoring.Point{
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896102, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896102, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896101, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896101, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896100, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896100, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 			},
 			expectedIncidentData: []incidentData{},
@@ -39,20 +51,36 @@
 		{
 			points: []*cloudmonitoring.Point{
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896103, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896103, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896102, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896102, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896101, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896101, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896100, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896100, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 			},
 			expectedIncidentData: []incidentData{
@@ -67,24 +95,44 @@
 		{
 			points: []*cloudmonitoring.Point{
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896104, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896104, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896103, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896103, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896102, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896102, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 5000,
-					Start:       time.Unix(1429896101, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896101, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 5000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896100, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896100, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 			},
 			expectedIncidentData: []incidentData{
@@ -104,20 +152,36 @@
 		{
 			points: []*cloudmonitoring.Point{
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896103, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896103, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 3000,
-					Start:       time.Unix(1429896102, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896102, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 3000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 1000,
-					Start:       time.Unix(1429896101, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896101, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 1000,
+					},
 				},
 				&cloudmonitoring.Point{
-					DoubleValue: 5000,
-					Start:       time.Unix(1429896100, 0).Format(time.RFC3339),
+					Interval: &cloudmonitoring.TimeInterval{
+						StartTime: time.Unix(1429896100, 0).Format(time.RFC3339),
+					},
+					Value: &cloudmonitoring.TypedValue{
+						DoubleValue: 5000,
+					},
 				},
 			},
 			expectedIncidentData: []incidentData{
diff --git a/vmon/check.go b/vmon/check.go
index df0e2fd..a628334 100644
--- a/vmon/check.go
+++ b/vmon/check.go
@@ -9,7 +9,7 @@
 	"sort"
 	"strings"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
diff --git a/vmon/cmd.go b/vmon/cmd.go
index e237aa3..9b6978b 100644
--- a/vmon/cmd.go
+++ b/vmon/cmd.go
@@ -23,7 +23,7 @@
 	queryFilterFlag   string
 	projectFlag       string
 
-	defaultQueryFilter = "custom.cloudmonitoring.googleapis.com"
+	defaultQueryFilter = `metric.type=starts_with("custom.googleapis.com")`
 )
 
 func init() {
diff --git a/vmon/doc.go b/vmon/doc.go
index 14909dd..fee81e9 100644
--- a/vmon/doc.go
+++ b/vmon/doc.go
@@ -174,7 +174,7 @@
    vmon md query [flags]
 
 The vmon md query flags are:
- -filter=custom.cloudmonitoring.googleapis.com
+ -filter=metric.type=starts_with("custom.googleapis.com")
    The filter used for query. Default to only query custom metrics.
 
  -color=true
diff --git a/vmon/gceinstance.go b/vmon/gceinstance.go
index e5646e6..7c1e0e5 100644
--- a/vmon/gceinstance.go
+++ b/vmon/gceinstance.go
@@ -18,7 +18,7 @@
 	"strings"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/collect"
 	"v.io/jiri/tool"
@@ -386,7 +386,7 @@
 	if err != nil {
 		return err
 	}
-	timeStr := time.Now().Format(time.RFC3339)
+	timeStr := time.Now().UTC().Format(time.RFC3339)
 	for _, instance := range instances {
 		msg := fmt.Sprintf("Send gce instance data for %s (%s)\n", instance.name, instance.zone)
 		for _, metricName := range gceMetricNames {
@@ -453,28 +453,11 @@
 
 // sendInstanceDataToGCM sends a single instance's stat to GCM.
 func sendInstanceDataToGCM(s *cloudmonitoring.Service, metricType, metricName, timeStr string, instance *gceInstanceData, value float64) error {
-	pt := cloudmonitoring.Point{
-		DoubleValue: value,
-		Start:       timeStr,
-		End:         timeStr,
-	}
-	md := monitoring.CustomMetricDescriptors[metricType]
-	_, err := s.Timeseries.Write(projectFlag, &cloudmonitoring.WriteTimeseriesRequest{
-		Timeseries: []*cloudmonitoring.TimeseriesPoint{
-			&cloudmonitoring.TimeseriesPoint{
-				Point: &pt,
-				TimeseriesDesc: &cloudmonitoring.TimeseriesDescriptor{
-					Metric: md.Name,
-					Labels: map[string]string{
-						md.Labels[0].Key: instance.name,
-						md.Labels[1].Key: instance.zone,
-						md.Labels[2].Key: metricName,
-					},
-				},
-			},
-		},
-	}).Do()
+	md, err := monitoring.GetMetric(metricType, projectFlag)
 	if err != nil {
+		return err
+	}
+	if err := sendDataToGCM(s, md, value, timeStr, "", "", metricName); err != nil {
 		return fmt.Errorf("failed to write timeseries: %v", err)
 	}
 	return nil
diff --git a/vmon/jenkins.go b/vmon/jenkins.go
index 7a5fa80..e891b7a 100644
--- a/vmon/jenkins.go
+++ b/vmon/jenkins.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 	"v.io/jiri/tool"
 	"v.io/v23/context"
 	"v.io/x/devtools/internal/monitoring"
@@ -30,12 +30,15 @@
 		return err
 	}
 	now := time.Now()
-	strNow := now.Format(time.RFC3339)
+	strNow := now.UTC().Format(time.RFC3339)
 	ageInHours := now.Sub(time.Unix(info.Timestamp/1000, 0)).Hours()
 	msg := fmt.Sprintf("vanadium-go-build age: %f hours.\n", ageInHours)
 
 	// Send data to GCM.
-	md := monitoring.CustomMetricDescriptors["jenkins"]
+	md, err := monitoring.GetMetric("jenkins", projectFlag)
+	if err != nil {
+		return err
+	}
 	if err := sendDataToGCM(s, md, float64(ageInHours), strNow, "", "", "vanadium-go-build age"); err != nil {
 		test.Fail(ctx, msg)
 		return err
diff --git a/vmon/metricdescriptor.go b/vmon/metricdescriptor.go
index ae86361..5a86cad 100644
--- a/vmon/metricdescriptor.go
+++ b/vmon/metricdescriptor.go
@@ -6,10 +6,9 @@
 
 import (
 	"fmt"
-	"sort"
 	"strings"
 
-	cloudmonitoring "google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/v23/context"
 	"v.io/x/devtools/internal/monitoring"
@@ -41,7 +40,7 @@
 	Short:    "Create the given metric descriptor in GCM",
 	Long:     "Create the given metric descriptor in GCM.",
 	ArgsName: "<names>",
-	ArgsLong: "<names> is a list of metric descriptor names to create. Available: " + strings.Join(knownMetricDescriptorNames(), ", "),
+	ArgsLong: "<names> is a list of metric descriptor names to create. Available: " + strings.Join(monitoring.GetSortedMetricNames(), ", "),
 }
 
 func runMetricDescriptorCreate(_ *context.T, env *cmdline.Env, args []string) error {
@@ -54,7 +53,11 @@
 		return err
 	}
 	for _, arg := range args {
-		_, err := s.MetricDescriptors.Create(projectFlag, monitoring.CustomMetricDescriptors[arg]).Do()
+		md, err := monitoring.GetMetric(arg, projectFlag)
+		if err != nil {
+			return err
+		}
+		_, err = s.Projects.MetricDescriptors.Create(fmt.Sprintf("projects/%s", projectFlag), md).Do()
 		if err != nil {
 			return fmt.Errorf("Create failed: %v", err)
 		}
@@ -70,7 +73,7 @@
 	Short:    "Delete the given metric descriptor from GCM",
 	Long:     "Delete the given metric descriptor from GCM.",
 	ArgsName: "<names>",
-	ArgsLong: "<names> is a list of metric descriptor names to delete. Available: " + strings.Join(knownMetricDescriptorNames(), ", "),
+	ArgsLong: "<names> is a list of metric descriptor names to delete. Available: " + strings.Join(monitoring.GetSortedMetricNames(), ", "),
 }
 
 func runMetricDescriptorDelete(_ *context.T, env *cmdline.Env, args []string) error {
@@ -83,7 +86,11 @@
 		return err
 	}
 	for _, arg := range args {
-		_, err := s.MetricDescriptors.Delete(projectFlag, monitoring.CustomMetricDescriptors[arg].Name).Do()
+		md, err := monitoring.GetMetric(arg, projectFlag)
+		if err != nil {
+			return err
+		}
+		_, err = s.Projects.MetricDescriptors.Delete(md.Name).Do()
 		if err != nil {
 			return fmt.Errorf("Delete failed: %v", err)
 		}
@@ -101,7 +108,7 @@
 }
 
 func runMetricDescriptorList(_ *context.T, env *cmdline.Env, _ []string) error {
-	for _, n := range knownMetricDescriptorNames() {
+	for _, n := range monitoring.GetSortedMetricNames() {
 		fmt.Fprintf(env.Stdout, "%s\n", n)
 	}
 	return nil
@@ -125,13 +132,13 @@
 	nextPageToken := ""
 	descriptors := []*cloudmonitoring.MetricDescriptor{}
 	for {
-		resp, err := s.MetricDescriptors.List(projectFlag, &cloudmonitoring.ListMetricDescriptorsRequest{
-			Kind: "cloudmonitoring#listMetricDescriptorsRequest",
-		}).Query(queryFilterFlag).PageToken(nextPageToken).Do()
+		resp, err := s.Projects.MetricDescriptors.List(fmt.Sprintf("projects/%s", projectFlag)).
+			Filter(queryFilterFlag).
+			PageToken(nextPageToken).Do()
 		if err != nil {
 			return fmt.Errorf("Query failed: %v", err)
 		}
-		descriptors = append(descriptors, resp.Metrics...)
+		descriptors = append(descriptors, resp.MetricDescriptors...)
 		nextPageToken = resp.NextPageToken
 		if nextPageToken == "" {
 			break
@@ -140,10 +147,11 @@
 
 	// Output results.
 	for _, metric := range descriptors {
-		fmt.Fprintf(env.Stdout, "%s\n", metric.Name)
+		fmt.Fprintf(env.Stdout, "%s\n", metric.Type)
+		fmt.Fprintf(env.Stdout, "- Name: %s\n", metric.Name)
 		fmt.Fprintf(env.Stdout, "- Description: %s\n", metric.Description)
-		fmt.Fprintf(env.Stdout, "- Metric Type: %s\n", metric.TypeDescriptor.MetricType)
-		fmt.Fprintf(env.Stdout, "- Value Type: %s\n", metric.TypeDescriptor.ValueType)
+		fmt.Fprintf(env.Stdout, "- Metric Type: %s\n", metric.MetricKind)
+		fmt.Fprintf(env.Stdout, "- Value Type: %s\n", metric.ValueType)
 		if len(metric.Labels) > 0 {
 			fmt.Fprintf(env.Stdout, "- Labels:\n")
 			for _, label := range metric.Labels {
@@ -157,19 +165,10 @@
 	return nil
 }
 
-func knownMetricDescriptorNames() []string {
-	names := []string{}
-	for n := range monitoring.CustomMetricDescriptors {
-		names = append(names, n)
-	}
-	sort.Strings(names)
-	return names
-}
-
 func checkArgs(env *cmdline.Env, args []string) error {
 	for _, arg := range args {
-		if _, ok := monitoring.CustomMetricDescriptors[arg]; !ok {
-			return env.UsageErrorf("metric descriptor %v does not exist", arg)
+		if _, err := monitoring.GetMetric(arg, projectFlag); err != nil {
+			return err
 		}
 	}
 	if len(args) == 0 {
diff --git a/vmon/rpcloadtest.go b/vmon/rpcloadtest.go
index 0728963..786956a 100644
--- a/vmon/rpcloadtest.go
+++ b/vmon/rpcloadtest.go
@@ -11,7 +11,7 @@
 	"path/filepath"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -41,31 +41,17 @@
 		"latency": results.MsecPerRpc,
 		"qps":     results.Qps,
 	}
-	mdRpcLoadTest := monitoring.CustomMetricDescriptors["rpc-load-test"]
+	mdRpcLoadTest, err := monitoring.GetMetric("rpc-load-test", projectFlag)
+	if err != nil {
+		return err
+	}
 	fi, err := seq.Stat(resultFile)
 	if err != nil {
 		return err
 	}
-	timeStr := fi.ModTime().Format(time.RFC3339)
+	timeStr := fi.ModTime().UTC().Format(time.RFC3339)
 	for label, value := range items {
-		_, err = s.Timeseries.Write(projectFlag, &cloudmonitoring.WriteTimeseriesRequest{
-			Timeseries: []*cloudmonitoring.TimeseriesPoint{
-				&cloudmonitoring.TimeseriesPoint{
-					Point: &cloudmonitoring.Point{
-						DoubleValue: value,
-						Start:       timeStr,
-						End:         timeStr,
-					},
-					TimeseriesDesc: &cloudmonitoring.TimeseriesDescriptor{
-						Metric: mdRpcLoadTest.Name,
-						Labels: map[string]string{
-							mdRpcLoadTest.Labels[0].Key: label,
-						},
-					},
-				},
-			},
-		}).Do()
-		if err != nil {
+		if err := sendDataToGCM(s, mdRpcLoadTest, value, timeStr, "", ""); err != nil {
 			test.Fail(ctx, "%s: %f\n", label, value)
 			return fmt.Errorf("Timeseries Write failed: %v", err)
 		}
diff --git a/vmon/servicecommon.go b/vmon/servicecommon.go
index 58f8e57..b7153a9 100644
--- a/vmon/servicecommon.go
+++ b/vmon/servicecommon.go
@@ -10,7 +10,7 @@
 	"math"
 	"strings"
 
-	cloudmonitoring "google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23"
@@ -273,22 +273,27 @@
 	for i := range labels {
 		labelsMap[md.Labels[i].Key] = labels[i]
 	}
-	if _, err := s.Timeseries.Write(projectFlag, &cloudmonitoring.WriteTimeseriesRequest{
-		Timeseries: []*cloudmonitoring.TimeseriesPoint{
-			&cloudmonitoring.TimeseriesPoint{
-				Point: &cloudmonitoring.Point{
-					DoubleValue: value,
-					Start:       now,
-					End:         now,
-				},
-				TimeseriesDesc: &cloudmonitoring.TimeseriesDescriptor{
-					Metric: md.Name,
+	if _, err := s.Projects.TimeSeries.Create(fmt.Sprintf("projects/%s", projectFlag), &cloudmonitoring.CreateTimeSeriesRequest{
+		TimeSeries: []*cloudmonitoring.TimeSeries{
+			&cloudmonitoring.TimeSeries{
+				Metric: &cloudmonitoring.Metric{
+					Type:   md.Type,
 					Labels: labelsMap,
 				},
+				Points: []*cloudmonitoring.Point{
+					&cloudmonitoring.Point{
+						Value: &cloudmonitoring.TypedValue{
+							DoubleValue: value,
+						},
+						Interval: &cloudmonitoring.TimeInterval{
+							StartTime: now,
+							EndTime:   now,
+						},
+					},
+				},
 			},
-		},
-	}).Do(); err != nil {
-		return fmt.Errorf("Timeseries Write failed for metric %q with value %q: %v", md.Name, value, err)
+		}}).Do(); err != nil {
+		return fmt.Errorf("Timeseries Write failed for metric %q with value %f: %v", md.Name, value, err)
 	}
 	return nil
 }
diff --git a/vmon/servicecounters.go b/vmon/servicecounters.go
index bd41ffa..8320a85 100644
--- a/vmon/servicecounters.go
+++ b/vmon/servicecounters.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -42,8 +42,11 @@
 	}
 
 	hasError := false
-	mdCounter := monitoring.CustomMetricDescriptors["service-counters"]
-	now := time.Now().Format(time.RFC3339)
+	mdCounter, err := monitoring.GetMetric("service-counters", projectFlag)
+	if err != nil {
+		return err
+	}
+	now := time.Now().UTC().Format(time.RFC3339)
 	for serviceName, serviceCounters := range counters {
 		for _, counter := range serviceCounters {
 			vs, err := checkSingleCounter(v23ctx, ctx, serviceName, counter)
@@ -69,7 +72,11 @@
 			}
 
 			// Send aggregated data to GCM.
-			if err := sendAggregatedDataToGCM(ctx, s, monitoring.CustomMetricDescriptors["service-counters-agg"], agg, now, counter.name); err != nil {
+			mdAgg, err := monitoring.GetMetric("service-counters-agg", projectFlag)
+			if err != nil {
+				return err
+			}
+			if err := sendAggregatedDataToGCM(ctx, s, mdAgg, agg, now, counter.name); err != nil {
 				return err
 			}
 		}
diff --git a/vmon/servicelatency.go b/vmon/servicelatency.go
index bfb560e..1eb3fef 100644
--- a/vmon/servicelatency.go
+++ b/vmon/servicelatency.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -44,8 +44,11 @@
 	}
 
 	hasError := false
-	mdLat := monitoring.CustomMetricDescriptors["service-latency"]
-	now := time.Now().Format(time.RFC3339)
+	mdLat, err := monitoring.GetMetric("service-latency", projectFlag)
+	if err != nil {
+		return err
+	}
+	now := time.Now().UTC().Format(time.RFC3339)
 	for _, serviceName := range serviceNames {
 		lats, err := checkSingleServiceLatency(v23ctx, ctx, serviceName)
 		if err != nil {
@@ -75,7 +78,11 @@
 		}
 
 		// Send aggregated data to GCM.
-		if err := sendAggregatedDataToGCM(ctx, s, monitoring.CustomMetricDescriptors["service-latency-agg"], agg, now, serviceName); err != nil {
+		mdAgg, err := monitoring.GetMetric("service-latency-agg", projectFlag)
+		if err != nil {
+			return err
+		}
+		if err := sendAggregatedDataToGCM(ctx, s, mdAgg, agg, now, serviceName); err != nil {
 			return err
 		}
 	}
diff --git a/vmon/servicemetadata.go b/vmon/servicemetadata.go
index a05fc53..15a050b 100644
--- a/vmon/servicemetadata.go
+++ b/vmon/servicemetadata.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -35,9 +35,12 @@
 	}
 
 	hasError := false
-	mdMetadata := monitoring.CustomMetricDescriptors["service-metadata"]
+	mdMetadata, err := monitoring.GetMetric("service-metadata", projectFlag)
+	if err != nil {
+		return err
+	}
 	now := time.Now()
-	strNow := now.Format(time.RFC3339)
+	strNow := now.UTC().Format(time.RFC3339)
 	for _, serviceName := range serviceNames {
 		ms, err := checkSingleServiceMetadata(v23ctx, ctx, serviceName)
 		if err != nil {
@@ -70,7 +73,10 @@
 		}
 
 		// Send aggregated data to GCM.
-		mdMetadataAgg := monitoring.CustomMetricDescriptors["service-metadata-agg"]
+		mdMetadataAgg, err := monitoring.GetMetric("service-metadata-agg", projectFlag)
+		if err != nil {
+			return err
+		}
 		if err := sendAggregatedDataToGCM(ctx, s, mdMetadataAgg, aggBuildTime, strNow, serviceName, "build time"); err != nil {
 			return err
 		}
diff --git a/vmon/servicemethodlatency.go b/vmon/servicemethodlatency.go
index eb6b3ca..454bb31 100644
--- a/vmon/servicemethodlatency.go
+++ b/vmon/servicemethodlatency.go
@@ -11,7 +11,7 @@
 	"sort"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -41,8 +41,11 @@
 	}
 
 	hasError := false
-	mdLatPerMethod := monitoring.CustomMetricDescriptors["service-permethod-latency"]
-	now := time.Now().Format(time.RFC3339)
+	mdLatPerMethod, err := monitoring.GetMetric("service-permethod-latency", projectFlag)
+	if err != nil {
+		return err
+	}
+	now := time.Now().UTC().Format(time.RFC3339)
 	for _, serviceName := range serviceNames {
 		lats, err := checkSingleServicePerMethodLatency(v23ctx, ctx, serviceName)
 		if err != nil {
@@ -80,7 +83,11 @@
 
 		// Send aggregated data to GCM.
 		for method, agg := range aggByMethod {
-			if err := sendAggregatedDataToGCM(ctx, s, monitoring.CustomMetricDescriptors["service-permethod-latency-agg"], agg, now, serviceName, method); err != nil {
+			mdAgg, err := monitoring.GetMetric("service-permethod-latency-agg", projectFlag)
+			if err != nil {
+				return err
+			}
+			if err := sendAggregatedDataToGCM(ctx, s, mdAgg, agg, now, serviceName, method); err != nil {
 				return err
 			}
 		}
diff --git a/vmon/serviceqps.go b/vmon/serviceqps.go
index 8b6b4cd..3dc4c7a 100644
--- a/vmon/serviceqps.go
+++ b/vmon/serviceqps.go
@@ -10,7 +10,7 @@
 	"sort"
 	"time"
 
-	"google.golang.org/api/cloudmonitoring/v2beta2"
+	cloudmonitoring "google.golang.org/api/monitoring/v3"
 
 	"v.io/jiri/tool"
 	"v.io/v23/context"
@@ -44,9 +44,15 @@
 	}
 
 	hasError := false
-	mdPerMethodQPS := monitoring.CustomMetricDescriptors["service-qps-method"]
-	mdTotalQPS := monitoring.CustomMetricDescriptors["service-qps-total"]
-	now := time.Now().Format(time.RFC3339)
+	mdPerMethodQPS, err := monitoring.GetMetric("service-qps-method", projectFlag)
+	if err != nil {
+		return err
+	}
+	mdTotalQPS, err := monitoring.GetMetric("service-qps-total", projectFlag)
+	if err != nil {
+		return err
+	}
+	now := time.Now().UTC().Format(time.RFC3339)
 	for _, serviceName := range serviceNames {
 		qps, err := checkSingleServiceQPS(v23ctx, ctx, serviceName)
 		if err != nil {
@@ -94,11 +100,19 @@
 		}
 
 		// Send aggregated data to GCM.
-		if err := sendAggregatedDataToGCM(ctx, s, monitoring.CustomMetricDescriptors["service-qps-total-agg"], agg, now, serviceName); err != nil {
+		mdTotalAgg, err := monitoring.GetMetric("service-qps-total-agg", projectFlag)
+		if err != nil {
+			return err
+		}
+		if err := sendAggregatedDataToGCM(ctx, s, mdTotalAgg, agg, now, serviceName); err != nil {
 			return err
 		}
 		for method, agg := range aggByMethod {
-			if err := sendAggregatedDataToGCM(ctx, s, monitoring.CustomMetricDescriptors["service-qps-method-agg"], agg, now, serviceName, method); err != nil {
+			mdMethodAgg, err := monitoring.GetMetric("service-qps-method-agg", projectFlag)
+			if err != nil {
+				return err
+			}
+			if err := sendAggregatedDataToGCM(ctx, s, mdMethodAgg, agg, now, serviceName, method); err != nil {
 				return err
 			}
 		}