Repository: census-ecosystem/opencensus-go-exporter-stackdriver
Pull request #252 (2020-02-11 02:09:52)

Default mapping for knative_revision & autodetect labels
Changes:
- Add default mapping for the [knative_revision](https://cloud.google.com/monitoring/api/resources#tag_knative_revision) Monitored Resource type
- During resource mapping, attempt to autodetect as many monitored resource labels as possible from monitoredresource.Autodetect

Using just some of the autodetected labels allows users of the Stackdriver exporter to avoid duplicating the Google-specific logic in monitoredresource.Autodetect. 

This is useful for codebases like Knative which can run on any k8s platform and would like to avoid embedding cloud-provider-specific code in the codebase (https://github.com/knative/pkg/blob/master/metrics/README.md). 


anniefu: cc @evankanderson
codecov-io: # [Codecov](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=h1) Report
> Merging [#252](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=desc) into [master](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/commit/ddbc64abe5fffb1dd27cf39339f0ac45e7f2ea46?src=pr&el=desc) will **increase** coverage by `0.3%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252/graphs/tree.svg?width=650&token=os6w8WyKNy&height=150&src=pr)](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master     #252     +/-   ##
=========================================
+ Coverage   72.11%   72.41%   +0.3%     
=========================================
  Files          14       14             
  Lines        1646     1664     +18     
=========================================
+ Hits         1187     1205     +18     
  Misses        383      383             
  Partials       76       76
```


| [Impacted Files](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [resource.go](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252/diff?src=pr&el=tree#diff-cmVzb3VyY2UuZ28=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=footer). Last update [ddbc64a...1f26e94](https://codecov.io/gh/census-ecosystem/opencensus-go-exporter-stackdriver/pull/252?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/monitoredresource/aws_identity_doc_utils.go b/monitoredresource/aws/aws_identity_doc_utils.go
similarity index 96%
rename from monitoredresource/aws_identity_doc_utils.go
rename to monitoredresource/aws/aws_identity_doc_utils.go
index ee519a4..f83b402 100644
--- a/monitoredresource/aws_identity_doc_utils.go
+++ b/monitoredresource/aws/aws_identity_doc_utils.go
@@ -1,4 +1,4 @@
-// Copyright 2018, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package monitoredresource
+package aws
 
 import (
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
diff --git a/monitoredresource/aws/monitored_resources.go b/monitoredresource/aws/monitored_resources.go
new file mode 100644
index 0000000..5e4cde5
--- /dev/null
+++ b/monitoredresource/aws/monitored_resources.go
@@ -0,0 +1,97 @@
+// Copyright 2020, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package aws
+
+import (
+	"fmt"
+	"sync"
+)
+
+// Interface is a type that represent monitor resource that satisfies monitoredresource.Interface
+type Interface interface {
+
+	// MonitoredResource returns the resource type and resource labels.
+	MonitoredResource() (resType string, labels map[string]string)
+}
+
+// EC2Instance represents aws_ec2_instance type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance
+type EC2Instance struct {
+
+	// AWSAccount is the AWS account number for the VM.
+	AWSAccount string
+
+	// InstanceID is the instance id of the instance.
+	InstanceID string
+
+	// Region is the AWS region for the VM. The format of this field is "aws:{region}",
+	// where supported values for {region} are listed at
+	// http://docs.aws.amazon.com/general/latest/gr/rande.html.
+	Region string
+}
+
+// MonitoredResource returns resource type and resource labels for AWSEC2Instance
+func (aws *EC2Instance) MonitoredResource() (resType string, labels map[string]string) {
+	labels = map[string]string{
+		"aws_account": aws.AWSAccount,
+		"instance_id": aws.InstanceID,
+		"region":      aws.Region,
+	}
+	return "aws_ec2_instance", labels
+}
+
+// Autodetect auto detects monitored resources based on
+// the environment where the application is running.
+// It supports detection of following resource types
+// 1. aws_ec2_instance:
+//
+// Returns MonitoredResInterface which implements getLabels() and getType()
+// For resource definition go to https://cloud.google.com/monitoring/api/resources
+func Autodetect() Interface {
+	return func() Interface {
+		detectOnce.Do(func() {
+			autoDetected = detectResourceType(retrieveAWSIdentityDocument())
+		})
+		return autoDetected
+	}()
+
+}
+
+// createAWSEC2InstanceMonitoredResource creates a aws_ec2_instance monitored resource
+// awsIdentityDoc contains AWS EC2 specific attributes.
+func createEC2InstanceMonitoredResource(awsIdentityDoc *awsIdentityDocument) *EC2Instance {
+	awsInstance := EC2Instance{
+		AWSAccount: awsIdentityDoc.accountID,
+		InstanceID: awsIdentityDoc.instanceID,
+		Region:     fmt.Sprintf("aws:%s", awsIdentityDoc.region),
+	}
+	return &awsInstance
+}
+
+// detectOnce is used to make sure AWS metadata detect function executes only once.
+var detectOnce sync.Once
+
+// autoDetected is the metadata detected after the first execution of Autodetect function.
+var autoDetected Interface
+
+// detectResourceType determines the resource type.
+// awsIdentityDoc contains AWS EC2 attributes. nil if it is not AWS EC2 environment
+func detectResourceType(awsIdentityDoc *awsIdentityDocument) Interface {
+	if awsIdentityDoc != nil {
+		return createEC2InstanceMonitoredResource(awsIdentityDoc)
+	}
+	return nil
+}
diff --git a/monitoredresource/deprecated.go b/monitoredresource/deprecated.go
new file mode 100644
index 0000000..fec5ec1
--- /dev/null
+++ b/monitoredresource/deprecated.go
@@ -0,0 +1,101 @@
+// Copyright 2020, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package monitoredresource
+
+import (
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws"
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp"
+)
+
+// GKEContainer represents gke_container type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_gke_container
+// Deprecated: please use gcp.GKEContainer from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp".
+type GKEContainer struct {
+	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
+	ProjectID string
+
+	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
+	InstanceID string
+
+	// ClusterName is the name for the cluster the container is running in.
+	ClusterName string
+
+	// ContainerName is the name of the container.
+	ContainerName string
+
+	// NamespaceID is the identifier for the cluster namespace the container is running in
+	NamespaceID string
+
+	// PodID is the identifier for the pod the container is running in.
+	PodID string
+
+	// Zone is the Compute Engine zone in which the VM is running.
+	Zone string
+
+	// LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE
+	LoggingMonitoringV2Enabled bool
+}
+
+// MonitoredResource returns resource type and resource labels for GKEContainer
+func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) {
+	gcpGKE := gcp.GKEContainer(*gke)
+	return gcpGKE.MonitoredResource()
+}
+
+// GCEInstance represents gce_instance type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_gce_instance
+// Deprecated: please use gcp.GCEInstance from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp".
+type GCEInstance struct {
+
+	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
+	ProjectID string
+
+	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
+	InstanceID string
+
+	// Zone is the Compute Engine zone in which the VM is running.
+	Zone string
+}
+
+// MonitoredResource returns resource type and resource labels for GCEInstance
+func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) {
+	gcpGCE := gcp.GCEInstance(*gce)
+	return gcpGCE.MonitoredResource()
+}
+
+// AWSEC2Instance represents aws_ec2_instance type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance
+// Deprecated: please use aws.EC2Container from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws".
+type AWSEC2Instance struct {
+	// AWSAccount is the AWS account number for the VM.
+	AWSAccount string
+
+	// InstanceID is the instance id of the instance.
+	InstanceID string
+
+	// Region is the AWS region for the VM. The format of this field is "aws:{region}",
+	// where supported values for {region} are listed at
+	// http://docs.aws.amazon.com/general/latest/gr/rande.html.
+	Region string
+}
+
+// MonitoredResource returns resource type and resource labels for AWSEC2Instance
+func (ec2 *AWSEC2Instance) MonitoredResource() (resType string, labels map[string]string) {
+	awsEC2 := aws.EC2Instance(*ec2)
+	return awsEC2.MonitoredResource()
+}
diff --git a/monitoredresource/gcp_metadata_config.go b/monitoredresource/gcp/gcp_metadata_config.go
similarity index 98%
rename from monitoredresource/gcp_metadata_config.go
rename to monitoredresource/gcp/gcp_metadata_config.go
index f0d8885..ccaade6 100644
--- a/monitoredresource/gcp_metadata_config.go
+++ b/monitoredresource/gcp/gcp_metadata_config.go
@@ -1,4 +1,4 @@
-// Copyright 2018, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package monitoredresource
+package gcp
 
 import (
 	"context"
diff --git a/monitoredresource/gcp/monitored_resources.go b/monitoredresource/gcp/monitored_resources.go
new file mode 100644
index 0000000..1a08b1c
--- /dev/null
+++ b/monitoredresource/gcp/monitored_resources.go
@@ -0,0 +1,168 @@
+// Copyright 2020, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gcp
+
+import (
+	"os"
+	"sync"
+)
+
+// Interface is a type that represent monitor resource that satisfies monitoredresource.Interface
+type Interface interface {
+
+	// MonitoredResource returns the resource type and resource labels.
+	MonitoredResource() (resType string, labels map[string]string)
+}
+
+// GKEContainer represents gke_container type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_gke_container
+type GKEContainer struct {
+
+	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
+	ProjectID string
+
+	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
+	InstanceID string
+
+	// ClusterName is the name for the cluster the container is running in.
+	ClusterName string
+
+	// ContainerName is the name of the container.
+	ContainerName string
+
+	// NamespaceID is the identifier for the cluster namespace the container is running in
+	NamespaceID string
+
+	// PodID is the identifier for the pod the container is running in.
+	PodID string
+
+	// Zone is the Compute Engine zone in which the VM is running.
+	Zone string
+
+	// LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE
+	LoggingMonitoringV2Enabled bool
+}
+
+// MonitoredResource returns resource type and resource labels for GKEContainer
+func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) {
+	labels = map[string]string{
+		"project_id":     gke.ProjectID,
+		"cluster_name":   gke.ClusterName,
+		"container_name": gke.ContainerName,
+	}
+	var typ string
+	if gke.LoggingMonitoringV2Enabled {
+		typ = "k8s_container"
+		labels["pod_name"] = gke.PodID
+		labels["namespace_name"] = gke.NamespaceID
+		labels["location"] = gke.Zone
+	} else {
+		typ = "gke_container"
+		labels["pod_id"] = gke.PodID
+		labels["namespace_id"] = gke.NamespaceID
+		labels["zone"] = gke.Zone
+		labels["instance_id"] = gke.InstanceID
+	}
+	return typ, labels
+}
+
+// GCEInstance represents gce_instance type monitored resource.
+// For definition refer to
+// https://cloud.google.com/monitoring/api/resources#tag_gce_instance
+type GCEInstance struct {
+
+	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
+	ProjectID string
+
+	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
+	InstanceID string
+
+	// Zone is the Compute Engine zone in which the VM is running.
+	Zone string
+}
+
+// MonitoredResource returns resource type and resource labels for GCEInstance
+func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) {
+	labels = map[string]string{
+		"project_id":  gce.ProjectID,
+		"instance_id": gce.InstanceID,
+		"zone":        gce.Zone,
+	}
+	return "gce_instance", labels
+}
+
+// Autodetect auto detects monitored resources based on
+// the environment where the application is running.
+// It supports detection of following resource types
+// 1. gke_container:
+// 2. gce_instance:
+//
+// Returns MonitoredResInterface which implements getLabels() and getType()
+// For resource definition go to https://cloud.google.com/monitoring/api/resources
+func Autodetect() Interface {
+	return func() Interface {
+		detectOnce.Do(func() {
+			autoDetected = detectResourceType(retrieveGCPMetadata())
+		})
+		return autoDetected
+	}()
+
+}
+
+// createGCEInstanceMonitoredResource creates a gce_instance monitored resource
+// gcpMetadata contains GCP (GKE or GCE) specific attributes.
+func createGCEInstanceMonitoredResource(gcpMetadata *gcpMetadata) *GCEInstance {
+	gceInstance := GCEInstance{
+		ProjectID:  gcpMetadata.projectID,
+		InstanceID: gcpMetadata.instanceID,
+		Zone:       gcpMetadata.zone,
+	}
+	return &gceInstance
+}
+
+// createGKEContainerMonitoredResource creates a gke_container monitored resource
+// gcpMetadata contains GCP (GKE or GCE) specific attributes.
+func createGKEContainerMonitoredResource(gcpMetadata *gcpMetadata) *GKEContainer {
+	gkeContainer := GKEContainer{
+		ProjectID:                  gcpMetadata.projectID,
+		InstanceID:                 gcpMetadata.instanceID,
+		Zone:                       gcpMetadata.zone,
+		ContainerName:              gcpMetadata.containerName,
+		ClusterName:                gcpMetadata.clusterName,
+		NamespaceID:                gcpMetadata.namespaceID,
+		PodID:                      gcpMetadata.podID,
+		LoggingMonitoringV2Enabled: gcpMetadata.monitoringV2,
+	}
+	return &gkeContainer
+}
+
+// detectOnce is used to make sure GCP metadata detect function executes only once.
+var detectOnce sync.Once
+
+// autoDetected is the metadata detected after the first execution of Autodetect function.
+var autoDetected Interface
+
+// detectResourceType determines the resource type.
+// gcpMetadata contains GCP (GKE or GCE) specific attributes.
+func detectResourceType(gcpMetadata *gcpMetadata) Interface {
+	if os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
+		gcpMetadata != nil && gcpMetadata.instanceID != "" {
+		return createGKEContainerMonitoredResource(gcpMetadata)
+	} else if gcpMetadata != nil && gcpMetadata.instanceID != "" {
+		return createGCEInstanceMonitoredResource(gcpMetadata)
+	}
+	return nil
+}
diff --git a/monitoredresource/monitored_resources.go b/monitoredresource/monitored_resources.go
index 86e7600..46943eb 100644
--- a/monitoredresource/monitored_resources.go
+++ b/monitoredresource/monitored_resources.go
@@ -1,4 +1,4 @@
-// Copyright 2018, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,123 +15,18 @@
 package monitoredresource
 
 import (
-	"fmt"
-	"os"
 	"sync"
+
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws"
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp"
 )
 
 // Interface is a type that represent monitor resource that satisfies monitoredresource.Interface
 type Interface interface {
-
 	// MonitoredResource returns the resource type and resource labels.
 	MonitoredResource() (resType string, labels map[string]string)
 }
 
-// GKEContainer represents gke_container type monitored resource.
-// For definition refer to
-// https://cloud.google.com/monitoring/api/resources#tag_gke_container
-type GKEContainer struct {
-
-	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
-	ProjectID string
-
-	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
-	InstanceID string
-
-	// ClusterName is the name for the cluster the container is running in.
-	ClusterName string
-
-	// ContainerName is the name of the container.
-	ContainerName string
-
-	// NamespaceID is the identifier for the cluster namespace the container is running in
-	NamespaceID string
-
-	// PodID is the identifier for the pod the container is running in.
-	PodID string
-
-	// Zone is the Compute Engine zone in which the VM is running.
-	Zone string
-
-	// LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE
-	LoggingMonitoringV2Enabled bool
-}
-
-// MonitoredResource returns resource type and resource labels for GKEContainer
-func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) {
-	labels = map[string]string{
-		"project_id":     gke.ProjectID,
-		"cluster_name":   gke.ClusterName,
-		"container_name": gke.ContainerName,
-	}
-	var typ string
-	if gke.LoggingMonitoringV2Enabled {
-		typ = "k8s_container"
-		labels["pod_name"] = gke.PodID
-		labels["namespace_name"] = gke.NamespaceID
-		labels["location"] = gke.Zone
-	} else {
-		typ = "gke_container"
-		labels["pod_id"] = gke.PodID
-		labels["namespace_id"] = gke.NamespaceID
-		labels["zone"] = gke.Zone
-		labels["instance_id"] = gke.InstanceID
-	}
-	return typ, labels
-}
-
-// GCEInstance represents gce_instance type monitored resource.
-// For definition refer to
-// https://cloud.google.com/monitoring/api/resources#tag_gce_instance
-type GCEInstance struct {
-
-	// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
-	ProjectID string
-
-	// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
-	InstanceID string
-
-	// Zone is the Compute Engine zone in which the VM is running.
-	Zone string
-}
-
-// MonitoredResource returns resource type and resource labels for GCEInstance
-func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) {
-	labels = map[string]string{
-		"project_id":  gce.ProjectID,
-		"instance_id": gce.InstanceID,
-		"zone":        gce.Zone,
-	}
-	return "gce_instance", labels
-}
-
-// AWSEC2Instance represents aws_ec2_instance type monitored resource.
-// For definition refer to
-// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance
-type AWSEC2Instance struct {
-
-	// AWSAccount is the AWS account number for the VM.
-	AWSAccount string
-
-	// InstanceID is the instance id of the instance.
-	InstanceID string
-
-	// Region is the AWS region for the VM. The format of this field is "aws:{region}",
-	// where supported values for {region} are listed at
-	// http://docs.aws.amazon.com/general/latest/gr/rande.html.
-	Region string
-}
-
-// MonitoredResource returns resource type and resource labels for AWSEC2Instance
-func (aws *AWSEC2Instance) MonitoredResource() (resType string, labels map[string]string) {
-	labels = map[string]string{
-		"aws_account": aws.AWSAccount,
-		"instance_id": aws.InstanceID,
-		"region":      aws.Region,
-	}
-	return "aws_ec2_instance", labels
-}
-
 // Autodetect auto detects monitored resources based on
 // the environment where the application is running.
 // It supports detection of following resource types
@@ -144,9 +39,7 @@ func (aws *AWSEC2Instance) MonitoredResource() (resType string, labels map[strin
 func Autodetect() Interface {
 	return func() Interface {
 		detectOnce.Do(func() {
-			var awsIdentityDoc *awsIdentityDocument
-			var gcpMetadata *gcpMetadata
-
+			var awsDetect, gcpDetect Interface
 			// First attempts to retrieve AWS Identity Doc and GCP metadata.
 			// It then determines the resource type
 			// In GCP and AWS environment both func finishes quickly. However,
@@ -157,57 +50,21 @@ func Autodetect() Interface {
 
 			go func() {
 				defer wg.Done()
-				awsIdentityDoc = retrieveAWSIdentityDocument()
+				awsDetect = aws.Autodetect()
 			}()
 			go func() {
 				defer wg.Done()
-				gcpMetadata = retrieveGCPMetadata()
+				gcpDetect = gcp.Autodetect()
 			}()
 
 			wg.Wait()
-			autoDetected = detectResourceType(awsIdentityDoc, gcpMetadata)
+			autoDetected = awsDetect
+			if gcpDetect != nil {
+				autoDetected = gcpDetect
+			}
 		})
 		return autoDetected
 	}()
-
-}
-
-// createAWSEC2InstanceMonitoredResource creates a aws_ec2_instance monitored resource
-// awsIdentityDoc contains AWS EC2 specific attributes.
-func createAWSEC2InstanceMonitoredResource(awsIdentityDoc *awsIdentityDocument) *AWSEC2Instance {
-	awsInstance := AWSEC2Instance{
-		AWSAccount: awsIdentityDoc.accountID,
-		InstanceID: awsIdentityDoc.instanceID,
-		Region:     fmt.Sprintf("aws:%s", awsIdentityDoc.region),
-	}
-	return &awsInstance
-}
-
-// createGCEInstanceMonitoredResource creates a gce_instance monitored resource
-// gcpMetadata contains GCP (GKE or GCE) specific attributes.
-func createGCEInstanceMonitoredResource(gcpMetadata *gcpMetadata) *GCEInstance {
-	gceInstance := GCEInstance{
-		ProjectID:  gcpMetadata.projectID,
-		InstanceID: gcpMetadata.instanceID,
-		Zone:       gcpMetadata.zone,
-	}
-	return &gceInstance
-}
-
-// createGKEContainerMonitoredResource creates a gke_container monitored resource
-// gcpMetadata contains GCP (GKE or GCE) specific attributes.
-func createGKEContainerMonitoredResource(gcpMetadata *gcpMetadata) *GKEContainer {
-	gkeContainer := GKEContainer{
-		ProjectID:                  gcpMetadata.projectID,
-		InstanceID:                 gcpMetadata.instanceID,
-		Zone:                       gcpMetadata.zone,
-		ContainerName:              gcpMetadata.containerName,
-		ClusterName:                gcpMetadata.clusterName,
-		NamespaceID:                gcpMetadata.namespaceID,
-		PodID:                      gcpMetadata.podID,
-		LoggingMonitoringV2Enabled: gcpMetadata.monitoringV2,
-	}
-	return &gkeContainer
 }
 
 // detectOnce is used to make sure GCP and AWS metadata detect function executes only once.
@@ -215,18 +72,3 @@ var detectOnce sync.Once
 
 // autoDetected is the metadata detected after the first execution of Autodetect function.
 var autoDetected Interface
-
-// detectResourceType determines the resource type.
-// awsIdentityDoc contains AWS EC2 attributes. nil if it is not AWS EC2 environment
-// gcpMetadata contains GCP (GKE or GCE) specific attributes.
-func detectResourceType(awsIdentityDoc *awsIdentityDocument, gcpMetadata *gcpMetadata) Interface {
-	if os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
-		gcpMetadata != nil && gcpMetadata.instanceID != "" {
-		return createGKEContainerMonitoredResource(gcpMetadata)
-	} else if gcpMetadata != nil && gcpMetadata.instanceID != "" {
-		return createGCEInstanceMonitoredResource(gcpMetadata)
-	} else if awsIdentityDoc != nil {
-		return createAWSEC2InstanceMonitoredResource(awsIdentityDoc)
-	}
-	return nil
-}
diff --git a/resource.go b/resource.go
index 291cfc8..82173c0 100644
--- a/resource.go
+++ b/resource.go
@@ -1,4 +1,4 @@
-// Copyright 2019, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,7 +16,9 @@ package stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver"
 
 import (
 	"fmt"
+	"sync"
 
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp"
 	"go.opencensus.io/resource"
 	"go.opencensus.io/resource/resourcekeys"
 	monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
@@ -27,12 +29,40 @@ import (
 // for customization.
 const (
 	stackdriverProjectID            = "contrib.opencensus.io/exporter/stackdriver/project_id"
+	stackdriverLocation             = "contrib.opencensus.io/exporter/stackdriver/location"
+	stackdriverClusterName          = "contrib.opencesus.io/exporter/stackdriver/cluster_name"
 	stackdriverGenericTaskNamespace = "contrib.opencensus.io/exporter/stackdriver/generic_task/namespace"
 	stackdriverGenericTaskJob       = "contrib.opencensus.io/exporter/stackdriver/generic_task/job"
 	stackdriverGenericTaskID        = "contrib.opencensus.io/exporter/stackdriver/generic_task/task_id"
+
+	knativeResType           = "knative_revision"
+	knativeServiceName       = "service_name"
+	knativeRevisionName      = "revision_name"
+	knativeConfigurationName = "configuration_name"
+	knativeNamespaceName     = "namespace_name"
 )
 
-// Mappings for the well-known OpenCensus resources to applicable Stackdriver resources.
+var (
+	// autodetectFunc returns a monitored resource that is autodetected.
+	// from the cloud environment at runtime.
+	autodetectFunc func() gcp.Interface
+
+	// autodetectOnce is used to lazy initialize autodetectedLabels.
+	autodetectOnce *sync.Once
+	// autodetectedLabels stores all the labels from the autodetected monitored resource
+	// with a possible additional label for the GCP "location".
+	autodetectedLabels map[string]string
+)
+
+func init() {
+	autodetectFunc = gcp.Autodetect
+	// monitoredresource.Autodetect only makes calls to the metadata APIs once
+	// and caches the results
+	autodetectOnce = new(sync.Once)
+}
+
+// Mappings for the well-known OpenCensus resource label keys
+// to applicable Stackdriver Monitored Resource label keys.
 var k8sContainerMap = map[string]string{
 	"project_id":     stackdriverProjectID,
 	"location":       resourcekeys.CloudKeyZone,
@@ -79,16 +109,58 @@ var genericResourceMap = map[string]string{
 	"task_id":    stackdriverGenericTaskID,
 }
 
+var knativeRevisionResourceMap = map[string]string{
+	"project_id":             stackdriverProjectID,
+	"location":               resourcekeys.CloudKeyZone,
+	"cluster_name":           resourcekeys.K8SKeyClusterName,
+	knativeServiceName:       knativeServiceName,
+	knativeRevisionName:      knativeRevisionName,
+	knativeConfigurationName: knativeConfigurationName,
+	knativeNamespaceName:     knativeNamespaceName,
+}
+
+// getAutodetectedLabels returns all the labels from the Monitored Resource detected
+// from the environment by calling monitoredresource.Autodetect. If a "zone" label is detected,
+// a "location" label is added with the same value to account for differences between
+// Legacy Stackdriver and Stackdriver Kubernetes Engine Monitoring,
+// see https://cloud.google.com/monitoring/kubernetes-engine/migration.
+func getAutodetectedLabels() map[string]string {
+	autodetectOnce.Do(func() {
+		autodetectedLabels = map[string]string{}
+		if mr := autodetectFunc(); mr != nil {
+			_, labels := mr.MonitoredResource()
+			// accept "zone" value for "location" because values for location can be a zone
+			// or region, see https://cloud.google.com/docs/geography-and-regions
+			if _, ok := labels["zone"]; ok {
+				labels["location"] = labels["zone"]
+			}
+
+			autodetectedLabels = labels
+		}
+	})
+
+	return autodetectedLabels
+}
+
 // returns transformed label map and true if all labels in match are found
 // in input except optional project_id. It returns false if at least one label
 // other than project_id is missing.
 func transformResource(match, input map[string]string) (map[string]string, bool) {
 	output := make(map[string]string, len(input))
 	for dst, src := range match {
-		v, ok := input[src]
-		if ok {
+		if v, ok := input[src]; ok {
 			output[dst] = v
-		} else if dst != "project_id" {
+			continue
+		}
+
+		// attempt to autodetect missing labels, autodetected label keys should
+		// match destination label keys
+		if v, ok := getAutodetectedLabels()[dst]; ok {
+			output[dst] = v
+			continue
+		}
+
+		if dst != "project_id" {
 			return nil, true
 		}
 	}
@@ -120,6 +192,9 @@ func defaultMapResource(res *resource.Resource) *monitoredrespb.MonitoredResourc
 	case res.Labels[resourcekeys.CloudKeyProvider] == resourcekeys.CloudProviderAWS:
 		result.Type = "aws_ec2_instance"
 		match = awsResourceMap
+	case res.Type == knativeResType:
+		result.Type = res.Type
+		match = knativeRevisionResourceMap
 	}
 
 	var missing bool


Test patch:
diff --git a/monitoredresource/aws/monitored_resources_test.go b/monitoredresource/aws/monitored_resources_test.go
new file mode 100644
index 0000000..b7c7403
--- /dev/null
+++ b/monitoredresource/aws/monitored_resources_test.go
@@ -0,0 +1,39 @@
+// Copyright 2020, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package aws
+
+import (
+	"testing"
+)
+
+func TestAWSEC2InstanceMonitoredResources(t *testing.T) {
+	awsIdentityDoc := &awsIdentityDocument{
+		"123456789012",
+		"i-1234567890abcdef0",
+		"us-west-2",
+	}
+	autoDetected := detectResourceType(awsIdentityDoc)
+
+	if autoDetected == nil {
+		t.Fatal("AWSEC2InstanceMonitoredResource nil")
+	}
+	resType, labels := autoDetected.MonitoredResource()
+	if resType != "aws_ec2_instance" ||
+		labels["instance_id"] != "i-1234567890abcdef0" ||
+		labels["aws_account"] != "123456789012" ||
+		labels["region"] != "aws:us-west-2" {
+		t.Errorf("AWSEC2InstanceMonitoredResource Failed: %v", autoDetected)
+	}
+}
diff --git a/monitoredresource/deprecated_test.go b/monitoredresource/deprecated_test.go
new file mode 100644
index 0000000..4f46763
--- /dev/null
+++ b/monitoredresource/deprecated_test.go
@@ -0,0 +1,113 @@
+// Copyright 2020, OpenCensus Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package monitoredresource
+
+import (
+	"os"
+	"testing"
+)
+
+const (
+	GCPProjectIDStr     = "gcp-project"
+	GCPInstanceIDStr    = "instance"
+	GCPZoneStr          = "us-east1"
+	GKENamespaceStr     = "namespace"
+	GKEPodIDStr         = "pod-id"
+	GKEContainerNameStr = "container"
+	GKEClusterNameStr   = "cluster"
+)
+
+func TestGKEContainerMonitoredResources(t *testing.T) {
+	os.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1")
+	autoDetected := GKEContainer{
+		InstanceID:    GCPInstanceIDStr,
+		ProjectID:     GCPProjectIDStr,
+		Zone:          GCPZoneStr,
+		ClusterName:   GKEClusterNameStr,
+		ContainerName: GKEContainerNameStr,
+		NamespaceID:   GKENamespaceStr,
+		PodID:         GKEPodIDStr,
+	}
+
+	resType, labels := autoDetected.MonitoredResource()
+	if resType != "gke_container" ||
+		labels["instance_id"] != GCPInstanceIDStr ||
+		labels["project_id"] != GCPProjectIDStr ||
+		labels["cluster_name"] != GKEClusterNameStr ||
+		labels["container_name"] != GKEContainerNameStr ||
+		labels["zone"] != GCPZoneStr ||
+		labels["namespace_id"] != GKENamespaceStr ||
+		labels["pod_id"] != GKEPodIDStr {
+		t.Errorf("GKEContainerMonitoredResource Failed: %v", autoDetected)
+	}
+}
+
+func TestGKEContainerMonitoredResourcesV2(t *testing.T) {
+	os.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1")
+	autoDetected := GKEContainer{
+		InstanceID:                 GCPInstanceIDStr,
+		ProjectID:                  GCPProjectIDStr,
+		Zone:                       GCPZoneStr,
+		ClusterName:                GKEClusterNameStr,
+		ContainerName:              GKEContainerNameStr,
+		NamespaceID:                GKENamespaceStr,
+		PodID:                      GKEPodIDStr,
+		LoggingMonitoringV2Enabled: true,
+	}
+
+	resType, labels := autoDetected.MonitoredResource()
+	if resType != "k8s_container" ||
+		labels["project_id"] != GCPProjectIDStr ||
+		labels["cluster_name"] != GKEClusterNameStr ||
+		labels["container_name"] != GKEContainerNameStr ||
+		labels["location"] != GCPZoneStr ||
+		labels["namespace_name"] != GKENamespaceStr ||
+		labels["pod_name"] != GKEPodIDStr {
+		t.Errorf("GKEContainerMonitoredResourceV2 Failed: %v", autoDetected)
+	}
+}
+
+func TestGCEInstanceMonitoredResources(t *testing.T) {
+	os.Setenv("KUBERNETES_SERVICE_HOST", "")
+	autoDetected := GCEInstance{
+		InstanceID: GCPInstanceIDStr,
+		ProjectID:  GCPProjectIDStr,
+		Zone:       GCPZoneStr,
+	}
+
+	resType, labels := autoDetected.MonitoredResource()
+	if resType != "gce_instance" ||
+		labels["instance_id"] != GCPInstanceIDStr ||
+		labels["project_id"] != GCPProjectIDStr ||
+		labels["zone"] != GCPZoneStr {
+		t.Errorf("GCEInstanceMonitoredResource Failed: %v", autoDetected)
+	}
+}
+
+func TestAWSEC2InstanceMonitoredResources(t *testing.T) {
+	autoDetected := AWSEC2Instance{
+		AWSAccount: "123456789012",
+		InstanceID: "i-1234567890abcdef0",
+		Region:     "aws:us-west-2",
+	}
+
+	resType, labels := autoDetected.MonitoredResource()
+	if resType != "aws_ec2_instance" ||
+		labels["instance_id"] != "i-1234567890abcdef0" ||
+		labels["aws_account"] != "123456789012" ||
+		labels["region"] != "aws:us-west-2" {
+		t.Errorf("AWSEC2InstanceMonitoredResource Failed: %v", autoDetected)
+	}
+}
diff --git a/monitoredresource/monitored_resources_test.go b/monitoredresource/gcp/monitored_resources_test.go
similarity index 78%
rename from monitoredresource/monitored_resources_test.go
rename to monitoredresource/gcp/monitored_resources_test.go
index d46c137..7d27c6c 100644
--- a/monitoredresource/monitored_resources_test.go
+++ b/monitoredresource/gcp/monitored_resources_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package monitoredresource
+package gcp
 
 import (
 	"os"
@@ -40,7 +40,7 @@ func TestGKEContainerMonitoredResources(t *testing.T) {
 		namespaceID:   GKENamespaceStr,
 		podID:         GKEPodIDStr,
 	}
-	autoDetected := detectResourceType(nil, &gcpMetadata)
+	autoDetected := detectResourceType(&gcpMetadata)
 
 	if autoDetected == nil {
 		t.Fatal("GKEContainerMonitoredResource nil")
@@ -70,7 +70,7 @@ func TestGKEContainerMonitoredResourcesV2(t *testing.T) {
 		podID:         GKEPodIDStr,
 		monitoringV2:  true,
 	}
-	autoDetected := detectResourceType(nil, &gcpMetadata)
+	autoDetected := detectResourceType(&gcpMetadata)
 
 	if autoDetected == nil {
 		t.Fatal("GKEContainerMonitoredResource nil")
@@ -94,7 +94,7 @@ func TestGCEInstanceMonitoredResources(t *testing.T) {
 		projectID:  GCPProjectIDStr,
 		zone:       GCPZoneStr,
 	}
-	autoDetected := detectResourceType(nil, &gcpMetadata)
+	autoDetected := detectResourceType(&gcpMetadata)
 
 	if autoDetected == nil {
 		t.Fatal("GCEInstanceMonitoredResource nil")
@@ -107,26 +107,3 @@ func TestGCEInstanceMonitoredResources(t *testing.T) {
 		t.Errorf("GCEInstanceMonitoredResource Failed: %v", autoDetected)
 	}
 }
-
-func TestAWSEC2InstanceMonitoredResources(t *testing.T) {
-	os.Setenv("KUBERNETES_SERVICE_HOST", "")
-	gcpMetadata := gcpMetadata{}
-
-	awsIdentityDoc := &awsIdentityDocument{
-		"123456789012",
-		"i-1234567890abcdef0",
-		"us-west-2",
-	}
-	autoDetected := detectResourceType(awsIdentityDoc, &gcpMetadata)
-
-	if autoDetected == nil {
-		t.Fatal("AWSEC2InstanceMonitoredResource nil")
-	}
-	resType, labels := autoDetected.MonitoredResource()
-	if resType != "aws_ec2_instance" ||
-		labels["instance_id"] != "i-1234567890abcdef0" ||
-		labels["aws_account"] != "123456789012" ||
-		labels["region"] != "aws:us-west-2" {
-		t.Errorf("AWSEC2InstanceMonitoredResource Failed: %v", autoDetected)
-	}
-}
diff --git a/resource_test.go b/resource_test.go
index 621de23..fa3bd4d 100644
--- a/resource_test.go
+++ b/resource_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,8 +16,11 @@ package stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver"
 
 import (
 	"fmt"
+	"sync"
 	"testing"
 
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp"
 	"github.com/google/go-cmp/cmp"
 	"go.opencensus.io/resource"
 	"go.opencensus.io/resource/resourcekeys"
@@ -27,7 +30,9 @@ import (
 func TestDefaultMapResource(t *testing.T) {
 	cases := []struct {
 		input *resource.Resource
-		want  *monitoredrespb.MonitoredResource
+		// used to replace the resource returned by monitoredresource.Autodetect
+		autoRes gcp.Interface
+		want    *monitoredrespb.MonitoredResource
 	}{
 		// Verify that the mapping works and that we skip over the
 		// first mapping that doesn't apply.
@@ -158,6 +163,81 @@ func TestDefaultMapResource(t *testing.T) {
 				},
 			},
 		},
+		// Test autodecting missing Resource labels
+		{
+			input: &resource.Resource{
+				Type: resourcekeys.CloudType,
+				Labels: map[string]string{
+					stackdriverProjectID:          "proj1",
+					resourcekeys.CloudKeyProvider: resourcekeys.CloudProviderAWS,
+					"extra_key":                   "must be ignored",
+				},
+			},
+			autoRes: &monitoredresource.AWSEC2Instance{
+				AWSAccount: "account1",
+				InstanceID: "inst1",
+				Region:     "region1",
+			},
+			want: &monitoredrespb.MonitoredResource{
+				Type: "aws_ec2_instance",
+				Labels: map[string]string{
+					"project_id":  "proj1",
+					"instance_id": "inst1",
+					"region":      "aws:region1",
+					"aws_account": "account1",
+				},
+			},
+		},
+		// Test autodetecting partial missing Resource labels
+		{
+			input: &resource.Resource{
+				Type: resourcekeys.CloudType,
+				Labels: map[string]string{
+					stackdriverProjectID:          "proj1",
+					resourcekeys.CloudKeyProvider: resourcekeys.CloudProviderGCP,
+					resourcekeys.CloudKeyZone:     "zone1",
+				},
+			},
+			autoRes: &monitoredresource.GCEInstance{
+				ProjectID:  "proj1",
+				InstanceID: "inst2",
+				Zone:       "zone2",
+			},
+			want: &monitoredrespb.MonitoredResource{
+				Type: "gce_instance",
+				Labels: map[string]string{
+					"project_id":  "proj1",
+					"instance_id": "inst2",
+					"zone":        "zone1", // incoming labels take precedent over autodetected
+				},
+			},
+		},
+		// Test GCP "zone" label is accepted for "location"
+		{
+			input: &resource.Resource{
+				Type: resourcekeys.K8SType,
+				Labels: map[string]string{
+					resourcekeys.K8SKeyPodName:       "pod1",
+					resourcekeys.K8SKeyNamespaceName: "namespace1",
+				},
+			},
+			autoRes: &monitoredresource.GKEContainer{
+				ProjectID:                  "proj1",
+				ClusterName:                "cluster1",
+				Zone:                       "zone1",
+				LoggingMonitoringV2Enabled: false,
+			},
+			want: &monitoredrespb.MonitoredResource{
+				Type: "k8s_pod",
+				Labels: map[string]string{
+					"project_id":     "proj1",
+					"location":       "zone1",
+					"cluster_name":   "cluster1",
+					"namespace_name": "namespace1",
+					"pod_name":       "pod1",
+				},
+			},
+		},
 		// Convert to Global.
 		{
 			input: &resource.Resource{
@@ -200,9 +280,76 @@ func TestDefaultMapResource(t *testing.T) {
 				Labels: nil,
 			},
 		},
+		// mapping for knative_revision with autodetected GCP metadata labels
+		{
+			input: &resource.Resource{
+				Type: "knative_revision",
+				Labels: map[string]string{
+					knativeServiceName:       "helloworld-go",
+					knativeRevisionName:      "helloworld-go-hfc7j",
+					knativeConfigurationName: "helloworld-go",
+					knativeNamespaceName:     "namespace1",
+				},
+			},
+			autoRes: &monitoredresource.GKEContainer{
+				ProjectID:   "proj1",
+				Zone:        "zone1",
+				ClusterName: "cluster1",
+			},
+			want: &monitoredrespb.MonitoredResource{
+				Type: "knative_revision",
+				Labels: map[string]string{
+					"project_id":         "proj1",
+					"service_name":       "helloworld-go",
+					"revision_name":      "helloworld-go-hfc7j",
+					"location":           "zone1",
+					"configuration_name": "helloworld-go",
+					"cluster_name":       "cluster1",
+					"namespace_name":     "namespace1",
+				},
+			},
+		},
+		// mapping for knative_revision with explicit GCP metadata labels
+		{
+			input: &resource.Resource{
+				Type: "knative_revision",
+				Labels: map[string]string{
+					stackdriverProjectID:           "proj1",
+					resourcekeys.CloudKeyZone:      "zone1",
+					resourcekeys.K8SKeyClusterName: "cluster1",
+					knativeServiceName:             "helloworld-go",
+					knativeRevisionName:            "helloworld-go-hfc7j",
+					knativeConfigurationName:       "helloworld-go",
+					knativeNamespaceName:           "namespace1",
+				},
+			},
+			autoRes: &monitoredresource.GKEContainer{},
+			want: &monitoredrespb.MonitoredResource{
+				Type: "knative_revision",
+				Labels: map[string]string{
+					"project_id":         "proj1",
+					"service_name":       "helloworld-go",
+					"revision_name":      "helloworld-go-hfc7j",
+					"location":           "zone1",
+					"configuration_name": "helloworld-go",
+					"cluster_name":       "cluster1",
+					"namespace_name":     "namespace1",
+				},
+			},
+		},
 	}
 	for i, c := range cases {
 		t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
+			// defer test cleanup
+			defer func() {
+				autodetectOnce = new(sync.Once)
+				autodetectFunc = dummyAutodetect
+			}()
+
+			if c.autoRes != nil {
+				autodetectFunc = func() gcp.Interface { return c.autoRes }
+			}
+
 			got := defaultMapResource(c.input)
 			if diff := cmp.Diff(got, c.want); diff != "" {
 				t.Errorf("Values differ -got +want: %s", diff)
diff --git a/stackdriver_test.go b/stackdriver_test.go
index 3238489..91773dd 100644
--- a/stackdriver_test.go
+++ b/stackdriver_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018, OpenCensus Authors
+// Copyright 2020, OpenCensus Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -24,12 +24,25 @@ import (
 	"time"
 
 	"contrib.go.opencensus.io/exporter/stackdriver/internal/testpb"
+	"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp"
 	"go.opencensus.io/plugin/ochttp"
 	"go.opencensus.io/stats/view"
 	"go.opencensus.io/trace"
 	"golang.org/x/net/context/ctxhttp"
 )
 
+var (
+	dummyAutodetect = func() gcp.Interface {
+		return nil
+	}
+)
+
+func init() {
+	// monitoredresource.Autodetect() takes a few seconds to return when
+	// run outside of a cloud environment, so use a dummy autodetect for tests
+	autodetectFunc = dummyAutodetect
+}
+
 func TestExport(t *testing.T) {
 	projectID, ok := os.LookupEnv("STACKDRIVER_TEST_PROJECT_ID")
 	if !ok {