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

Commit

Permalink
Integrate with core resource and known detectors (#84)
Browse files Browse the repository at this point in the history
* Integrate with core resource and known detectors

Signed-off-by: Fabian Reinartz <[email protected]>

* Fix typo

Signed-off-by: Fabian Reinartz <[email protected]>
  • Loading branch information
fabxc authored and rghetia committed Feb 19, 2019
1 parent c06c82c commit 7d76817
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 3 deletions.
109 changes: 109 additions & 0 deletions resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2019, 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 stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver"

import (
"contrib.go.opencensus.io/resource/resourcekeys"
"go.opencensus.io/resource"
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
)

type resourceMap struct {
// Mapping from the input resource type to the monitored resource type in Stackdriver.
srcType, dstType string
// Mapping from Stackdriver monitored resource label to an OpenCensus resource label.
labels map[string]string
}

// Resource labels that are generally internal to the exporter.
// Consider exposing these labels and a type identifier in the future to allow
// for customization.
const (
stackdriverLocation = "contrib.opencensus.io/exporter/stackdriver/location"
stackdriverProjectID = "contrib.opencensus.io/exporter/stackdriver/project_id"
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"
)

// Mappings for the well-known OpenCensus resources to applicable Stackdriver resources.
var resourceMappings = []resourceMap{
{
srcType: resourcekeys.K8STypeContainer,
dstType: "k8s_container",
labels: map[string]string{
"project_id": stackdriverProjectID,
"location": stackdriverLocation,
"cluster_name": resourcekeys.K8SKeyClusterName,
"namespace_name": resourcekeys.K8SKeyNamespaceName,
"pod_name": resourcekeys.K8SKeyPodName,
"container_name": resourcekeys.K8SKeyContainerName,
},
},
{
srcType: resourcekeys.GCPTypeGCEInstance,
dstType: "gce_instance",
labels: map[string]string{
"project_id": resourcekeys.GCPKeyGCEProjectID,
"instance_id": resourcekeys.GCPKeyGCEInstanceID,
"zone": resourcekeys.GCPKeyGCEZone,
},
},
{
srcType: resourcekeys.AWSTypeEC2Instance,
dstType: "aws_ec2_instance",
labels: map[string]string{
"project_id": stackdriverProjectID,
"instance_id": resourcekeys.AWSKeyEC2InstanceID,
"region": resourcekeys.AWSKeyEC2Region,
"aws_account": resourcekeys.AWSKeyEC2AccountID,
},
},
// Fallback to generic task resource.
{
srcType: "",
dstType: "generic_task",
labels: map[string]string{
"project_id": stackdriverProjectID,
"location": stackdriverLocation,
"namespace": stackdriverGenericTaskNamespace,
"job": stackdriverGenericTaskJob,
"task_id": stackdriverGenericTaskID,
},
},
}

func DefaultMapResource(res *resource.Resource) *monitoredrespb.MonitoredResource {
Outer:
for _, rm := range resourceMappings {
if res.Type != rm.srcType {
continue
}
result := &monitoredrespb.MonitoredResource{
Type: rm.dstType,
Labels: make(map[string]string, len(rm.labels)),
}
for dst, src := range rm.labels {
if v, ok := res.Labels[src]; ok {
result.Labels[dst] = v
} else {
// A required label wasn't filled at all. Try subsequent mappings.
continue Outer
}
}
return result
}
return nil
}
73 changes: 73 additions & 0 deletions resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2019, 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 stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver"

import (
"fmt"
"testing"

"contrib.go.opencensus.io/resource/resourcekeys"
"github.com/google/go-cmp/cmp"
"go.opencensus.io/resource"
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
)

func TestDefaultMapResource(t *testing.T) {
cases := []struct {
input *resource.Resource
want *monitoredrespb.MonitoredResource
}{
// Verify that the mapping works and that we skip over the
// first mapping that doesn't apply.
{
input: &resource.Resource{
Type: resourcekeys.GCPTypeGCEInstance,
Labels: map[string]string{
resourcekeys.GCPKeyGCEProjectID: "proj1",
resourcekeys.GCPKeyGCEInstanceID: "inst1",
resourcekeys.GCPKeyGCEZone: "zone1",
"extra_key": "must be ignored",
},
},
want: &monitoredrespb.MonitoredResource{
Type: "gce_instance",
Labels: map[string]string{
"project_id": "proj1",
"instance_id": "inst1",
"zone": "zone1",
},
},
},
// No match due to missing key.
{
input: &resource.Resource{
Type: resourcekeys.GCPTypeGCEInstance,
Labels: map[string]string{
resourcekeys.GCPKeyGCEProjectID: "proj1",
resourcekeys.GCPKeyGCEInstanceID: "inst1",
},
},
want: nil,
},
}
for i, c := range cases {
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
got := DefaultMapResource(c.input)
if diff := cmp.Diff(got, c.want); diff != "" {
t.Errorf("Values differ -got +want: %s", diff)
}
})
}
}
69 changes: 68 additions & 1 deletion stackdriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ import (
"errors"
"fmt"
"log"
"os"
"path"
"time"

metadataapi "cloud.google.com/go/compute/metadata"
traceapi "cloud.google.com/go/trace/apiv2"
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
"go.opencensus.io/resource"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"go.opencensus.io/trace"
Expand All @@ -73,9 +77,22 @@ type Options struct {
// ProjectID is the identifier of the Stackdriver
// project the user is uploading the stats data to.
// If not set, this will default to your "Application Default Credentials".
// For details see: https://developers.google.com/accounts/docs/application-default-credentials
// For details see: https://developers.google.com/accounts/docs/application-default-credentials.
//
// It will be used in the project_id label of a Stackdriver monitored
// resource if the resource does not inherently belong to a specific
// project, e.g. on-premise resource like k8s_container or generic_task.
ProjectID string

// Location is the identifier of the GCP or AWS cloud region/zone in which
// the data for a resource is stored.
// If not set, it will default to the location provided by the metadata server.
//
// It will be used in the location label of a Stackdriver monitored resource
// if the resource does not inherently belong to a specific project, e.g.
// on-premise resource like k8s_container or generic_task.
Location string

// OnError is the hook to be called when there is
// an error uploading the stats or tracing data.
// If no custom hook is set, errors are logged.
Expand Down Expand Up @@ -153,6 +170,21 @@ type Options struct {
// Optional, but encouraged.
MonitoredResource monitoredresource.Interface

// ResourceDetector provides a hook to discover arbitrary resource information.
//
// The translation function provided in MapResource must be able to conver the
// the resource information to a Stackdriver monitored resource.
//
// If this field is unset, resource type and tags will automatically be discovered through
// the OC_RESOURCE_TYPE and OC_RESOURCE_LABELS environment variables.
ResourceDetector resource.Detector

// MapResource converts a OpenCensus resource to a Stackdriver monitored resource.
//
// If this field is unset, DefaultMapResource will be used which encodes a set of default
// conversions from auto-detected resources to well-known Stackdriver monitored resources.
MapResource func(*resource.Resource) *monitoredrespb.MonitoredResource

// MetricPrefix overrides the prefix of a Stackdriver metric display names.
// Optional. If unset defaults to "OpenCensus/".
// Deprecated: Provide GetMetricDisplayName to change the display name of
Expand Down Expand Up @@ -253,10 +285,45 @@ func NewExporter(o Options) (*Exporter, error) {
}
o.ProjectID = creds.ProjectID
}
if o.Location == "" {
ctx := o.Context
if ctx == nil {
ctx = context.Background()
}
zone, err := metadataapi.Zone()
if err != nil {
log.Printf("Setting Stackdriver default location failed: %s", err)
} else {
log.Printf("Setting Stackdriver default location to %q", zone)

This comment has been minimized.

Copy link
@thanosp

thanosp Apr 9, 2019

Error or not, a line is dumped in the logs.
Was this intentional? How can we tell good logs from bad?
Should the error had been returned in the first case and perhaps no log written in the good case?

This comment has been minimized.

Copy link
@rghetia

rghetia Apr 9, 2019

Contributor

The first one should be warning because it will continue to work without the zone information.
The second one should he Info. Since it is only executes during initialization, it would not hurt to have the information logged.

This comment has been minimized.

Copy link
@thanosp

thanosp Apr 9, 2019

For us this is now printed on every pod starting on every deploy which seems pointless information.
handleError offers a nice alternative to avoid using generic Printf which was used by us to set the proper levels as we see fit. For this one our choices are limited.
I think the logging logic here is now a bit inconsistent with the rest of the code.

This comment has been minimized.

Copy link
@rghetia

rghetia Apr 9, 2019

Contributor

@thanosp Do you mind sending a PR with handleError?

o.Location = zone
}
}

if o.MonitoredResource != nil {
o.Resource = convertMonitoredResourceToPB(o.MonitoredResource)
}
if o.MapResource == nil {
o.MapResource = DefaultMapResource
}
if o.ResourceDetector != nil {
// For backwards-compatibility we still respect the deprecated resource field.
if o.Resource != nil {
return nil, errors.New("stackdriver: ResourceDetector must not be used in combination with deprecated resource fields")
}
res, err := o.ResourceDetector(o.Context)
if err != nil {
return nil, fmt.Errorf("stackdriver: detect resource: %s", err)
}
// Populate internal resource labels for defaulting project_id, location, and
// generic resource labels of applicable monitored resources.
res.Labels[stackdriverProjectID] = o.ProjectID
res.Labels[stackdriverLocation] = o.Location
res.Labels[stackdriverGenericTaskNamespace] = "default"
res.Labels[stackdriverGenericTaskJob] = path.Base(os.Args[0])
res.Labels[stackdriverGenericTaskID] = getTaskValue()

o.Resource = o.MapResource(res)
}

se, err := newStatsExporter(o)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import (
"sync"
"time"

"go.opencensus.io"
opencensus "go.opencensus.io"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"go.opencensus.io/trace"

"cloud.google.com/go/monitoring/apiv3"
monitoring "cloud.google.com/go/monitoring/apiv3"
"github.com/golang/protobuf/ptypes/timestamp"
"google.golang.org/api/option"
"google.golang.org/api/support/bundler"
Expand Down

0 comments on commit 7d76817

Please sign in to comment.