Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Support cgroup v2 #7167

Merged
merged 4 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.resources;

import io.opentelemetry.api.internal.OtelEncodingUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

/** Utility for extracting the container ID from runtimes inside cgroup v1 containers. */
final class CgroupV1ContainerIdExtractor {

private static final Logger logger =
Logger.getLogger(CgroupV1ContainerIdExtractor.class.getName());
static final Path V1_CGROUP_PATH = Paths.get("/proc/self/cgroup");
private final ContainerResource.Filesystem filesystem;

CgroupV1ContainerIdExtractor() {
this(ContainerResource.FILESYSTEM_INSTANCE);
}

// Exists for testing
CgroupV1ContainerIdExtractor(ContainerResource.Filesystem filesystem) {
this.filesystem = filesystem;
}

/**
* Each line of cgroup file looks like "14:name=systemd:/docker/.../... A hex string is expected
* inside the last section separated by '/' Each segment of the '/' can contain metadata separated
* by either '.' (at beginning) or '-' (at end)
*
* @return containerId
*/
Optional<String> extractContainerId() {
if (!filesystem.isReadable(V1_CGROUP_PATH)) {
return Optional.empty();
}
try (Stream<String> lines = filesystem.lines(V1_CGROUP_PATH)) {
return lines
.filter(line -> !line.isEmpty())
.map(CgroupV1ContainerIdExtractor::getIdFromLine)
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read file", e);
}
return Optional.empty();
}

private static Optional<String> getIdFromLine(String line) {
// This cgroup output line should have the container id in it
int lastSlashIdx = line.lastIndexOf('/');
if (lastSlashIdx < 0) {
return Optional.empty();
}

String containerId;

String lastSection = line.substring(lastSlashIdx + 1);
int colonIdx = lastSection.lastIndexOf(':');

if (colonIdx != -1) {
// since containerd v1.5.0+, containerId is divided by the last colon when the cgroupDriver is
// systemd:
// https://github.com/containerd/containerd/blob/release/1.5/pkg/cri/server/helpers_linux.go#L64
containerId = lastSection.substring(colonIdx + 1);
} else {
int startIdx = lastSection.lastIndexOf('-');
int endIdx = lastSection.lastIndexOf('.');

startIdx = startIdx == -1 ? 0 : startIdx + 1;
if (endIdx == -1) {
endIdx = lastSection.length();
}
if (startIdx > endIdx) {
return Optional.empty();
}

containerId = lastSection.substring(startIdx, endIdx);
}

if (OtelEncodingUtils.isValidBase16String(containerId) && !containerId.isEmpty()) {
return Optional.of(containerId);
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.resources;

import static java.util.Optional.empty;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Utility for extracting the container ID from runtimes inside cgroup v2 containers. */
class CgroupV2ContainerIdExtractor {

private static final Logger logger =
Logger.getLogger(CgroupV2ContainerIdExtractor.class.getName());

static final Path V2_CGROUP_PATH = Paths.get("/proc/self/mountinfo");
private static final Pattern CONTAINER_RE =
Pattern.compile(".*/docker/containers/([0-9a-f]{64})/.*");

private final ContainerResource.Filesystem filesystem;

CgroupV2ContainerIdExtractor() {
this(ContainerResource.FILESYSTEM_INSTANCE);
}

// Exists for testing
CgroupV2ContainerIdExtractor(ContainerResource.Filesystem filesystem) {
this.filesystem = filesystem;
}

Optional<String> extractContainerId() {
if (!filesystem.isReadable(V2_CGROUP_PATH)) {
return empty();
}
try {
return filesystem
.lines(V2_CGROUP_PATH)
.map(CONTAINER_RE::matcher)
.filter(Matcher::matches)
.findFirst()
.map(matcher -> matcher.group(1));
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to read v2 cgroup path", e);
}
return empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,106 +5,75 @@

package io.opentelemetry.instrumentation.resources;

import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.CONTAINER_ID;

import com.google.errorprone.annotations.MustBeClosed;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.internal.OtelEncodingUtils;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

/** Factory for {@link Resource} retrieving Container ID information. */
/**
* Factory for {@link Resource} retrieving Container ID information. It supports both cgroup v1 and
* v2 runtimes.
*/
public final class ContainerResource {

private static final Logger logger = Logger.getLogger(ContainerResource.class.getName());
private static final String UNIQUE_HOST_NAME_FILE_NAME = "/proc/self/cgroup";
private static final Resource INSTANCE = buildSingleton(UNIQUE_HOST_NAME_FILE_NAME);
static final Filesystem FILESYSTEM_INSTANCE = new Filesystem();
private static final Resource INSTANCE = buildSingleton();

private static Resource buildSingleton(String uniqueHostNameFileName) {
private static Resource buildSingleton() {
// can't initialize this statically without running afoul of animalSniffer on paths
return buildResource(Paths.get(uniqueHostNameFileName));
return new ContainerResource().buildResource();
}

// package private for testing
static Resource buildResource(Path path) {
return extractContainerId(path)
.map(
containerId ->
Resource.create(Attributes.of(ResourceAttributes.CONTAINER_ID, containerId)))
.orElseGet(Resource::empty);
private final CgroupV1ContainerIdExtractor v1Extractor;
private final CgroupV2ContainerIdExtractor v2Extractor;

private ContainerResource() {
this(new CgroupV1ContainerIdExtractor(), new CgroupV2ContainerIdExtractor());
}

/** Returns resource with container information. */
public static Resource get() {
return INSTANCE;
// Visible for testing
ContainerResource(
CgroupV1ContainerIdExtractor v1Extractor, CgroupV2ContainerIdExtractor v2Extractor) {
this.v1Extractor = v1Extractor;
this.v2Extractor = v2Extractor;
}

/**
* Each line of cgroup file looks like "14:name=systemd:/docker/.../... A hex string is expected
* inside the last section separated by '/' Each segment of the '/' can contain metadata separated
* by either '.' (at beginning) or '-' (at end)
*
* @return containerId
*/
private static Optional<String> extractContainerId(Path cgroupFilePath) {
if (!Files.exists(cgroupFilePath) || !Files.isReadable(cgroupFilePath)) {
return Optional.empty();
}
try (Stream<String> lines = Files.lines(cgroupFilePath)) {
return lines
.filter(line -> !line.isEmpty())
.map(ContainerResource::getIdFromLine)
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read file", e);
}
return Optional.empty();
// Visible for testing
Resource buildResource() {
return getContainerId()
.map(id -> Resource.create(Attributes.of(CONTAINER_ID, id)))
.orElseGet(Resource::empty);
}

private static Optional<String> getIdFromLine(String line) {
// This cgroup output line should have the container id in it
int lastSlashIdx = line.lastIndexOf('/');
if (lastSlashIdx < 0) {
return Optional.empty();
private Optional<String> getContainerId() {
Optional<String> v1Result = v1Extractor.extractContainerId();
if (v1Result.isPresent()) {
return v1Result;
}
return v2Extractor.extractContainerId();
}

String containerId;

String lastSection = line.substring(lastSlashIdx + 1);
int colonIdx = lastSection.lastIndexOf(':');

if (colonIdx != -1) {
// since containerd v1.5.0+, containerId is divided by the last colon when the cgroupDriver is
// systemd:
// https://github.com/containerd/containerd/blob/release/1.5/pkg/cri/server/helpers_linux.go#L64
containerId = lastSection.substring(colonIdx + 1);
} else {
int startIdx = lastSection.lastIndexOf('-');
int endIdx = lastSection.lastIndexOf('.');
/** Returns resource with container information. */
public static Resource get() {
return INSTANCE;
}

startIdx = startIdx == -1 ? 0 : startIdx + 1;
if (endIdx == -1) {
endIdx = lastSection.length();
}
if (startIdx > endIdx) {
return Optional.empty();
}
// Exists for testing
static class Filesystem {

containerId = lastSection.substring(startIdx, endIdx);
boolean isReadable(Path path) {
return Files.isReadable(path);
}

if (OtelEncodingUtils.isValidBase16String(containerId) && !containerId.isEmpty()) {
return Optional.of(containerId);
} else {
return Optional.empty();
@MustBeClosed
Stream<String> lines(Path path) throws IOException {
return Files.lines(path);
}
}

private ContainerResource() {}
}
Loading