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

Commit 81c8a51

Browse files
authored
Add monitored resource util for stackdriver exporter. (#26)
* Add monitored resource util for stackdriver exporter. * Move aws prefix for zone from the utility to stackdriver export. * Add Monitored Resource support for tracing. * Remove stackdriver dependency from MonitoredResources. * Create new package for monitored resources. - fixed naming convention - used sync.once and lazy loading to initialize metadata/awsdoc - fixed other review comments. * Fixed go formating. * Fix more review comments. - removed const file - replaced getter functioni for type/labels with single get function. * remove commented out code. * add documentation make gcpMetadata and awsIdentityDocument type private. * Retrieve AWS and GCP metadata in parallel Fixed review comments * Fix style issues.
1 parent 4fdc0ef commit 81c8a51

10 files changed

Lines changed: 707 additions & 53 deletions

File tree

examples/stats/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"time"
2525

2626
"contrib.go.opencensus.io/exporter/stackdriver"
27+
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
2728
"go.opencensus.io/stats"
2829
"go.opencensus.io/stats/view"
2930
)
@@ -46,7 +47,8 @@ func main() {
4647
// See https://developers.google.com/identity/protocols/application-default-credentials
4748
// for more details.
4849
exporter, err := stackdriver.NewExporter(stackdriver.Options{
49-
ProjectID: "project-id", // Google Cloud Console project ID.
50+
ProjectID: "your-project-id", // Google Cloud Console project ID for stackdriver.
51+
MonitoredResource: monitoredresource.Autodetect(),
5052
})
5153
if err != nil {
5254
log.Fatal(err)

examples/stats/stats

14.6 MB
Binary file not shown.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2018, 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+
15+
package monitoredresource
16+
17+
import (
18+
"github.com/aws/aws-sdk-go/aws/ec2metadata"
19+
"github.com/aws/aws-sdk-go/aws/session"
20+
)
21+
22+
// awsIdentityDocument is used to store parsed AWS Identity Document.
23+
type awsIdentityDocument struct {
24+
// accountID is the AWS account number for the VM.
25+
accountID string
26+
27+
// instanceID is the instance id of the instance.
28+
instanceID string
29+
30+
// Region is the AWS region for the VM.
31+
region string
32+
}
33+
34+
// retrieveAWSIdentityDocument attempts to retrieve AWS Identity Document.
35+
// If the environment is AWS EC2 Instance then a valid document is retrieved.
36+
// Relevant attributes from the document are stored in awsIdentityDoc.
37+
// This is only done once.
38+
func retrieveAWSIdentityDocument() *awsIdentityDocument {
39+
awsIdentityDoc := awsIdentityDocument{}
40+
c := ec2metadata.New(session.New())
41+
if c.Available() == false {
42+
return nil
43+
}
44+
ec2InstanceIdentifyDocument, err := c.GetInstanceIdentityDocument()
45+
if err != nil {
46+
return nil
47+
}
48+
awsIdentityDoc.region = ec2InstanceIdentifyDocument.Region
49+
awsIdentityDoc.instanceID = ec2InstanceIdentifyDocument.InstanceID
50+
awsIdentityDoc.accountID = ec2InstanceIdentifyDocument.AccountID
51+
52+
return &awsIdentityDoc
53+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2018, 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+
15+
package monitoredresource
16+
17+
import (
18+
"log"
19+
"os"
20+
"strings"
21+
22+
"cloud.google.com/go/compute/metadata"
23+
)
24+
25+
// gcpMetadata represents metadata retrieved from GCP (GKE and GCE) environment.
26+
type gcpMetadata struct {
27+
28+
// projectID is the identifier of the GCP project associated with this resource, such as "my-project".
29+
projectID string
30+
31+
// instanceID is the numeric VM instance identifier assigned by Compute Engine.
32+
instanceID string
33+
34+
// clusterName is the name for the cluster the container is running in.
35+
clusterName string
36+
37+
// containerName is the name of the container.
38+
containerName string
39+
40+
// namespaceID is the identifier for the cluster namespace the container is running in
41+
namespaceID string
42+
43+
// podID is the identifier for the pod the container is running in.
44+
podID string
45+
46+
// zone is the Compute Engine zone in which the VM is running.
47+
zone string
48+
}
49+
50+
// retrieveGCPMetadata retrieves value of each Attribute from Metadata Server
51+
// in GKE container and GCE instance environment.
52+
// Some attributes are retrieved from the system environment.
53+
// This is only executed detectOnce.
54+
func retrieveGCPMetadata() *gcpMetadata {
55+
gcpMetadata := gcpMetadata{}
56+
var err error
57+
gcpMetadata.instanceID, err = metadata.InstanceID()
58+
if err != nil {
59+
// Not a GCP environment
60+
return &gcpMetadata
61+
}
62+
63+
gcpMetadata.projectID, err = metadata.ProjectID()
64+
logError(err)
65+
66+
gcpMetadata.zone, err = metadata.Zone()
67+
logError(err)
68+
69+
clusterName, err := metadata.InstanceAttributeValue("cluster-name")
70+
logError(err)
71+
gcpMetadata.clusterName = strings.TrimSpace(clusterName)
72+
73+
// Following attributes are derived from environment variables. They are configured
74+
// via yaml file. For details refer to:
75+
// https://cloud.google.com/kubernetes-engine/docs/tutorials/custom-metrics-autoscaling#exporting_metrics_from_the_application
76+
gcpMetadata.namespaceID = os.Getenv("NAMESPACE")
77+
gcpMetadata.containerName = os.Getenv("CONTAINER_NAME")
78+
gcpMetadata.podID = os.Getenv("HOSTNAME")
79+
80+
return &gcpMetadata
81+
}
82+
83+
// logError logs error only if the error is present and it is not 'not defined'
84+
func logError(err error) {
85+
if err != nil {
86+
if !strings.Contains(err.Error(), "not defined") {
87+
log.Printf("Error retrieving gcp metadata: %v", err)
88+
}
89+
}
90+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright 2018, 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+
15+
package monitoredresource
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"sync"
21+
)
22+
23+
// Interface is a type that represent monitor resource that satisfies monitoredresource.Interface
24+
type Interface interface {
25+
26+
// MonitoredResource returns the resource type and resource labels.
27+
MonitoredResource() (resType string, labels map[string]string)
28+
}
29+
30+
// GKEContainer represents gke_container type monitored resource.
31+
// For definition refer to
32+
// https://cloud.google.com/monitoring/api/resources#tag_gke_container
33+
type GKEContainer struct {
34+
35+
// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
36+
ProjectID string
37+
38+
// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
39+
InstanceID string
40+
41+
// ClusterName is the name for the cluster the container is running in.
42+
ClusterName string
43+
44+
// ContainerName is the name of the container.
45+
ContainerName string
46+
47+
// NamespaceID is the identifier for the cluster namespace the container is running in
48+
NamespaceID string
49+
50+
// PodID is the identifier for the pod the container is running in.
51+
PodID string
52+
53+
// Zone is the Compute Engine zone in which the VM is running.
54+
Zone string
55+
}
56+
57+
// MonitoredResource returns resource type and resource labels for GKEContainer
58+
func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) {
59+
labels = map[string]string{
60+
"project_id": gke.ProjectID,
61+
"instance_id": gke.InstanceID,
62+
"zone": gke.Zone,
63+
"cluster_name": gke.ClusterName,
64+
"container_name": gke.ContainerName,
65+
"namespace_id": gke.NamespaceID,
66+
"pod_id": gke.PodID,
67+
}
68+
return "gke_container", labels
69+
}
70+
71+
// GCEInstance represents gce_instance type monitored resource.
72+
// For definition refer to
73+
// https://cloud.google.com/monitoring/api/resources#tag_gce_instance
74+
type GCEInstance struct {
75+
76+
// ProjectID is the identifier of the GCP project associated with this resource, such as "my-project".
77+
ProjectID string
78+
79+
// InstanceID is the numeric VM instance identifier assigned by Compute Engine.
80+
InstanceID string
81+
82+
// Zone is the Compute Engine zone in which the VM is running.
83+
Zone string
84+
}
85+
86+
// MonitoredResource returns resource type and resource labels for GCEInstance
87+
func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) {
88+
labels = map[string]string{
89+
"project_id": gce.ProjectID,
90+
"instance_id": gce.InstanceID,
91+
"zone": gce.Zone,
92+
}
93+
return "gce_instance", labels
94+
}
95+
96+
// AWSEC2Instance represents aws_ec2_instance type monitored resource.
97+
// For definition refer to
98+
// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance
99+
type AWSEC2Instance struct {
100+
101+
// AWSAccount is the AWS account number for the VM.
102+
AWSAccount string
103+
104+
// InstanceID is the instance id of the instance.
105+
InstanceID string
106+
107+
// Region is the AWS region for the VM. The format of this field is "aws:{region}",
108+
// where supported values for {region} are listed at
109+
// http://docs.aws.amazon.com/general/latest/gr/rande.html.
110+
Region string
111+
}
112+
113+
// MonitoredResource returns resource type and resource labels for AWSEC2Instance
114+
func (aws *AWSEC2Instance) MonitoredResource() (resType string, labels map[string]string) {
115+
labels = map[string]string{
116+
"aws_account": aws.AWSAccount,
117+
"instance_id": aws.InstanceID,
118+
"region": aws.Region,
119+
}
120+
return "aws_ec2_instance", labels
121+
}
122+
123+
// Autodetect auto detects monitored resources based on
124+
// the environment where the application is running.
125+
// It supports detection of following resource types
126+
// 1. gke_container:
127+
// 2. gce_instance:
128+
// 3. aws_ec2_instance:
129+
//
130+
// Returns MonitoredResInterface which implements getLabels() and getType()
131+
// For resource definition go to https://cloud.google.com/monitoring/api/resources
132+
func Autodetect() Interface {
133+
return func() Interface {
134+
var autoDetected Interface
135+
var awsIdentityDoc *awsIdentityDocument
136+
var gcpMetadata *gcpMetadata
137+
detectOnce.Do(func() {
138+
139+
// First attempts to retrieve AWS Identity Doc and GCP metadata.
140+
// It then determines the resource type
141+
// In GCP and AWS environment both func finishes quickly. However,
142+
// in an environment other than those (e.g local laptop) it
143+
// takes 2 seconds for GCP and 5-6 for AWS.
144+
var wg sync.WaitGroup
145+
wg.Add(2)
146+
147+
go func() {
148+
defer wg.Done()
149+
awsIdentityDoc = retrieveAWSIdentityDocument()
150+
}()
151+
go func() {
152+
defer wg.Done()
153+
gcpMetadata = retrieveGCPMetadata()
154+
}()
155+
156+
wg.Wait()
157+
autoDetected = detectResourceType(awsIdentityDoc, gcpMetadata)
158+
})
159+
return autoDetected
160+
}()
161+
162+
}
163+
164+
// createAWSEC2InstanceMonitoredResource creates a aws_ec2_instance monitored resource
165+
// awsIdentityDoc contains AWS EC2 specific attributes.
166+
func createAWSEC2InstanceMonitoredResource(awsIdentityDoc *awsIdentityDocument) *AWSEC2Instance {
167+
awsInstance := AWSEC2Instance{
168+
AWSAccount: awsIdentityDoc.accountID,
169+
InstanceID: awsIdentityDoc.instanceID,
170+
Region: fmt.Sprintf("aws:%s", awsIdentityDoc.region),
171+
}
172+
return &awsInstance
173+
}
174+
175+
// createGCEInstanceMonitoredResource creates a gce_instance monitored resource
176+
// gcpMetadata contains GCP (GKE or GCE) specific attributes.
177+
func createGCEInstanceMonitoredResource(gcpMetadata *gcpMetadata) *GCEInstance {
178+
gceInstance := GCEInstance{
179+
ProjectID: gcpMetadata.projectID,
180+
InstanceID: gcpMetadata.instanceID,
181+
Zone: gcpMetadata.zone,
182+
}
183+
return &gceInstance
184+
}
185+
186+
// createGKEContainerMonitoredResource creates a gke_container monitored resource
187+
// gcpMetadata contains GCP (GKE or GCE) specific attributes.
188+
func createGKEContainerMonitoredResource(gcpMetadata *gcpMetadata) *GKEContainer {
189+
gkeContainer := GKEContainer{
190+
ProjectID: gcpMetadata.projectID,
191+
InstanceID: gcpMetadata.instanceID,
192+
Zone: gcpMetadata.zone,
193+
ContainerName: gcpMetadata.containerName,
194+
ClusterName: gcpMetadata.clusterName,
195+
NamespaceID: gcpMetadata.namespaceID,
196+
PodID: gcpMetadata.podID,
197+
}
198+
return &gkeContainer
199+
}
200+
201+
// detectOnce is used to make sure GCP and AWS metadata detect function executes only once.
202+
var detectOnce sync.Once
203+
204+
// detectResourceType determines the resource type.
205+
// awsIdentityDoc contains AWS EC2 attributes. nil if it is not AWS EC2 environment
206+
// gcpMetadata contains GCP (GKE or GCE) specific attributes.
207+
func detectResourceType(awsIdentityDoc *awsIdentityDocument, gcpMetadata *gcpMetadata) Interface {
208+
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
209+
gcpMetadata != nil && gcpMetadata.instanceID != "" {
210+
return createGKEContainerMonitoredResource(gcpMetadata)
211+
} else if gcpMetadata != nil && gcpMetadata.instanceID != "" {
212+
return createGCEInstanceMonitoredResource(gcpMetadata)
213+
} else if awsIdentityDoc != nil {
214+
return createAWSEC2InstanceMonitoredResource(awsIdentityDoc)
215+
}
216+
return nil
217+
}

0 commit comments

Comments
 (0)