blob: 657298033ab69f50f913f20fe1d763bc85f1dd28 [file] [log] [blame]
/*
* Copyright 2018, 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 io.opencensus.exporter.trace.ocagent;
import com.google.common.annotations.VisibleForTesting;
import io.opencensus.common.OpenCensusLibraryInformation;
import io.opencensus.common.Timestamp;
import io.opencensus.contrib.monitoredresource.util.MonitoredResource;
import io.opencensus.contrib.monitoredresource.util.MonitoredResource.AwsEc2InstanceMonitoredResource;
import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGceInstanceMonitoredResource;
import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGkeContainerMonitoredResource;
import io.opencensus.contrib.monitoredresource.util.MonitoredResourceUtils;
import io.opencensus.proto.agent.common.v1.LibraryInfo;
import io.opencensus.proto.agent.common.v1.LibraryInfo.Language;
import io.opencensus.proto.agent.common.v1.Node;
import io.opencensus.proto.agent.common.v1.ProcessIdentifier;
import io.opencensus.proto.agent.common.v1.ServiceInfo;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
/** Utilities for detecting and creating {@link Node}. */
final class OcAgentNodeUtils {
// The current version of the OpenCensus OC-Agent Exporter.
@VisibleForTesting
static final String OC_AGENT_EXPORTER_VERSION = "0.17.0-SNAPSHOT"; // CURRENT_OPENCENSUS_VERSION
@VisibleForTesting static final String RESOURCE_TYPE_ATTRIBUTE_KEY = "OPENCENSUS_SOURCE_TYPE";
@VisibleForTesting static final String RESOURCE_LABEL_ATTRIBUTE_KEY = "OPENCENSUS_SOURCE_LABELS";
@Nullable
private static final MonitoredResource RESOURCE = MonitoredResourceUtils.getDefaultResource();
// Creates a Node with information from the OpenCensus library and environment variables.
static Node getNodeInfo(String serviceName) {
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
Timestamp censusTimestamp = Timestamp.fromMillis(System.currentTimeMillis());
return Node.newBuilder()
.setIdentifier(getProcessIdentifier(jvmName, censusTimestamp))
.setLibraryInfo(getLibraryInfo(OpenCensusLibraryInformation.VERSION))
.setServiceInfo(getServiceInfo(serviceName))
.putAllAttributes(getAttributeMap(RESOURCE))
.build();
}
// Creates process identifier with the given JVM name and start time.
@VisibleForTesting
static ProcessIdentifier getProcessIdentifier(String jvmName, Timestamp censusTimestamp) {
String hostname;
int pid;
// jvmName should be something like '<pid>@<hostname>', at least in Oracle and OpenJdk JVMs
int delimiterIndex = jvmName.indexOf('@');
if (delimiterIndex < 1) {
// Not the expected format, generate a random number.
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostname = "localhost";
}
// Generate a random number as the PID.
pid = new SecureRandom().nextInt();
} else {
hostname = jvmName.substring(delimiterIndex + 1, jvmName.length());
try {
pid = Integer.parseInt(jvmName.substring(0, delimiterIndex));
} catch (NumberFormatException e) {
// Generate a random number as the PID if format is unexpected.
pid = new SecureRandom().nextInt();
}
}
return ProcessIdentifier.newBuilder()
.setHostName(hostname)
.setPid(pid)
.setStartTimestamp(TraceProtoUtils.toTimestampProto(censusTimestamp))
.build();
}
// Creates library info with the given OpenCensus Java version.
@VisibleForTesting
static LibraryInfo getLibraryInfo(String currentOcJavaVersion) {
return LibraryInfo.newBuilder()
.setLanguage(Language.JAVA)
.setCoreLibraryVersion(currentOcJavaVersion)
.setExporterVersion(OC_AGENT_EXPORTER_VERSION)
.build();
}
// Creates service info with the given service name.
@VisibleForTesting
static ServiceInfo getServiceInfo(String serviceName) {
return ServiceInfo.newBuilder().setName(serviceName).build();
}
/*
* Creates an attribute map with the given MonitoredResource.
* If the given resource is not null, the attribute map contains exactly two entries:
*
* OPENCENSUS_SOURCE_TYPE:
* A string that describes the type of the resource prefixed by a domain namespace,
* e.g. “kubernetes.io/container”.
* OPENCENSUS_SOURCE_LABELS:
* A comma-separated list of labels describing the source in more detail,
* e.g. “key1=val1,key2=val2”. The allowed character set is appropriately constrained.
*/
// TODO: update the resource attributes once we have an agreement on the resource specs:
// https://github.com/census-instrumentation/opencensus-specs/pull/162.
@VisibleForTesting
static Map<String, String> getAttributeMap(@Nullable MonitoredResource resource) {
if (resource == null) {
return Collections.emptyMap();
} else {
Map<String, String> resourceAttributes = new HashMap<String, String>();
resourceAttributes.put(RESOURCE_TYPE_ATTRIBUTE_KEY, resource.getResourceType().name());
resourceAttributes.put(RESOURCE_LABEL_ATTRIBUTE_KEY, getConcatenatedResourceLabels(resource));
return resourceAttributes;
}
}
// Encodes the attributes of MonitoredResource into a comma-separated list of labels.
// For example "aws_account=account1,instance_id=instance1,region=us-east-2".
private static String getConcatenatedResourceLabels(MonitoredResource resource) {
StringBuilder resourceLabels = new StringBuilder();
if (resource instanceof AwsEc2InstanceMonitoredResource) {
AwsEc2InstanceMonitoredResource awsEc2Resource = (AwsEc2InstanceMonitoredResource) resource;
putIntoBuilderIfHasValue(resourceLabels, "aws_account", awsEc2Resource.getAccount());
putIntoBuilderIfHasValue(resourceLabels, "instance_id", awsEc2Resource.getInstanceId());
putIntoBuilderIfHasValue(resourceLabels, "region", awsEc2Resource.getRegion());
} else if (resource instanceof GcpGceInstanceMonitoredResource) {
GcpGceInstanceMonitoredResource gceResource = (GcpGceInstanceMonitoredResource) resource;
putIntoBuilderIfHasValue(resourceLabels, "gcp_account", gceResource.getAccount());
putIntoBuilderIfHasValue(resourceLabels, "instance_id", gceResource.getInstanceId());
putIntoBuilderIfHasValue(resourceLabels, "zone", gceResource.getZone());
} else if (resource instanceof GcpGkeContainerMonitoredResource) {
GcpGkeContainerMonitoredResource gkeResource = (GcpGkeContainerMonitoredResource) resource;
putIntoBuilderIfHasValue(resourceLabels, "gcp_account", gkeResource.getAccount());
putIntoBuilderIfHasValue(resourceLabels, "instance_id", gkeResource.getInstanceId());
putIntoBuilderIfHasValue(resourceLabels, "location", gkeResource.getZone());
putIntoBuilderIfHasValue(resourceLabels, "namespace_name", gkeResource.getNamespaceId());
putIntoBuilderIfHasValue(resourceLabels, "cluster_name", gkeResource.getClusterName());
putIntoBuilderIfHasValue(resourceLabels, "container_name", gkeResource.getContainerName());
putIntoBuilderIfHasValue(resourceLabels, "pod_name", gkeResource.getPodId());
}
return resourceLabels.toString();
}
// If the given resourceValue is not empty, encodes resourceKey and resourceValue as
// "resourceKey:resourceValue" and puts it into the given StringBuilder. Otherwise skip the value.
private static void putIntoBuilderIfHasValue(
StringBuilder builder, String resourceKey, String resourceValue) {
if (resourceValue.isEmpty()) {
return;
}
if (!(builder.length() == 0)) {
// Appends the comma separator to the front, if the StringBuilder already has entries.
builder.append(',');
}
builder.append(resourceKey);
builder.append('=');
builder.append(resourceValue);
}
private OcAgentNodeUtils() {}
}