exporter/collector/googlemanagedprometheus/monitoredresource.go (115 lines of code) (raw):
// Copyright 2022 Google LLC
//
// 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
//
// https://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 googlemanagedprometheus
import (
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
semconv "go.opentelemetry.io/collector/semconv/v1.22.0"
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
)
const (
// In GMP, location, cluster, and namespace labels can be overridden by
// users to set corresponding fields in the monitored resource. To
// replicate that behavior in the collector, we expect these labels
// to be moved from metric labels to resource labels using the groupbyattrs
// processor. If these resource labels are present, use them to set the MR.
locationLabel = "location"
clusterLabel = "cluster"
namespaceLabel = "namespace"
jobLabel = "job"
serviceNamespaceLabel = "service_namespace"
instanceLabel = "instance"
unknownServicePrefix = "unknown_service"
)
// promTargetKeys are attribute keys which are used in the prometheus_target monitored resource.
// It is also used by GMP to exclude these keys from the target_info metric.
var promTargetKeys = map[string][]string{
locationLabel: {
locationLabel,
semconv.AttributeCloudAvailabilityZone,
semconv.AttributeCloudRegion,
},
clusterLabel: {
clusterLabel,
semconv.AttributeK8SClusterName,
semconv.AttributeK8SClusterUID,
},
namespaceLabel: {
namespaceLabel,
semconv.AttributeK8SNamespaceName,
},
jobLabel: {
jobLabel,
semconv.AttributeServiceName,
semconv.AttributeFaaSName,
semconv.AttributeK8SDeploymentName,
semconv.AttributeK8SStatefulSetName,
semconv.AttributeK8SDaemonSetName,
semconv.AttributeK8SJobName,
semconv.AttributeK8SCronJobName,
},
serviceNamespaceLabel: {
semconv.AttributeServiceNamespace,
},
instanceLabel: {
instanceLabel,
semconv.AttributeServiceInstanceID,
semconv.AttributeFaaSInstance,
semconv.AttributeK8SPodName,
semconv.AttributeHostID,
},
}
func (c Config) MapToPrometheusTarget(res pcommon.Resource) *monitoredrespb.MonitoredResource {
attrs := res.Attributes()
// Prepend namespace if it exists to match what is specified in
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#resource-attributes-1
job := getStringOrEmpty(attrs, promTargetKeys[jobLabel]...)
serviceNamespace := getStringOrEmpty(attrs, promTargetKeys[serviceNamespaceLabel]...)
if serviceNamespace != "" {
job = serviceNamespace + "/" + job
}
// Append k8s.container.name to instance if we are using the pod name
instanceKey, instance := getKeyAndStringOrDefault(attrs, "", promTargetKeys[instanceLabel]...)
if instanceKey == semconv.AttributeK8SPodName {
if containerName, ok := attrs.Get(semconv.AttributeK8SContainerName); ok {
instance += "/" + containerName.Str()
}
}
return &monitoredrespb.MonitoredResource{
Type: "prometheus_target",
Labels: map[string]string{
locationLabel: getStringOrEmpty(attrs, promTargetKeys[locationLabel]...),
clusterLabel: getStringOrDefaultClusterName(attrs, promTargetKeys[clusterLabel]...),
namespaceLabel: getStringOrEmpty(attrs, promTargetKeys[namespaceLabel]...),
jobLabel: job,
instanceLabel: instance,
},
}
}
// According to Cloud Monitoring docs, there are special values for
// cluser in some runtimes.
// See: https://cloud.google.com/stackdriver/docs/managed-prometheus/setup-opsagent
func getStringOrDefaultClusterName(attrs pcommon.Map, keys ...string) string {
defaultClusterName := ""
cloudPlatform := getStringOrEmpty(attrs, semconv.AttributeCloudPlatform)
switch cloudPlatform {
case semconv.AttributeCloudPlatformGCPComputeEngine:
defaultClusterName = "__gce__"
case semconv.AttributeCloudPlatformGCPCloudRun:
defaultClusterName = "__run__"
}
return getStringOrDefault(attrs, defaultClusterName, promTargetKeys[clusterLabel]...)
}
// getStringOrDefault returns the value of the first key found, or the orElse string.
func getStringOrDefault(attributes pcommon.Map, orElse string, keys ...string) string {
_, str := getKeyAndStringOrDefault(attributes, orElse, keys...)
return str
}
// getStringOrEmpty returns the value of the first key found, or the orElse string.
func getKeyAndStringOrDefault(attributes pcommon.Map, orElse string, keys ...string) (string, string) {
for _, k := range keys {
// skip the attribute if it starts with unknown_service, since the SDK
// sets this by default. It is used as a fallback below if no other
// values are found.
if val, ok := attributes.Get(k); ok && !strings.HasPrefix(val.Str(), unknownServicePrefix) {
return k, val.Str()
}
}
if contains(keys, string(semconv.AttributeServiceName)) {
// the service name started with unknown_service, and was ignored above
if val, ok := attributes.Get(semconv.AttributeServiceName); ok {
return semconv.AttributeServiceName, val.Str()
}
}
return "", orElse
}
// getStringOrEmpty returns the value of the first key found, or the empty string.
func getStringOrEmpty(attributes pcommon.Map, keys ...string) string {
return getStringOrDefault(attributes, "", keys...)
}
func contains(list []string, element string) bool {
for _, item := range list {
if item == element {
return true
}
}
return false
}