Skip to content

Commit d2479b5

Browse files
committed
Common temporary location manager for profiling product
1 parent f3f2b15 commit d2479b5

File tree

6 files changed

+343
-99
lines changed

6 files changed

+343
-99
lines changed

dd-java-agent/agent-profiling/profiling-controller-openjdk/src/main/java/com/datadog/profiling/controller/openjdk/OpenJdkController.java

Lines changed: 22 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import static com.datadog.profiling.controller.ProfilingSupport.*;
1919
import static com.datadog.profiling.controller.ProfilingSupport.isObjectCountParallelized;
2020
import static datadog.trace.api.Platform.isJavaVersionAtLeast;
21-
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DEBUG_CLEANUP_REPO;
22-
import static datadog.trace.api.config.ProfilingConfig.PROFILING_DEBUG_CLEANUP_REPO_DEFAULT;
2321
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED;
2422
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_ENABLED_DEFAULT;
2523
import static datadog.trace.api.config.ProfilingConfig.PROFILING_HEAP_HISTOGRAM_MODE;
@@ -33,6 +31,7 @@
3331
import com.datadog.profiling.controller.ConfigurationException;
3432
import com.datadog.profiling.controller.Controller;
3533
import com.datadog.profiling.controller.ControllerContext;
34+
import com.datadog.profiling.controller.TempLocationManager;
3635
import com.datadog.profiling.controller.jfr.JFRAccess;
3736
import com.datadog.profiling.controller.jfr.JfpUtils;
3837
import com.datadog.profiling.controller.openjdk.events.AvailableProcessorCoresEvent;
@@ -42,19 +41,13 @@
4241
import datadog.trace.bootstrap.config.provider.ConfigProvider;
4342
import datadog.trace.bootstrap.instrumentation.jfr.backpressure.BackpressureProfiling;
4443
import datadog.trace.bootstrap.instrumentation.jfr.exceptions.ExceptionProfiling;
45-
import datadog.trace.util.PidHelper;
4644
import de.thetaphi.forbiddenapis.SuppressForbidden;
4745
import java.io.IOException;
48-
import java.nio.file.FileVisitResult;
49-
import java.nio.file.FileVisitor;
5046
import java.nio.file.Files;
5147
import java.nio.file.Path;
52-
import java.nio.file.Paths;
53-
import java.nio.file.attribute.BasicFileAttributes;
5448
import java.time.Duration;
5549
import java.util.Collections;
5650
import java.util.Map;
57-
import java.util.Set;
5851
import org.slf4j.Logger;
5952
import org.slf4j.LoggerFactory;
6053

@@ -92,14 +85,8 @@ public OpenJdkController(final ConfigProvider configProvider)
9285
// configure the JFR stackdepth before we try to load any JFR classes
9386
int requestedStackDepth = getConfiguredStackDepth(configProvider);
9487
this.jfrStackDepthApplied = JFRAccess.instance().setStackDepth(requestedStackDepth);
95-
boolean shouldCleanupJfrRepository =
96-
configProvider.getBoolean(
97-
PROFILING_DEBUG_CLEANUP_REPO, PROFILING_DEBUG_CLEANUP_REPO_DEFAULT);
98-
String jfrRepositoryBase = null;
99-
if (shouldCleanupJfrRepository) {
100-
jfrRepositoryBase = getJfrRepositoryBase(configProvider);
101-
JFRAccess.instance().setBaseLocation(jfrRepositoryBase + "/pid_" + PidHelper.getPid());
102-
}
88+
String jfrRepositoryBase = getJfrRepositoryBase(configProvider);
89+
JFRAccess.instance().setBaseLocation(jfrRepositoryBase);
10390
// Make sure we can load JFR classes before declaring that we have successfully created
10491
// factory and can use it.
10592
Class.forName("jdk.jfr.Recording");
@@ -112,10 +99,6 @@ public OpenJdkController(final ConfigProvider configProvider)
11299
Map<String, String> recordingSettings;
113100

114101
try {
115-
if (shouldCleanupJfrRepository) {
116-
cleanupJfrRepositories(Paths.get(jfrRepositoryBase));
117-
}
118-
119102
recordingSettings =
120103
JfpUtils.readNamedJfpResource(
121104
ultraMinimal ? JfpUtils.SAFEPOINTS_JFP : JfpUtils.DEFAULT_JFP);
@@ -270,21 +253,27 @@ && isEventEnabled(recordingSettings, "jdk.NativeMethodSample")) {
270253
}
271254

