From 3dbf4bf0ae1e777ff4c1247fae5a9b9d44d8a16f Mon Sep 17 00:00:00 2001 From: John Oliver <1615532+johnoliver@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:14:52 +0000 Subject: [PATCH] Add cgroupv2 support. Add support for modifying where cgroups is mounted to. Fix bug with reading of the body of profile upload responses, early closing of the response prevented the body from being read --- .../diagnostics/DiagnosticEngineFactory.java | 4 +- ...OptimizerApplicationInsightFactoryJfr.java | 49 ++++++- .../CodeOptimizerDiagnosticEngineJfr.java | 26 ++-- .../libos/kernel/CGroupDataReader.java | 2 + .../libos/kernel/CGroupUsageDataReader.java | 2 + .../linux/cgroups/CGroupCpuSystemReader.java | 9 +- .../linux/cgroups/CGroupCpuUsageReader.java | 9 +- .../os/linux/cgroups/CGroupCpuUserReader.java | 9 +- .../os/linux/cgroups/CGroupStatReader.java | 9 +- .../os/linux/cgroups/CGroupValueReader.java | 9 +- .../linux/cgroups/LinuxCGroupDataReader.java | 64 ++++++---- .../cgroups/LinuxCGroupUsageDataReader.java | 30 ++++- .../os/linux/cgroupsv2/CGroupv2CpuReader.java | 52 ++++++++ .../cgroupsv2/LinuxCGroupV2DataReader.java | 120 ++++++++++++++++++ .../LinuxCGroupV2UsageDataReader.java | 63 +++++++++ .../libos/os/nop/NoOpCGroupDataReader.java | 5 + .../os/nop/NoOpCGroupUsageDataReader.java | 5 + .../jfr/CodeOptimizerDiagnosticsJfrInit.java | 30 +++-- .../diagnostics/jfr/SystemStatsProvider.java | 60 ++++++--- .../internal/configuration/Configuration.java | 1 + .../PerformanceMonitoringService.java | 3 +- .../service/ServiceProfilerClient.java | 28 ++-- .../MockDiagnosticEngineFactory.java | 2 +- 23 files changed, 488 insertions(+), 103 deletions(-) create mode 100644 agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/CGroupv2CpuReader.java create mode 100644 agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2DataReader.java create mode 100644 agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2UsageDataReader.java diff --git a/agent/agent-profiler/agent-diagnostics-api/src/main/java/com/microsoft/applicationinsights/diagnostics/DiagnosticEngineFactory.java b/agent/agent-profiler/agent-diagnostics-api/src/main/java/com/microsoft/applicationinsights/diagnostics/DiagnosticEngineFactory.java index 22468d866c7..cd9402ab2a0 100644 --- a/agent/agent-profiler/agent-diagnostics-api/src/main/java/com/microsoft/applicationinsights/diagnostics/DiagnosticEngineFactory.java +++ b/agent/agent-profiler/agent-diagnostics-api/src/main/java/com/microsoft/applicationinsights/diagnostics/DiagnosticEngineFactory.java @@ -4,6 +4,7 @@ package com.microsoft.applicationinsights.diagnostics; import java.util.concurrent.ScheduledExecutorService; +import javax.annotation.Nullable; /** * Factory to be invoked to create a DiagnosticEngine. This factory will be service loaded by the @@ -11,5 +12,6 @@ * this interface. */ public interface DiagnosticEngineFactory { - DiagnosticEngine create(ScheduledExecutorService executorService); + DiagnosticEngine create( + ScheduledExecutorService executorService, @Nullable String cgroupBasePath); } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerApplicationInsightFactoryJfr.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerApplicationInsightFactoryJfr.java index ac7533a2703..b237b9ef862 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerApplicationInsightFactoryJfr.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerApplicationInsightFactoryJfr.java @@ -6,13 +6,58 @@ import com.google.auto.service.AutoService; import com.microsoft.applicationinsights.diagnostics.DiagnosticEngine; import com.microsoft.applicationinsights.diagnostics.DiagnosticEngineFactory; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.concurrent.ScheduledExecutorService; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Factory for Code Optimizer diagnostics to be service loaded */ @AutoService(DiagnosticEngineFactory.class) public class CodeOptimizerApplicationInsightFactoryJfr implements DiagnosticEngineFactory { + + private static final Path FILE_SYSTEM_ROOT = + Paths.get(System.getProperty("applicationinsights.profiler.filesystemRoot", "/")); + private static final Path CGROUP_DIR = Paths.get("./sys/fs/cgroup"); + + private static final Logger logger = + LoggerFactory.getLogger(CodeOptimizerApplicationInsightFactoryJfr.class); + @Override - public DiagnosticEngine create(ScheduledExecutorService executorService) { - return new CodeOptimizerDiagnosticEngineJfr(executorService); + public DiagnosticEngine create( + ScheduledExecutorService executorService, @Nullable String cgroupBasePath) { + Path cgroupPath = getCgroupPath(cgroupBasePath); + return new CodeOptimizerDiagnosticEngineJfr(executorService, cgroupPath); + } + + @SuppressFBWarnings( + value = "SECPTI", // Potential Path Traversal + justification = + "The constructed file path cannot be controlled by an end user of the instrumented application") + @Nullable + private static Path getCgroupPath(@Nullable String cgroupBasePath) { + Path cgroupPath = null; + if (cgroupBasePath != null) { + cgroupPath = Paths.get(cgroupBasePath); + + if (!Files.exists(cgroupPath)) { + logger.warn("Configured Cgroup path {} does not exist, setting to default", cgroupBasePath); + cgroupPath = null; + } + } + + if (cgroupPath == null) { + cgroupPath = FILE_SYSTEM_ROOT.resolve(CGROUP_DIR); + + if (!Files.exists(cgroupPath)) { + logger.warn("Expected default Cgroup path {} does not exist", cgroupBasePath); + cgroupPath = null; + } + } + + return cgroupPath; } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerDiagnosticEngineJfr.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerDiagnosticEngineJfr.java index 8334272398a..de01d5d6901 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerDiagnosticEngineJfr.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/appinsights/CodeOptimizerDiagnosticEngineJfr.java @@ -14,6 +14,7 @@ import com.microsoft.applicationinsights.diagnostics.jfr.SystemStatsProvider; import java.io.IOException; import java.io.StringWriter; +import java.nio.file.Path; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -33,10 +34,13 @@ public class CodeOptimizerDiagnosticEngineJfr implements DiagnosticEngine { public static final long TIME_BEFORE_END_OF_PROFILE_TO_EMIT_EVENT = 10L; private final ScheduledExecutorService executorService; private final Semaphore semaphore = new Semaphore(1, false); + private final Path cgroupBasePath; private int thisPid; - public CodeOptimizerDiagnosticEngineJfr(ScheduledExecutorService executorService) { + public CodeOptimizerDiagnosticEngineJfr( + ScheduledExecutorService executorService, Path cgroupBasePath) { this.executorService = executorService; + this.cgroupBasePath = cgroupBasePath; } @Override @@ -49,14 +53,14 @@ public void init(int thisPid) { this.thisPid = thisPid; logger.debug("Initialising Code Optimizer Diagnostic Engine"); - CodeOptimizerDiagnosticsJfrInit.initFeature(thisPid); + CodeOptimizerDiagnosticsJfrInit.initFeature(thisPid, cgroupBasePath); logger.debug("Code Optimizer Diagnostic Engine Initialised"); } - private static void startDiagnosticCycle(int thisPid) { + private static void startDiagnosticCycle(int thisPid, Path cgroupBasePath) { logger.debug("Starting Code Optimizer Diagnostic Cycle"); - CodeOptimizerDiagnosticsJfrInit.initFeature(thisPid); - CodeOptimizerDiagnosticsJfrInit.start(thisPid); + CodeOptimizerDiagnosticsJfrInit.initFeature(thisPid, cgroupBasePath); + CodeOptimizerDiagnosticsJfrInit.start(thisPid, cgroupBasePath); } private static void endDiagnosticCycle() { @@ -70,13 +74,13 @@ public Future> performDiagnosis(AlertBreach alert) { new CompletableFuture<>(); try { if (semaphore.tryAcquire(SEMAPHORE_TIMEOUT_IN_SEC, TimeUnit.SECONDS)) { - emitInfo(alert); + emitInfo(alert, cgroupBasePath); long profileDurationInSec = alert.getAlertConfiguration().getProfileDurationSeconds(); long end = profileDurationInSec - TIME_BEFORE_END_OF_PROFILE_TO_EMIT_EVENT; - startDiagnosticCycle(thisPid); + startDiagnosticCycle(thisPid, cgroupBasePath); scheduleEmittingAlertBreachEvent(alert, end); @@ -101,7 +105,7 @@ private void scheduleShutdown( executorService.schedule( () -> { try { - emitInfo(alert); + emitInfo(alert, cgroupBasePath); // We do not return a result atm diagnosisResultCompletableFuture.complete(null); @@ -123,7 +127,7 @@ private void scheduleEmittingAlertBreachEvent(AlertBreach alert, long end) { executorService.schedule( () -> { try { - emitInfo(alert); + emitInfo(alert, cgroupBasePath); } catch (RuntimeException e) { logger.error("Failed to emit breach", e); } @@ -132,10 +136,10 @@ private void scheduleEmittingAlertBreachEvent(AlertBreach alert, long end) { TimeUnit.SECONDS); } - private static void emitInfo(AlertBreach alert) { + private static void emitInfo(AlertBreach alert, Path cgroupBasePath) { logger.debug("Emitting Code Optimizer Diagnostic Event"); emitAlertBreachJfrEvent(alert); - CodeOptimizerDiagnosticsJfrInit.emitCGroupData(); + CodeOptimizerDiagnosticsJfrInit.emitCGroupData(cgroupBasePath); emitMachineStats(); } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupDataReader.java index 212190bb792..5af595f2382 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupDataReader.java @@ -19,4 +19,6 @@ public interface CGroupDataReader { long getCpuLimit() throws OperatingSystemInteractionException; long getCpuPeriod() throws OperatingSystemInteractionException; + + boolean isAvailable(); } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupUsageDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupUsageDataReader.java index f13463ac2a5..3a4217423c6 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupUsageDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/kernel/CGroupUsageDataReader.java @@ -13,4 +13,6 @@ public interface CGroupUsageDataReader extends TwoStepUpdatable, Closeable { @Nullable List getTelemetry(); + + boolean isAvailable(); } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuSystemReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuSystemReader.java index ecb14207ce6..6ea67dbcf23 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuSystemReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuSystemReader.java @@ -3,11 +3,12 @@ package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +import java.nio.file.Path; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class CGroupCpuSystemReader extends CGroupValueReader { // total system CPU time (in nanoseconds) consumed by all tasks in this cgroup - public CGroupCpuSystemReader() { - super("/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage_sys"); + public CGroupCpuSystemReader(Path cgroupPath) { + super(cgroupPath.resolve("./cpuacct.usage_sys")); } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUsageReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUsageReader.java index f642e04872d..55770fd50cb 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUsageReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUsageReader.java @@ -3,11 +3,12 @@ package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +import java.nio.file.Path; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class CGroupCpuUsageReader extends CGroupValueReader { // total CPU usage (in nanoseconds) consumed by all tasks in this cgroup - public CGroupCpuUsageReader() { - super("/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"); + public CGroupCpuUsageReader(Path cgroupPath) { + super(cgroupPath.resolve("./cpuacct.usage")); } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUserReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUserReader.java index c797dcdeefe..fd151073313 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUserReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupCpuUserReader.java @@ -3,11 +3,12 @@ package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +import java.nio.file.Path; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class CGroupCpuUserReader extends CGroupValueReader { // total user CPU time (in nanoseconds) consumed by all tasks in this cgroup - public CGroupCpuUserReader() { - super("/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage_user"); + public CGroupCpuUserReader(Path cgroupPath) { + super(cgroupPath.resolve("./cpuacct.usage_user")); } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupStatReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupStatReader.java index 9bd25f426ba..dfec08eb909 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupStatReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupStatReader.java @@ -5,16 +5,15 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.BigIncrementalCounter; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.TwoStepProcReader; -import java.io.File; +import java.nio.file.Path; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class CGroupStatReader extends TwoStepProcReader { private final BigIncrementalCounter user = new BigIncrementalCounter(); private final BigIncrementalCounter system = new BigIncrementalCounter(); - public CGroupStatReader() { - super(new File("/sys/fs/cgroup/cpu,cpuacct/cpuacct.stat"), true); + public CGroupStatReader(Path cgroupPath) { + super(cgroupPath.resolve("./cpuacct.stat").toFile(), true); } @Override diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupValueReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupValueReader.java index 4b076f9b3b6..bb1c23455ad 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupValueReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/CGroupValueReader.java @@ -5,15 +5,14 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.BigIncrementalCounter; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.TwoStepProcReader; -import java.io.File; +import java.nio.file.Path; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public abstract class CGroupValueReader extends TwoStepProcReader { private final BigIncrementalCounter usage = new BigIncrementalCounter(); - public CGroupValueReader(String fileName) { - super(new File(fileName), true); + public CGroupValueReader(Path file) { + super(file.toFile(), true); } @Override diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupDataReader.java index 0f9782bc13f..dbef2cf80b4 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupDataReader.java @@ -5,60 +5,80 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.OperatingSystemInteractionException; import com.microsoft.applicationinsights.diagnostics.collection.libos.kernel.CGroupDataReader; -import java.io.File; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class LinuxCGroupDataReader implements CGroupDataReader { - private static final String CGROUP_DIR = "/sys/fs/cgroup"; - private static final String K_MEM_LIMIT_FILE = CGROUP_DIR + "/memory/memory.kmem.limit_in_bytes"; - private static final String MEM_LIMIT_FILE = CGROUP_DIR + "/memory/memory.limit_in_bytes"; - private static final String MEM_SOFT_LIMIT_FILE = - CGROUP_DIR + "/memory/memory.soft_limit_in_bytes"; - private static final String CPU_LIMIT_FILE = CGROUP_DIR + "/cpu,cpuacct/cpu.cfs_quota_us"; - private static final String CPU_PERIOD_FILE = CGROUP_DIR + "/cpu,cpuacct/cpu.cfs_period_us"; + private static final String K_MEM_LIMIT_FILE = "./memory/memory.kmem.limit_in_bytes"; + private static final String MEM_LIMIT_FILE = "./memory/memory.limit_in_bytes"; + private static final String MEM_SOFT_LIMIT_FILE = "./memory/memory.soft_limit_in_bytes"; + private static final String CPU_LIMIT_FILE = "./cpu,cpuacct/cpu.cfs_quota_us"; + private static final String CPU_PERIOD_FILE = "./cpu,cpuacct/cpu.cfs_period_us"; + + private final Path kmemLimitFile; + private final Path memLimitFile; + private final Path memSoftLimitFile; + private final Path cpuLimitFile; + private final Path cpuPeriodFile; + + public LinuxCGroupDataReader(Path cgroupRoot) { + kmemLimitFile = cgroupRoot.resolve(K_MEM_LIMIT_FILE); + memLimitFile = cgroupRoot.resolve(MEM_LIMIT_FILE); + memSoftLimitFile = cgroupRoot.resolve(MEM_SOFT_LIMIT_FILE); + cpuLimitFile = cgroupRoot.resolve(CPU_LIMIT_FILE); + cpuPeriodFile = cgroupRoot.resolve(CPU_PERIOD_FILE); + } @Override public long getKmemLimit() throws OperatingSystemInteractionException { - return readLong(K_MEM_LIMIT_FILE); + return readLong(kmemLimitFile); } @Override public long getMemoryLimit() throws OperatingSystemInteractionException { - return readLong(MEM_LIMIT_FILE); + return readLong(memLimitFile); } @Override public long getMemorySoftLimit() throws OperatingSystemInteractionException { - return readLong(MEM_SOFT_LIMIT_FILE); + return readLong(memSoftLimitFile); } @Override public long getCpuLimit() throws OperatingSystemInteractionException { - return readLong(CPU_LIMIT_FILE); + return readLong(cpuLimitFile); } @Override public long getCpuPeriod() throws OperatingSystemInteractionException { - return readLong(CPU_PERIOD_FILE); + return readLong(cpuPeriodFile); + } + + @Override + public boolean isAvailable() { + return Files.exists(cpuLimitFile) + || Files.exists(memLimitFile) + || Files.exists(memSoftLimitFile) + || Files.exists(kmemLimitFile) + || Files.exists(cpuPeriodFile); } - private static long readLong(String fileName) throws OperatingSystemInteractionException { + private static long readLong(Path file) throws OperatingSystemInteractionException { try { - File file = new File(fileName); - if (!file.exists() || !file.isFile()) { - throw new OperatingSystemInteractionException("File does not exist: " + fileName); + if (!Files.exists(file) || !Files.isRegularFile(file)) { + throw new OperatingSystemInteractionException("File does not exist: " + file.getFileName()); } - List lines = Files.readAllLines(file.toPath(), Charset.defaultCharset()); - if (lines.size() > 0) { + List lines = Files.readAllLines(file, Charset.defaultCharset()); + if (!lines.isEmpty()) { return Long.parseLong(lines.get(0)); } else { - throw new OperatingSystemInteractionException("Unable to read value from: " + fileName); + throw new OperatingSystemInteractionException( + "Unable to read value from: " + file.getFileName()); } } catch (Exception e) { throw new OperatingSystemInteractionException(e); diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupUsageDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupUsageDataReader.java index ae643aac343..fe511144dbd 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupUsageDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroups/LinuxCGroupUsageDataReader.java @@ -5,21 +5,34 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.kernel.CGroupUsageDataReader; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -@SuppressWarnings( - "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control Group +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class LinuxCGroupUsageDataReader implements CGroupUsageDataReader { + private static final Path CGROUP_CPU_PATH = Paths.get("./cpu,cpuacct/"); - private final CGroupCpuUsageReader cgroupCpuUsageReader = new CGroupCpuUsageReader(); + private final CGroupCpuUsageReader cgroupCpuUsageReader; - private final CGroupCpuUserReader cgroupCpuUserReader = new CGroupCpuUserReader(); + private final CGroupCpuUserReader cgroupCpuUserReader; - private final CGroupCpuSystemReader cgroupCpuSystemReader = new CGroupCpuSystemReader(); + private final CGroupCpuSystemReader cgroupCpuSystemReader; - private final CGroupStatReader cgroupStatReader = new CGroupStatReader(); + private final CGroupStatReader cgroupStatReader; + + private final Path cgroupDirectory; + + public LinuxCGroupUsageDataReader(Path cgroupDirectory) { + this.cgroupDirectory = cgroupDirectory.resolve(CGROUP_CPU_PATH); + cgroupCpuUsageReader = new CGroupCpuUsageReader(this.cgroupDirectory); + cgroupCpuUserReader = new CGroupCpuUserReader(this.cgroupDirectory); + cgroupCpuSystemReader = new CGroupCpuSystemReader(this.cgroupDirectory); + cgroupStatReader = new CGroupStatReader(this.cgroupDirectory); + } @Override public void poll() { @@ -56,6 +69,11 @@ public List getTelemetry() { .collect(Collectors.toList()); } + @Override + public boolean isAvailable() { + return Files.exists(cgroupDirectory); + } + @Override public void close() throws IOException { cgroupCpuUsageReader.close(); diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/CGroupv2CpuReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/CGroupv2CpuReader.java new file mode 100644 index 00000000000..95163743346 --- /dev/null +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/CGroupv2CpuReader.java @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2; + +import com.microsoft.applicationinsights.diagnostics.collection.libos.BigIncrementalCounter; +import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.TwoStepProcReader; +import java.nio.file.Path; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class CGroupv2CpuReader extends TwoStepProcReader { + + private static final String CPU_USAGE_PROPERTY = "usage_usec"; + private static final String CPU_SYSTEM_PROPERTY = "system_usec"; + private static final String CPU_USER_PROPERTY = "user_usec"; + + private final BigIncrementalCounter cpuUsage = new BigIncrementalCounter(); + private final BigIncrementalCounter cpuSystem = new BigIncrementalCounter(); + private final BigIncrementalCounter cpuUser = new BigIncrementalCounter(); + + // total CPU usage (in microseconds) consumed by all tasks in this cgroup + public CGroupv2CpuReader(Path cgroupDir) { + super(cgroupDir.resolve("./cpu.stat").toFile()); + } + + @Override + protected void parseLine(String line) { + String[] tokens = line.split(" "); + + if (tokens.length == 2) { + if (CPU_USAGE_PROPERTY.equals(tokens[0])) { + cpuUsage.newValue(Long.parseLong(tokens[1])); + } else if (CPU_SYSTEM_PROPERTY.equals(tokens[0])) { + cpuSystem.newValue(Long.parseLong(tokens[1])); + } else if (CPU_USER_PROPERTY.equals(tokens[0])) { + cpuUser.newValue(Long.parseLong(tokens[1])); + } + } + } + + public BigIncrementalCounter getCpuUsage() { + return cpuUsage; + } + + public BigIncrementalCounter getCpuSystem() { + return cpuSystem; + } + + public BigIncrementalCounter getCpuUser() { + return cpuUser; + } +} diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2DataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2DataReader.java new file mode 100644 index 00000000000..b199ba7c014 --- /dev/null +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2DataReader.java @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2; + +import com.microsoft.applicationinsights.diagnostics.collection.libos.OperatingSystemInteractionException; +import com.microsoft.applicationinsights.diagnostics.collection.libos.kernel.CGroupDataReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class LinuxCGroupV2DataReader implements CGroupDataReader { + private static final Path MEM_MAX_FILE = Paths.get("./memory.max"); + private static final Path MEM_HIGH_FILE = Paths.get("./memory.high"); + private static final Path CPU_MAX_FILE = Paths.get("./cpu.max"); + + private final Path memMaxFile; + private final Path memHighFile; + private final Path cpuMaxFile; + + public LinuxCGroupV2DataReader(Path cgroupDir) { + memMaxFile = cgroupDir.resolve(MEM_MAX_FILE); + memHighFile = cgroupDir.resolve(MEM_HIGH_FILE); + cpuMaxFile = cgroupDir.resolve(CPU_MAX_FILE); + } + + @Override + public long getKmemLimit() { + // In cgroup v2, kernel memory accounting is not separately exposed + // Return a large value to indicate no specific limit + return Long.MAX_VALUE; + } + + @Override + public long getMemoryLimit() { + return parseMemoryValue(memMaxFile); + } + + @Override + public long getMemorySoftLimit() { + return parseMemoryValue(memHighFile); + } + + @Override + public long getCpuLimit() { + return parseCpuQuota(); + } + + @Override + public long getCpuPeriod() { + return parseCpuPeriod(); + } + + @Override + public boolean isAvailable() { + return Files.exists(memMaxFile) || Files.exists(memHighFile) || Files.exists(cpuMaxFile); + } + + private static long parseMemoryValue(Path file) { + try { + String content = readFileContent(file); + if ("max".equalsIgnoreCase(content.trim())) { + return Long.MAX_VALUE; + } + return Long.parseLong(content.trim()); + } catch (Exception e) { + return Long.MAX_VALUE; + } + } + + private long parseCpuQuota() { + try { + String content = readFileContent(cpuMaxFile); + String[] parts = content.trim().split("\\s+"); + if (parts.length >= 1) { + if ("max".equalsIgnoreCase(parts[0])) { + return -1; // No quota defined + } + return Long.parseLong(parts[0]); + } + } catch (Exception ignored) { + return -1; // No quota defined + } + return -1; // No quota defined + } + + private long parseCpuPeriod() { + try { + String content = readFileContent(cpuMaxFile); + String[] parts = content.trim().split("\\s+"); + if (parts.length >= 2) { + return Long.parseLong(parts[1]); + } + } catch (Exception ignored) { + return -1; // No quota defined + } + return -1; // No period defined + } + + private static String readFileContent(Path file) throws OperatingSystemInteractionException { + try { + if (!Files.exists(file) || !Files.isRegularFile(file)) { + throw new OperatingSystemInteractionException( + "File does not exist: " + file.getFileName().toString()); + } + + List lines = Files.readAllLines(file, Charset.defaultCharset()); + if (lines.isEmpty()) { + throw new OperatingSystemInteractionException( + "Empty file: " + file.getFileName().toString()); + } + return lines.get(0); + } catch (Exception e) { + throw new OperatingSystemInteractionException(e); + } + } +} diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2UsageDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2UsageDataReader.java new file mode 100644 index 00000000000..73b72b9c73c --- /dev/null +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/linux/cgroupsv2/LinuxCGroupV2UsageDataReader.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2; + +import com.microsoft.applicationinsights.diagnostics.collection.libos.kernel.CGroupUsageDataReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class LinuxCGroupV2UsageDataReader implements CGroupUsageDataReader { + + private final CGroupv2CpuReader cgroupV2CpuReader; + private final Path cgroupDir; + + public LinuxCGroupV2UsageDataReader(Path cgroupDir) { + this.cgroupDir = cgroupDir; + cgroupV2CpuReader = new CGroupv2CpuReader(cgroupDir); + } + + @Override + public void poll() { + cgroupV2CpuReader.poll(); + } + + @Override + public void update() { + cgroupV2CpuReader.update(); + } + + @Override + public List getTelemetry() { + return Stream.of( + cgroupV2CpuReader.getCpuUsage().getIncrement(), + cgroupV2CpuReader.getCpuUser().getIncrement(), + cgroupV2CpuReader.getCpuSystem().getIncrement(), + cgroupV2CpuReader.getCpuUser().getIncrement(), + cgroupV2CpuReader.getCpuSystem().getIncrement()) + .map( + value -> { + if (value == null) { + return -1.0d; + } else { + return value.doubleValue(); + } + }) + .collect(Collectors.toList()); + } + + @Override + public boolean isAvailable() { + return Files.exists(cgroupDir.resolve("./cgroup.controllers")); + } + + @Override + public void close() throws IOException { + cgroupV2CpuReader.close(); + } +} diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupDataReader.java index e0304030e46..97561e0d682 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupDataReader.java @@ -35,4 +35,9 @@ public long getCpuLimit() { public long getCpuPeriod() { return -1; } + + @Override + public boolean isAvailable() { + return true; + } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupUsageDataReader.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupUsageDataReader.java index 794971337fe..9b1c90ff0e5 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupUsageDataReader.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/collection/libos/os/nop/NoOpCGroupUsageDataReader.java @@ -25,4 +25,9 @@ public void update() {} @Override public void close() throws IOException {} + + @Override + public boolean isAvailable() { + return true; + } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/CodeOptimizerDiagnosticsJfrInit.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/CodeOptimizerDiagnosticsJfrInit.java index d5007db4b29..55dbd815331 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/CodeOptimizerDiagnosticsJfrInit.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/CodeOptimizerDiagnosticsJfrInit.java @@ -7,6 +7,7 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.OperatingSystemInteractionException; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.OperatingSystemDetector; import java.io.IOException; +import java.nio.file.Path; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -26,8 +27,9 @@ public class CodeOptimizerDiagnosticsJfrInit { private static final AtomicInteger exceptionLogCount = new AtomicInteger(0); private static final AtomicInteger telemetryFailureLogCount = new AtomicInteger(0); - private static final Runnable readCGroupData = CodeOptimizerDiagnosticsJfrInit::emitCGroupData; private static final AtomicReference telemetryEmitter = new AtomicReference<>(null); + private static final AtomicReference cgroupTelemetryEmitter = + new AtomicReference<>(null); private CodeOptimizerDiagnosticsJfrInit() {} @@ -70,9 +72,9 @@ private static void logFailure(String logLine, @Nullable Exception e, AtomicInte @SuppressWarnings( "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control // Group - public static void emitCGroupData() { + public static void emitCGroupData(Path cgroupBasePath) { try { - CGroupData cgroupData = SystemStatsProvider.getCGroupData(); + CGroupData cgroupData = SystemStatsProvider.getCGroupData(cgroupBasePath); if (cgroupData != null) { cgroupData.commit(); @@ -86,22 +88,23 @@ public static boolean isOsSupported() { return OperatingSystemDetector.getOperatingSystem().supportsDiagnostics(); } - public static void initFeature(int thisPid) { + public static void initFeature(int thisPid, Path cgroupBasePath) { if (!isOsSupported()) { return; } // eagerly get stats to warm it up - SystemStatsProvider.init(thisPid); + SystemStatsProvider.init(thisPid, cgroupBasePath); } - public static void start(int thisPidSupplier) { + public static void start(int thisPidSupplier, Path cgroupBasePath) { if (!isOsSupported()) { return; } if (running.compareAndSet(false, true)) { - SystemStatsReader statsReader = SystemStatsProvider.getStatsReader(thisPidSupplier); + SystemStatsReader statsReader = + SystemStatsProvider.getStatsReader(thisPidSupplier, cgroupBasePath); Runnable emitter = emitTelemetry(statsReader); if (telemetryEmitter.compareAndSet(null, emitter)) { FlightRecorder.addPeriodicEvent(Telemetry.class, emitter); @@ -112,9 +115,12 @@ public static void start(int thisPidSupplier) { logger.error("Failed to init stats reader", e); } } - FlightRecorder.addPeriodicEvent(CGroupData.class, readCGroupData); - readCGroupData.run(); + if (cgroupTelemetryEmitter.compareAndSet(null, () -> emitCGroupData(cgroupBasePath))) { + FlightRecorder.addPeriodicEvent(CGroupData.class, cgroupTelemetryEmitter.get()); + } + + cgroupTelemetryEmitter.get().run(); } } @@ -128,7 +134,11 @@ public static void stop() { FlightRecorder.removePeriodicEvent(telemetryEmitter.get()); telemetryEmitter.set(null); } - FlightRecorder.removePeriodicEvent(readCGroupData); + + if (cgroupTelemetryEmitter.get() != null) { + FlightRecorder.removePeriodicEvent(cgroupTelemetryEmitter.get()); + cgroupTelemetryEmitter.set(null); + } SystemStatsProvider.close(); } } diff --git a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/SystemStatsProvider.java b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/SystemStatsProvider.java index 0455ce9dd2c..953ffd970f8 100644 --- a/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/SystemStatsProvider.java +++ b/agent/agent-profiler/agent-diagnostics/src/main/java/com/microsoft/applicationinsights/diagnostics/jfr/SystemStatsProvider.java @@ -20,6 +20,8 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.LinuxProcessDumper; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups.LinuxCGroupDataReader; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroups.LinuxCGroupUsageDataReader; +import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2.LinuxCGroupV2DataReader; +import com.microsoft.applicationinsights.diagnostics.collection.libos.os.linux.cgroupsv2.LinuxCGroupV2UsageDataReader; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.nop.NoOpCGroupDataReader; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.nop.NoOpCGroupUsageDataReader; import com.microsoft.applicationinsights.diagnostics.collection.libos.os.nop.NoOpKernelMonitor; @@ -30,6 +32,7 @@ import com.microsoft.applicationinsights.diagnostics.collection.libos.process.ThisPidSupplier; import java.io.Closeable; import java.io.IOException; +import java.nio.file.Path; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -53,7 +56,7 @@ public class SystemStatsProvider { private SystemStatsProvider() {} - public static void init(int thisPid) { + public static void init(int thisPid, Path cgroupBasePath) { // Ensure we only initialize once if (initialised.compareAndSet(false, true)) { singletons.put(ThisPidSupplier.class, new AtomicReference<>((ThisPidSupplier) () -> thisPid)); @@ -62,7 +65,7 @@ public static void init(int thisPid) { try { getCalibration(); getMachineStats(); - getCGroupData(); + getCGroupData(cgroupBasePath); // Close until needed close(); @@ -108,12 +111,12 @@ private static T getSingleton(Class clazz, Supplier supplier) { "checkstyle:AbbreviationAsWordInName", "MemberName" }) // CGroup is the standard abbreviation for Control Group - public static CGroupData getCGroupData() { + public static CGroupData getCGroupData(Path cgroupBasePath) { return getSingleton( CGroupData.class, () -> { try { - CGroupDataReader reader = buildCGroupDataReader(); + CGroupDataReader reader = buildCGroupDataReader(cgroupBasePath); CGroupData data = new CGroupData(); return data.setKmemLimit(reader.getKmemLimit()) @@ -169,10 +172,21 @@ private static Process getThisProcess() { @SuppressWarnings( "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control // Group - private static CGroupDataReader buildCGroupDataReader() { + private static CGroupDataReader buildCGroupDataReader(Path cgroupBasePath) { switch (OperatingSystemDetector.getOperatingSystem()) { case LINUX: - return new LinuxCGroupDataReader(); + CGroupDataReader dataReader = new LinuxCGroupDataReader(cgroupBasePath); + if (dataReader.isAvailable()) { + return dataReader; + } + + dataReader = new LinuxCGroupV2DataReader(cgroupBasePath); + if (dataReader.isAvailable()) { + return dataReader; + } + + logger.info("No CGroup limits data not found"); + return new NoOpCGroupDataReader(); default: return new NoOpCGroupDataReader(); } @@ -194,13 +208,29 @@ private static ProcessDumper getProcessDumper() { @SuppressWarnings( "checkstyle:AbbreviationAsWordInName") // CGroup is the standard abbreviation for Control // Group - private static CGroupUsageDataReader buildCGroupUsageDataReader() { + private static CGroupUsageDataReader buildCGroupUsageDataReader(Path cgroupBasePath) { return getSingleton( CGroupUsageDataReader.class, () -> { + if (cgroupBasePath == null) { + logger.info("No CGroup data present"); + return new NoOpCGroupUsageDataReader(); + } + switch (OperatingSystemDetector.getOperatingSystem()) { case LINUX: - return new LinuxCGroupUsageDataReader(); + CGroupUsageDataReader usageReader = new LinuxCGroupUsageDataReader(cgroupBasePath); + if (usageReader.isAvailable()) { + return usageReader; + } + + usageReader = new LinuxCGroupV2UsageDataReader(cgroupBasePath); + if (usageReader.isAvailable()) { + return usageReader; + } + + logger.warn("CGroup data not found"); + return new NoOpCGroupUsageDataReader(); default: return new NoOpCGroupUsageDataReader(); } @@ -220,15 +250,15 @@ private static MemoryInfoReader buildMemoryInfoReader() { }); } - private static SystemStatsReader getSystemStatsReader() { - return getSingleton(SystemStatsReader.class, SystemStatsProvider::buildSystemStatsReader); + private static SystemStatsReader getSystemStatsReader(Path cgroupBasePath) { + return getSingleton(SystemStatsReader.class, () -> buildSystemStatsReader(cgroupBasePath)); } - private static SystemStatsReader buildSystemStatsReader() { + private static SystemStatsReader buildSystemStatsReader(Path cgroupBasePath) { SystemStatsReader ssr = new SystemStatsReader( getKernelMonitor(), - buildCGroupUsageDataReader(), + buildCGroupUsageDataReader(cgroupBasePath), getThisProcess().getCpuStats(), getThisProcess().getIoStats(), buildMemoryInfoReader()); @@ -257,9 +287,9 @@ private static KernelMonitorDeviceDriver getKernelMonitor() { }); } - public static SystemStatsReader getStatsReader(int thisPidSupplier) { - init(thisPidSupplier); - return getSystemStatsReader(); + public static SystemStatsReader getStatsReader(int thisPidSupplier, Path cgroupBasePath) { + init(thisPidSupplier, cgroupBasePath); + return getSystemStatsReader(cgroupBasePath); } public static void close() { diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java index a39a66b454c..26005a97585 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java @@ -1530,6 +1530,7 @@ public static class ProfilerConfiguration { public boolean enableDiagnostics = false; public boolean enableRequestTriggering = false; public List requestTriggerEndpoints = new ArrayList<>(); + @Nullable public String cgroupPath = null; } public static class GcEventConfiguration { diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/PerformanceMonitoringService.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/PerformanceMonitoringService.java index a49d2f24d5f..8a4860ab594 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/PerformanceMonitoringService.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/PerformanceMonitoringService.java @@ -145,7 +145,8 @@ private DiagnosticEngine startDiagnosticEngine() { 1, ThreadPoolUtils.createNamedDaemonThreadFactory("DiagnosisThreadPool")); DiagnosticEngine diagnosticEngine = - diagnosticEngineFactory.create(diagnosticEngineExecutorService); + diagnosticEngineFactory.create( + diagnosticEngineExecutorService, configuration.cgroupPath); if (diagnosticEngine != null) { diagnosticEngine.init(Integer.parseInt(new PidFinder().getValue(System::getenv))); diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/service/ServiceProfilerClient.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/service/ServiceProfilerClient.java index b4388ab2d5b..829a165eaf4 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/service/ServiceProfilerClient.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/service/ServiceProfilerClient.java @@ -123,19 +123,23 @@ private static Mono reportUploadFinish(HttpResponse response) { // this shouldn't happen, the mono should complete with a response or a failure return Mono.error(new AssertionError("http response mono returned empty")); } - try { - int statusCode = response.getStatusCode(); - if (statusCode != 201 && statusCode != 202) { - logger.error("Trace upload failed: {}", statusCode); - return Mono.error(new AssertionError("http request failed")); - } - return response.getBodyAsString(); - } finally { - // need to consume the body or close the response, otherwise get netty ByteBuf leak warnings: - // io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before - // it's garbage-collected (see https://github.com/Azure/azure-sdk-for-java/issues/10467) - response.close(); + int statusCode = response.getStatusCode(); + if (statusCode != 201 && statusCode != 202) { + logger.error("Trace upload failed: {}", statusCode); + return Mono.error(new AssertionError("http request failed")); } + + return response + .getBodyAsString() + .doFinally( + done -> { + // need to consume the body or close the response, otherwise get netty ByteBuf leak + // warnings: + // io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before + // it's garbage-collected (see + // https://github.com/Azure/azure-sdk-for-java/issues/10467) + response.close(); + }); } /** Obtain current settings that have been configured within the UI. */ diff --git a/smoke-tests/apps/DiagnosticExtension/MockExtension/src/main/java/com/microsoft/applicationinsights/smoketestextension/MockDiagnosticEngineFactory.java b/smoke-tests/apps/DiagnosticExtension/MockExtension/src/main/java/com/microsoft/applicationinsights/smoketestextension/MockDiagnosticEngineFactory.java index e1114d2fb9e..c278cb61a38 100644 --- a/smoke-tests/apps/DiagnosticExtension/MockExtension/src/main/java/com/microsoft/applicationinsights/smoketestextension/MockDiagnosticEngineFactory.java +++ b/smoke-tests/apps/DiagnosticExtension/MockExtension/src/main/java/com/microsoft/applicationinsights/smoketestextension/MockDiagnosticEngineFactory.java @@ -13,7 +13,7 @@ public class MockDiagnosticEngineFactory implements DiagnosticEngineFactory { @Override - public DiagnosticEngine create(ScheduledExecutorService executorService) { + public DiagnosticEngine create(ScheduledExecutorService executorService, String cgroupBasePath) { return new DiagnosticEngine() { @Override