Skip to content

Commit a008256

Browse files
authored
feat: Config to supply a path for the files usually created in /tmp.
1 parent 4e20b6a commit a008256

12 files changed

Lines changed: 859 additions & 21 deletions

agent/src/main/java/io/pyroscope/javaagent/AsyncProfilerDelegate.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
package io.pyroscope.javaagent;
22

3+
import io.pyroscope.PyroscopeAsyncProfiler;
34
import io.pyroscope.http.Format;
45
import io.pyroscope.javaagent.config.Config;
5-
import io.pyroscope.PyroscopeAsyncProfiler;
6+
import io.pyroscope.javaagent.util.TmpFileUtil;
67
import io.pyroscope.labels.v2.Pyroscope;
78
import one.profiler.AsyncProfiler;
89
import one.profiler.Counter;
910
import org.jetbrains.annotations.NotNull;
1011

1112
import java.io.DataInputStream;
1213
import java.io.File;
13-
import java.io.FileInputStream;
1414
import java.io.IOException;
1515
import java.nio.charset.StandardCharsets;
1616
import java.time.Duration;
1717
import java.time.Instant;
1818

19-
2019
import static io.pyroscope.Preconditions.checkNotNull;
20+
import static java.nio.file.Files.newInputStream;
2121

2222
public final class AsyncProfilerDelegate implements ProfilerDelegate {
2323
private static final String PROFILER_NOT_ACTIVE = "Profiler is not active";
@@ -48,8 +48,7 @@ public void setConfig(@NotNull final Config config) {
4848

4949
if (format == Format.JFR && null == tempJFRFile) {
5050
try {
51-
// flight recorder is built on top of a file descriptor, so we need a file.
52-
tempJFRFile = File.createTempFile("pyroscope", ".jfr");
51+
tempJFRFile = TmpFileUtil.createJfrFile(config);
5352
tempJFRFile.deleteOnExit();
5453
} catch (IOException e) {
5554
throw new IllegalStateException(e);
@@ -151,7 +150,7 @@ private Snapshot dumpImpl(Instant started, Instant ended) {
151150
private byte[] dumpJFR() {
152151
try {
153152
byte[] bytes = new byte[(int) tempJFRFile.length()];
154-
try (DataInputStream ds = new DataInputStream(new FileInputStream(tempJFRFile))) {
153+
try (DataInputStream ds = new DataInputStream(newInputStream(tempJFRFile.toPath()))) {
155154
ds.readFully(bytes);
156155
}
157156
return bytes;

agent/src/main/java/io/pyroscope/javaagent/BootstrapApiInjector.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package io.pyroscope.javaagent;
22

33
import io.pyroscope.javaagent.api.Logger;
4+
import io.pyroscope.javaagent.impl.DefaultConfigurationProvider;
45
import io.pyroscope.javaagent.impl.DefaultLogger;
6+
import io.pyroscope.javaagent.util.TmpFileUtil;
7+
import org.jetbrains.annotations.Nullable;
58

69
import java.io.IOException;
710
import java.io.InputStream;
811
import java.lang.instrument.Instrumentation;
912
import java.nio.file.Files;
13+
import java.nio.file.InvalidPathException;
1014
import java.nio.file.Path;
15+
import java.nio.file.Paths;
1116
import java.nio.file.StandardCopyOption;
1217
import java.util.jar.JarFile;
1318

@@ -34,8 +39,27 @@
3439
class BootstrapApiInjector {
3540

3641
private static final String RESOURCE_NAME = "/pyroscope-bootstrap.jar.bin";
42+
private static final String PYROSCOPE_TMP_DIR = "PYROSCOPE_TMP_DIR";
3743

3844
static void inject(Instrumentation instrumentation) {
45+
String s = getTmpDir();
46+
Path tmpDir = null;
47+
if (s != null && !s.isEmpty()) {
48+
try {
49+
tmpDir = Paths.get(s);
50+
} catch (InvalidPathException e) {
51+
DefaultLogger.PRECONFIG_LOGGER.log(Logger.Level.WARN,
52+
"BootstrapApiInjector: invalid PYROSCOPE_TMP_DIR '%s', using system temp: %s", s, e.getMessage());
53+
}
54+
}
55+
inject(instrumentation, tmpDir);
56+
}
57+
58+
private static String getTmpDir() {
59+
return DefaultConfigurationProvider.INSTANCE.get(PYROSCOPE_TMP_DIR);
60+
}
61+
62+
static void inject(Instrumentation instrumentation, @Nullable Path tmpDir) {
3963
try {
4064
try (InputStream is = BootstrapApiInjector.class.getResourceAsStream(RESOURCE_NAME)) {
4165
if (is == null) {
@@ -44,7 +68,7 @@ static void inject(Instrumentation instrumentation) {
4468
RESOURCE_NAME);
4569
return;
4670
}
47-
Path tempJar = Files.createTempFile("pyroscope-bootstrap-", ".jar");
71+
Path tempJar = createBootstrapJar(tmpDir);
4872
tempJar.toFile().deleteOnExit();
4973
Files.copy(is, tempJar, StandardCopyOption.REPLACE_EXISTING);
5074

@@ -57,4 +81,8 @@ static void inject(Instrumentation instrumentation) {
5781
"BootstrapApiInjector: Failed to inject bootstrap API: %s", e);
5882
}
5983
}
84+
85+
private static Path createBootstrapJar(@Nullable Path tmpDir) throws IOException {
86+
return TmpFileUtil.createTempFile(tmpDir, "pyroscope-bootstrap-", ".jar").toPath();
87+
}
6088
}

agent/src/main/java/io/pyroscope/javaagent/JFRJCMDProfilerDelegate.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.pyroscope.http.Format;
44
import io.pyroscope.javaagent.config.Config;
5+
import io.pyroscope.javaagent.util.TmpFileUtil;
56
import io.pyroscope.labels.v2.Pyroscope;
67

78
import java.io.BufferedReader;
@@ -48,7 +49,7 @@ public void setConfig(final Config config) {
4849
jfrSettingsPath = findJfrSettingsPath(config);
4950

5051
try {
51-
tempJFRFile = File.createTempFile("pyroscope", ".jfr");
52+
tempJFRFile = TmpFileUtil.createJfrFile(config);
5253
tempJFRFile.deleteOnExit();
5354
} catch (IOException e) {
5455
throw new IllegalStateException(e);
@@ -141,7 +142,8 @@ private static Path findJfrSettingsPath(Config config) {
141142
}
142143
// otherwise load default settings
143144
try (InputStream inputStream = JFRJCMDProfilerDelegate.class.getResourceAsStream(JFR_SETTINGS_RESOURCE)) {
144-
Path jfrSettingsPath = Files.createTempFile("pyroscope", ".jfc");
145+
Path jfrSettingsPath = TmpFileUtil.createTempFile(config.tmpDir, "pyroscope", ".jfc").toPath();
146+
jfrSettingsPath.toFile().deleteOnExit();
145147
Files.copy(inputStream, jfrSettingsPath, StandardCopyOption.REPLACE_EXISTING);
146148
return jfrSettingsPath;
147149
} catch (IOException e) {

agent/src/main/java/io/pyroscope/javaagent/JFRJDKProfilerDelegate.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.pyroscope.http.Format;
44
import io.pyroscope.javaagent.config.Config;
5+
import io.pyroscope.javaagent.util.TmpFileUtil;
56
import jdk.jfr.Recording;
67

78
import java.io.File;
@@ -11,9 +12,8 @@
1112
import java.time.Duration;
1213
import java.time.Instant;
1314
import java.time.temporal.ChronoUnit;
14-
import java.util.Optional;
1515

16-
import static io.pyroscope.labels.v2.Pyroscope.*;
16+
import static io.pyroscope.labels.v2.Pyroscope.LabelsWrapper;
1717
import static java.lang.String.format;
1818

1919
/**
@@ -37,18 +37,13 @@ public JFRJDKProfilerDelegate(Config config) {
3737
public void setConfig(final Config config) {
3838
this.config = config;
3939
try {
40-
tempJFRFile = jfrRecordingPath();
40+
tempJFRFile = TmpFileUtil.createJfrFile(config);
41+
tempJFRFile.deleteOnExit();
4142
} catch (IOException e) {
4243
throw new UncheckedIOException("cannot create JFR destination path", e);
4344
}
4445
}
4546

46-
private static File jfrRecordingPath() throws IOException {
47-
File tempJFRFile = File.createTempFile("pyroscope", ".jfr");
48-
tempJFRFile.deleteOnExit();
49-
return tempJFRFile;
50-
}
51-
5247
/**
5348
* Start JFR profiler
5449
*/

agent/src/main/java/io/pyroscope/javaagent/config/Config.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
import io.pyroscope.javaagent.impl.DefaultLogger;
1212
import okhttp3.HttpUrl;
1313
import org.jetbrains.annotations.NotNull;
14+
import org.jetbrains.annotations.Nullable;
1415

1516
import java.lang.reflect.Type;
1617
import java.nio.ByteBuffer;
1718
import java.nio.file.Files;
19+
import java.nio.file.Path;
1820
import java.nio.file.Paths;
1921
import java.time.Duration;
2022
import java.util.*;
@@ -58,6 +60,7 @@ public final class Config {
5860
private static final String PYROSCOPE_HTTP_HEADERS = "PYROSCOPE_HTTP_HEADERS";
5961
private static final String PYROSCOPE_TENANT_ID = "PYROSCOPE_TENANT_ID";
6062
private static final String PYROSCOPE_PROFILE_EXPORT_TIMEOUT = "PYROSCOPE_PROFILE_EXPORT_TIMEOUT";
63+
private static final String PYROSCOPE_TMP_DIR = "PYROSCOPE_TMP_DIR";
6164

6265
/**
6366
* Experimental feature, may be removed in the future
@@ -137,6 +140,7 @@ public final class Config {
137140
public final String APExtraArguments;
138141
public final String basicAuthUser;
139142
public final String basicAuthPassword;
143+
public final @Nullable Path tmpDir;
140144

141145
Config(final boolean agentEnabled,
142146
final String applicationName,
@@ -166,7 +170,8 @@ public final class Config {
166170
String APExtraArguments,
167171
String basicAuthUser,
168172
String basicAuthPassword,
169-
Duration profileExportTimeout) {
173+
Duration profileExportTimeout,
174+
@Nullable Path tmpDir) {
170175
this.agentEnabled = agentEnabled;
171176
this.applicationName = applicationName;
172177
this.profilerType = profilerType;
@@ -192,6 +197,7 @@ public final class Config {
192197
this.APExtraArguments = APExtraArguments;
193198
this.basicAuthUser = basicAuthUser;
194199
this.basicAuthPassword = basicAuthPassword;
200+
this.tmpDir = validateTmpDir(tmpDir);
195201
this.timeseries = timeseriesName(AppName.parse(applicationName), profilingEvent, format);
196202
this.timeseriesName = timeseries.toString();
197203
this.format = format;
@@ -250,6 +256,7 @@ public String toString() {
250256
", httpHeaders=" + httpHeaders +
251257
", samplingDuration=" + samplingDuration +
252258
", tenantID=" + tenantID +
259+
", tmpDir='" + tmpDir + '\'' +
253260
'}';
254261
}
255262

@@ -305,7 +312,8 @@ private static long durationToHertz(Duration duration) {
305312
cp.get(PYROSCOPE_AP_LOG_LEVEL_CONFIG),
306313
cp.get(PYROSCOPE_AP_EXTRA_ARGUMENTS_CONFIG),
307314
cp.get(PYROSCOPE_BASIC_AUTH_USER_CONFIG), cp.get(PYROSCOPE_BASIC_AUTH_PASSWORD_CONFIG),
308-
profileExportTimeout(cp));
315+
profileExportTimeout(cp),
316+
tmpDir(cp));
309317
}
310318

311319
/**
@@ -659,6 +667,19 @@ private static String tenantID(ConfigurationProvider cp) {
659667
return cp.get(PYROSCOPE_TENANT_ID);
660668
}
661669

670+
private static @Nullable Path tmpDir(ConfigurationProvider cp) {
671+
String s = cp.get(PYROSCOPE_TMP_DIR);
672+
return (s != null && !s.isEmpty()) ? Paths.get(s) : null;
673+
}
674+
675+
private static @Nullable Path validateTmpDir(@Nullable Path tmpDir) {
676+
if (tmpDir != null && tmpDir.toAbsolutePath().toString().contains(",")) {
677+
throw new IllegalArgumentException(
678+
"PYROSCOPE_TMP_DIR must not contain commas — async-profiler uses comma as option separator: " + tmpDir);
679+
}
680+
return tmpDir;
681+
}
682+
662683
private static Duration samplingDuration(ConfigurationProvider configurationProvider) {
663684
Duration uploadInterval = uploadInterval(configurationProvider);
664685

@@ -730,6 +751,7 @@ public static class Builder {
730751
private String basicAuthUser;
731752
private String basicAuthPassword;
732753
private String jfrProfilerSettings;
754+
private @Nullable Path tmpDir;
733755

734756
public Builder() {
735757
}
@@ -766,6 +788,7 @@ public Builder(@NotNull Config buildUpon) {
766788
APExtraArguments = buildUpon.APExtraArguments;
767789
basicAuthUser = buildUpon.basicAuthUser;
768790
basicAuthPassword = buildUpon.basicAuthPassword;
791+
tmpDir = buildUpon.tmpDir;
769792
}
770793

771794
public Builder setAgentEnabled(boolean agentEnabled) {
@@ -940,6 +963,12 @@ public Builder setProfilerType(ProfilerType profilerType) {
940963
return this;
941964
}
942965

966+
public Builder setTmpDir(@Nullable String tmpDir) {
967+
this.tmpDir = validateTmpDir(
968+
(tmpDir != null && !tmpDir.isEmpty()) ? Paths.get(tmpDir) : null);
969+
return this;
970+
}
971+
943972
public @NotNull Config build() {
944973
if (applicationName == null || applicationName.isEmpty()) {
945974
applicationName = generateApplicationName();
@@ -972,7 +1001,8 @@ public Builder setProfilerType(ProfilerType profilerType) {
9721001
APLogLevel,
9731002
APExtraArguments,
9741003
basicAuthUser, basicAuthPassword,
975-
profileExportTimeout);
1004+
profileExportTimeout,
1005+
tmpDir);
9761006
}
9771007
}
9781008
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.pyroscope.javaagent.util;
2+
3+
import io.pyroscope.javaagent.config.Config;
4+
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
12+
public class TmpFileUtil {
13+
private TmpFileUtil() {
14+
}
15+
16+
public static File createJfrFile(Config config) throws IOException {
17+
return createTempFile(config.tmpDir, "pyroscope", ".jfr");
18+
}
19+
20+
public static File createTempFile(@Nullable Path dir, String prefix, String suffix) throws IOException {
21+
if (dir != null) {
22+
Files.createDirectories(dir);
23+
return Files.createTempFile(dir, prefix, suffix).toFile().getAbsoluteFile();
24+
}
25+
return File.createTempFile(prefix, suffix).getAbsoluteFile();
26+
}
27+
}

0 commit comments

Comments
 (0)