272255
private static String getJfrRepositoryBase(ConfigProvider configProvider) {
273-
return configProvider.getString(
274-
ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE,
275-
ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE_DEFAULT);
276-
}
277-
278-
private static void cleanupJfrRepositories(Path repositoryBase) {
279-
try {
280-
Files.walkFileTree(repositoryBase, new JfrCleanupVisitor(repositoryBase));
281-
} catch (IOException e) {
282-
if (log.isDebugEnabled()) {
283-
log.warn("Unable to cleanup old JFR repositories", e);
284-
} else {
285-
log.warn("Unable to cleanup old JFR repositories");
256+
String legacy =
257+
configProvider.getString(
258+
ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE,
259+
ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE_DEFAULT);
260+
if (!legacy.equals(ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE_DEFAULT)) {
261+
log.warn(
262+
"The configuration key {} is deprecated. Please use {} instead.",
263+
ProfilingConfig.PROFILING_JFR_REPOSITORY_BASE,
264+
ProfilingConfig.PROFILING_TEMP_DIR);
265+
}
266+
Path repositoryPath = TempLocationManager.getInstance().getTempDir().resolve("jfr");
267+
if (!Files.exists(repositoryPath)) {
268+
try {
269+
Files.createDirectories(repositoryPath);
270+
} catch (IOException e) {
271+
log.error("Failed to create JFR repository directory: {}", repositoryPath, e);
272+
throw new IllegalStateException(
273+
"Failed to create JFR repository directory: " + repositoryPath, e);
286274
}
287275
}
276+
return repositoryPath.toString();
288277
}
289278

290279
int getMaxSize() {
@@ -331,58 +320,4 @@ private int getConfiguredStackDepth(ConfigProvider configProvider) {
331320
return configProvider.getInteger(
332321
ProfilingConfig.PROFILING_STACKDEPTH, ProfilingConfig.PROFILING_STACKDEPTH_DEFAULT);
333322
}
334-
335-
private static class JfrCleanupVisitor implements FileVisitor<Path> {
336-
private boolean shouldClean = false;
337-
338-
private final Path root;
339-
private final Set<String> pidSet = PidHelper.getJavaPids();
340-
341-
JfrCleanupVisitor(Path root) {
342-
this.root = root;
343-
}
344-
345-
@Override
346-
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
347-
throws IOException {
348-
if (dir.equals(root)) {
349-
return FileVisitResult.CONTINUE;
350-
}
351-
String fileName = dir.getFileName().toString();
352-
// the JFR repository directories are under <basedir>/pid_<pid>
353-
String pid = fileName.startsWith("pid_") ? fileName.substring(4) : null;
354-
shouldClean |= pid != null && !pidSet.contains(pid);
355-
if (shouldClean) {
356-
log.debug("Cleaning JFR repository under {}", dir);
357-
}
358-
return shouldClean ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
359-
}
360-
361-
@Override
362-
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
363-
if (file.toString().toLowerCase().endsWith(".jfr")) {
364-
Files.delete(file);
365-
}
366-
return FileVisitResult.CONTINUE;
367-
}
368-
369-
@Override
370-
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
371-
if (log.isDebugEnabled() && file.toString().toLowerCase().endsWith(".jfr")) {
372-
log.debug("Failed to delete file {}", file, exc);
373-
}
374-
return FileVisitResult.CONTINUE;
375-
}
376-
377-
@Override
378-
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
379-
if (shouldClean) {
380-
Files.delete(dir);
381-
String fileName = dir.getFileName().toString();
382-
// reset the flag only if we are done cleaning the top-level directory
383-
shouldClean = !fileName.startsWith("pid_");
384-
}
385-
return FileVisitResult.CONTINUE;
386-
}
387-
}
388323
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package com.datadog.profiling.controller;
2+
3+
import datadog.trace.api.config.ProfilingConfig;
4+
import datadog.trace.bootstrap.config.provider.ConfigProvider;
5+
import datadog.trace.util.PidHelper;
6+
import java.io.IOException;
7+
import java.nio.file.FileVisitResult;
8+
import java.nio.file.FileVisitor;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
import java.nio.file.attribute.BasicFileAttributes;
13+
import java.nio.file.attribute.PosixFilePermissions;
14+
import java.util.Set;
15+
import java.util.concurrent.CompletableFuture;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
/**
20+
* A manager class for temporary locations used by the profiling product. The temporary location is
21+
* keyed by the process ID and allows for cleanup of orphaned temporary files on startup by querying
22+
* the list of running Java processes and cleaning up any temporary locations that do not correspond
23+
* to a running Java process. Also, the temporary location is cleaned up on shutdown.
24+
*/
25+
public final class TempLocationManager {
26+
private static final Logger log = LoggerFactory.getLogger(TempLocationManager.class);
27+
28+
private static final class SingletonHolder {
29+
private static final TempLocationManager INSTANCE = new TempLocationManager();
30+
}
31+
32+
private class CleanupVisitor implements FileVisitor<Path> {
33+
private boolean shouldClean = false;
34+
35+
private final Set<String> pidSet = PidHelper.getJavaPids();
36+
37+
private final boolean cleanSelf;
38+
39+
CleanupVisitor(boolean cleanSelf) {
40+
this.cleanSelf = cleanSelf;
41+
}
42+
43+
@Override
44+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
45+
throws IOException {
46+
if (dir.equals(baseTempDir)) {
47+
return FileVisitResult.CONTINUE;
48+
}
49+
String fileName = dir.getFileName().toString();
50+
// the JFR repository directories are under <basedir>/pid_<pid>
51+
String pid = fileName.startsWith("pid_") ? fileName.substring(4) : null;
52+
boolean isSelfPid = pid != null && pid.equals(PidHelper.getPid());
53+
shouldClean |= (cleanSelf && isSelfPid) || (!cleanSelf && !pidSet.contains(pid));
54+
if (shouldClean) {
55+
log.debug("Cleaning temporary location {}", dir);
56+
}
57+
return shouldClean ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
58+
}
59+
60+
@Override
61+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
62+
Files.delete(file);
63+
return FileVisitResult.CONTINUE;
64+
}
65+
66+
@Override
67+
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
68+
if (log.isDebugEnabled()) {
69+
log.debug("Failed to delete file {}", file, exc);
70+
}
71+
return FileVisitResult.CONTINUE;
72+
}
73+
74+
@Override
75+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
76+
if (shouldClean) {
77+
Files.delete(dir);
78+
String fileName = dir.getFileName().toString();
79+
// reset the flag only if we are done cleaning the top-level directory
80+
shouldClean = !fileName.startsWith("pid_");
81+
}
82+
return FileVisitResult.CONTINUE;
83+
}
84+
}
85+
86+
private final Path baseTempDir;
87+
private final Path tempDir;
88+
89+
private final CompletableFuture<Void> cleanupTask;
90+
91+
/**
92+
* Get the singleton instance of the TempLocationManager. It will run the cleanup task in the
93+
* background.
94+
*
95+
* @return the singleton instance of the TempLocationManager
96+
*/
97+
public static TempLocationManager getInstance() {
98+
return getInstance(false);
99+
}
100+
101+
/**
102+
* Get the singleton instance of the TempLocationManager.
103+
*
104+
* @param waitForCleanup if true, wait for the cleanup task to finish before returning
105+
* @return the singleton instance of the TempLocationManager
106+
*/
107+
static TempLocationManager getInstance(boolean waitForCleanup) {
108+
TempLocationManager instance = SingletonHolder.INSTANCE;
109+
if (waitForCleanup) {
110+
instance.waitForCleanup();
111+
}
112+
return instance;
113+
}
114+
115+
private TempLocationManager() {
116+
this(ConfigProvider.getInstance());
117+
}
118+
119+
TempLocationManager(ConfigProvider configProvider) {
120+
Path configuredTempDir =
121+
Paths.get(
122+
configProvider.getString(
123+
ProfilingConfig.PROFILING_TEMP_DIR, ProfilingConfig.PROFILING_TEMP_DIR_DEFAULT));
124+
if (!Files.exists(configuredTempDir)) {
125+
log.warn(
126+
"Base temp directory, as defined in '"
127+
+ ProfilingConfig.PROFILING_TEMP_DIR
128+
+ "' does not exist: "
129+
+ configuredTempDir);
130+
throw new IllegalStateException(
131+
"Base temp directory, as defined in '"
132+
+ ProfilingConfig.PROFILING_TEMP_DIR
133+
+ "' does not exist: "
134+
+ configuredTempDir);
135+
}
136+
137+
String pid = PidHelper.getPid();
138+
139+
baseTempDir = configuredTempDir.resolve("ddprof");
140+
tempDir = baseTempDir.resolve("pid_" + pid);
141+
cleanupTask = CompletableFuture.runAsync(() -> cleanup(false));
142+
143+
Runtime.getRuntime()
144+
.addShutdownHook(
145+
new Thread(
146+
() -> {
147+
try {
148+
cleanupTask.join();
149+
} finally {
150+
cleanup(true);
151+
}
152+
},
153+
"Temp Location Manager Cleanup"));
154+
}
155+
156+
/**
157+
* Get the temporary directory for the current process.
158+
*
159+
* @return the temporary directory for the current process
160+
*/
161+
public Path getTempDir() {
162+
return getTempDir(true);
163+
}
164+
165+
/**
166+
* Get the temporary directory for the current process.
167+
*
168+
* @param create if true, create the directory if it does not exist
169+
* @return the temporary directory for the current process
170+
* @throws IllegalStateException if the directory could not be created
171+
*/
172+
public Path getTempDir(boolean create) {
173+
if (create && !Files.exists(tempDir)) {
174+
try {
175+
Files.createDirectories(
176+
tempDir,
177+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")));
178+
} catch (Exception e) {
179+
log.warn("Failed to create temp directory: {}", tempDir, e);
180+
throw new IllegalStateException("Failed to create temp directory: " + tempDir, e);
181+
}
182+
}
183+
return tempDir;
184+
}
185+
186+
void cleanup(boolean cleanSelf) {
187+
try {
188+
Files.walkFileTree(baseTempDir, new CleanupVisitor(cleanSelf));
189+
} catch (IOException e) {
190+
if (log.isDebugEnabled()) {
191+
log.warn("Unable to cleanup temp location {}", baseTempDir, e);
192+
} else {
193+
log.warn("Unable to cleanup temp location {}", baseTempDir);
194+
}
195+
}
196+
}
197+
198+
private void waitForCleanup() {
199+
cleanupTask.join();
200+
}
201+
}

0 commit comments

Comments
 (0)