io.opencensus.resource.Resource Maven / Gradle / Ivy
/*
* 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.resource;
import com.google.auto.value.AutoValue;
import io.opencensus.common.ExperimentalApi;
import io.opencensus.internal.DefaultVisibilityForTesting;
import io.opencensus.internal.StringUtils;
import io.opencensus.internal.Utils;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* {@link Resource} represents a resource, which capture identifying information about the entities
* for which signals (stats or traces) are reported. It further provides a framework for detection
* of resource information from the environment and progressive population as signals propagate from
* the core instrumentation library to a backend's exporter.
*
* @since 0.18
*/
@Immutable
@AutoValue
@ExperimentalApi
public abstract class Resource {
@DefaultVisibilityForTesting static final int MAX_LENGTH = 255;
private static final String OC_RESOURCE_TYPE_ENV = "OC_RESOURCE_TYPE";
private static final String OC_RESOURCE_LABELS_ENV = "OC_RESOURCE_LABELS";
private static final String LABEL_LIST_SPLITTER = ",";
private static final String LABEL_KEY_VALUE_SPLITTER = "=";
private static final String ERROR_MESSAGE_INVALID_CHARS =
" should be a ASCII string with a length greater than 0 and not exceed "
+ MAX_LENGTH
+ " characters.";
private static final String ERROR_MESSAGE_INVALID_VALUE =
" should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters.";
@Nullable
private static final String ENV_TYPE = parseResourceType(System.getenv(OC_RESOURCE_TYPE_ENV));
private static final Map ENV_LABEL_MAP =
parseResourceLabels(System.getenv(OC_RESOURCE_LABELS_ENV));
Resource() {}
/**
* Returns the type identifier for the resource.
*
* @return the type identifier for the resource.
* @since 0.18
*/
@Nullable
public abstract String getType();
/**
* Returns a map of labels that describe the resource.
*
* @return a map of labels.
* @since 0.18
*/
public abstract Map getLabels();
/**
* Returns a {@link Resource}. This resource information is loaded from the OC_RESOURCE_TYPE and
* OC_RESOURCE_LABELS environment variables.
*
* @return a {@code Resource}.
* @since 0.18
*/
public static Resource createFromEnvironmentVariables() {
return createInternal(ENV_TYPE, ENV_LABEL_MAP);
}
/**
* Returns a {@link Resource}.
*
* @param type the type identifier for the resource.
* @param labels a map of labels that describe the resource.
* @return a {@code Resource}.
* @throws NullPointerException if {@code labels} is null.
* @throws IllegalArgumentException if type or label key or label value is not a valid printable
* ASCII string or exceed {@link #MAX_LENGTH} characters.
* @since 0.18
*/
public static Resource create(@Nullable String type, Map labels) {
return createInternal(
type,
Collections.unmodifiableMap(
new LinkedHashMap(Utils.checkNotNull(labels, "labels"))));
}
/**
* Returns a {@link Resource} that runs all input resources sequentially and merges their results.
* In case a type of label key is already set, the first set value takes precedence.
*
* @param resources a list of resources.
* @return a {@code Resource}.
* @since 0.18
*/
@Nullable
public static Resource mergeResources(List resources) {
Resource currentResource = null;
for (Resource resource : resources) {
currentResource = merge(currentResource, resource);
}
return currentResource;
}
private static Resource createInternal(@Nullable String type, Map labels) {
return new AutoValue_Resource(type, labels);
}
/**
* Creates a resource type from the OC_RESOURCE_TYPE environment variable.
*
* OC_RESOURCE_TYPE: A string that describes the type of the resource prefixed by a domain
* namespace, e.g. “kubernetes.io/container”.
*/
@Nullable
static String parseResourceType(@Nullable String rawEnvType) {
if (rawEnvType != null && !rawEnvType.isEmpty()) {
Utils.checkArgument(isValidAndNotEmpty(rawEnvType), "Type" + ERROR_MESSAGE_INVALID_CHARS);
return rawEnvType.trim();
}
return rawEnvType;
}
/*
* Creates a label map from the OC_RESOURCE_LABELS environment variable.
*
*
OC_RESOURCE_LABELS: A comma-separated list of labels describing the source in more detail,
* e.g. “key1=val1,key2=val2”. Domain names and paths are accepted as label keys. Values may be
* quoted or unquoted in general. If a value contains whitespaces, =, or " characters, it must
* always be quoted.
*/
static Map parseResourceLabels(@Nullable String rawEnvLabels) {
if (rawEnvLabels == null) {
return Collections.emptyMap();
} else {
Map labels = new HashMap();
String[] rawLabels = rawEnvLabels.split(LABEL_LIST_SPLITTER, -1);
for (String rawLabel : rawLabels) {
String[] keyValuePair = rawLabel.split(LABEL_KEY_VALUE_SPLITTER, -1);
if (keyValuePair.length != 2) {
continue;
}
String key = keyValuePair[0].trim();
String value = keyValuePair[1].trim().replaceAll("^\"|\"$", "");
Utils.checkArgument(isValidAndNotEmpty(key), "Label key" + ERROR_MESSAGE_INVALID_CHARS);
Utils.checkArgument(isValid(value), "Label value" + ERROR_MESSAGE_INVALID_VALUE);
labels.put(key, value);
}
return Collections.unmodifiableMap(labels);
}
}
/**
* Returns a new, merged {@link Resource} by merging two resources. In case of a collision, first
* resource takes precedence.
*/
@Nullable
private static Resource merge(@Nullable Resource resource, @Nullable Resource otherResource) {
if (otherResource == null) {
return resource;
}
if (resource == null) {
return otherResource;
}
String mergedType = resource.getType() != null ? resource.getType() : otherResource.getType();
Map mergedLabelMap =
new LinkedHashMap(otherResource.getLabels());
// Labels from resource overwrite labels from otherResource.
for (Entry entry : resource.getLabels().entrySet()) {
mergedLabelMap.put(entry.getKey(), entry.getValue());
}
return createInternal(mergedType, Collections.unmodifiableMap(mergedLabelMap));
}
/**
* Determines whether the given {@code String} is a valid printable ASCII string with a length not
* exceed {@link #MAX_LENGTH} characters.
*
* @param name the name to be validated.
* @return whether the name is valid.
*/
private static boolean isValid(String name) {
return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name);
}
/**
* Determines whether the given {@code String} is a valid printable ASCII string with a length
* greater than 0 and not exceed {@link #MAX_LENGTH} characters.
*
* @param name the name to be validated.
* @return whether the name is valid.
*/
private static boolean isValidAndNotEmpty(String name) {
return !name.isEmpty() && isValid(name);
}
}