Skip to content
Open
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
Expand Up @@ -4,12 +4,14 @@
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
* agent and invoked. It is up to the provider of a DiagnosticEngine to provide a service loader for
* this interface.
*/
public interface DiagnosticEngineFactory {
DiagnosticEngine create(ScheduledExecutorService executorService);
DiagnosticEngine create(
ScheduledExecutorService executorService, @Nullable String cgroupBasePath);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -70,13 +74,13 @@ public Future<DiagnosisResult<?>> 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);

Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface CGroupDataReader {
long getCpuLimit() throws OperatingSystemInteractionException;

long getCpuPeriod() throws OperatingSystemInteractionException;

boolean isAvailable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@
public interface CGroupUsageDataReader extends TwoStepUpdatable, Closeable {
@Nullable
List<Double> getTelemetry();

boolean isAvailable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> lines = Files.readAllLines(file.toPath(), Charset.defaultCharset());
if (lines.size() > 0) {
List<String> 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);
Expand Down
Loading