Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
325df77
Initial implementation of thread dumps
Josh-Matsuoka May 2, 2025
e74a9d5
Support other thread dump format, sanity check format query parameter
Josh-Matsuoka May 2, 2025
caa7d2a
Merge remote-tracking branch 'upstream/main' into thread-dumps
Josh-Matsuoka May 16, 2025
368fa46
refactor, long-running api handling
Josh-Matsuoka May 30, 2025
4fe54df
Fix error notification class
Josh-Matsuoka Jun 2, 2025
fb193d5
Fix downloads, filter by target, temp debug logging
Josh-Matsuoka Jun 2, 2025
f4d9a40
Merge remote-tracking branch 'upstream/main' into thread-dumps
Josh-Matsuoka Jun 2, 2025
dd549aa
Support Metadata storage mode, move logging to tracev
Josh-Matsuoka Jun 17, 2025
9233076
close inputStream
Josh-Matsuoka Jun 17, 2025
7b3a512
merging changes from upstream
Josh-Matsuoka Jun 17, 2025
f58cf3c
Remove embedded thread dump content, fix smoketest
Josh-Matsuoka Jun 18, 2025
820d6dd
Address review feedback
Josh-Matsuoka Jun 18, 2025
3a9226a
Remove uuid from tagging since it's used as the key
Josh-Matsuoka Jun 18, 2025
be2de39
Fix potential NPE in getThreadDumps
Josh-Matsuoka Jun 19, 2025
c3f660a
formatting
Josh-Matsuoka Jun 19, 2025
3a78277
merging changes from upstream
Josh-Matsuoka Jun 25, 2025
eb4220c
refactor
Josh-Matsuoka Jun 25, 2025
e2bb382
Thread Dump endpoint tests
Josh-Matsuoka Jul 4, 2025
b37b15d
test full workflow (create, list, delete)
Josh-Matsuoka Jul 4, 2025
22da4a7
Review feedback
Josh-Matsuoka Jul 5, 2025
ecea0e3
chore(schema): automatic update
Jul 30, 2025
5a52688
Review feedback
Josh-Matsuoka Aug 1, 2025
ccbea34
deserialize thread dumps retrieved from agent
Josh-Matsuoka Aug 27, 2025
f495e5f
Merging changes from upstream
Josh-Matsuoka Aug 28, 2025
19b5025
Generate filename for content-disposition header
Josh-Matsuoka Aug 28, 2025
42c9c31
chore(schema): automatic update
Aug 28, 2025
9ca960e
Fix bug caused by rebase
Josh-Matsuoka Aug 28, 2025
66824d6
Merge remote-tracking branch 'origin/thread-dumps' into thread-dumps
Josh-Matsuoka Aug 28, 2025
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
32 changes: 20 additions & 12 deletions src/main/java/io/cryostat/diagnostic/Diagnostics.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;

@Path("/api/beta/diagnostics/targets/{targetId}")
@Path("/api/beta/diagnostics/")
public class Diagnostics {

@Inject TargetConnectionManager targetConnectionManager;
Expand All @@ -87,13 +87,13 @@ public class Diagnostics {
@Inject EventBus bus;
@Inject DiagnosticsHelper helper;

@Path("/threaddump")
@Path("targets/{targetId}/threaddump")
@RolesAllowed("write")
@Blocking
@POST
public String threadDump(
HttpServerResponse response, @RestPath long targetId, @RestQuery String format) {
log.trace("Creating new thread dump request");
log.trace("Creating new thread dump request for target: " + targetId);
ThreadDumpRequest request =
new ThreadDumpRequest(
UUID.randomUUID().toString(), Long.toString(targetId), format);
Expand All @@ -102,21 +102,24 @@ public String threadDump(
return request.id();
}

@Path("/threaddump")
@Path("targets/{targetId}/threaddump")
@RolesAllowed("read")
@Blocking
@GET
public List<ThreadDump> getThreadDumps(@RestPath long targetId) {
log.trace("Fetching thread dumps");
return helper.getThreadDumps(bucket);
log.warn("Fetching thread dumps for target: " + targetId);
log.warn("Thread dumps: " + helper.getThreadDumps(targetId));
log.warn("Storage bucket: " + bucket);
return helper.getThreadDumps(targetId);
}

@DELETE
@Blocking
@Path("/threaddump/{threadDumpId}")
@Path("targets/{targetId}/threaddump/{threadDumpId}")
@RolesAllowed("write")
public void deleteThreadDump(@RestPath String threadDumpId) {
try {
log.warn("Deleting thread dump with ID: " + threadDumpId);
storage.headObject(
HeadObjectRequest.builder().bucket(bucket).key(threadDumpId).build());
} catch (NoSuchKeyException e) {
Expand All @@ -133,9 +136,13 @@ public void deleteThreadDump(@RestPath String threadDumpId) {
public RestResponse<Object> handleStorageDownload(
@RestPath String encodedKey, @RestQuery String query) throws URISyntaxException {
Pair<String, String> decodedKey = helper.decodedKey(encodedKey);
String key = helper.threadDumpKey(decodedKey);

storage.headObject(HeadObjectRequest.builder().bucket(bucket).key(key).build())
var threadDumpId = decodedKey.getValue().strip();
log.warn("Handling download Request for encodedKey: " + encodedKey);
log.warn("Handling download Request for query: " + query);
log.warn("Decoded key: " + decodedKey.toString());
log.warn("UUID: " + threadDumpId);
log.warn("Bucket: " + bucket);
storage.headObject(HeadObjectRequest.builder().bucket(bucket).key(threadDumpId).build())
.sdkHttpResponse();

if (!presignedDownloadsEnabled) {
Expand All @@ -149,7 +156,8 @@ public RestResponse<Object> handleStorageDownload(
}

log.tracev("Handling presigned download request for {0}", decodedKey);
GetObjectRequest getRequest = GetObjectRequest.builder().bucket(bucket).key(key).build();
GetObjectRequest getRequest =
GetObjectRequest.builder().bucket(bucket).key(threadDumpId).build();
GetObjectPresignRequest presignRequest =
GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(1))
Expand Down Expand Up @@ -185,7 +193,7 @@ public RestResponse<Object> handleStorageDownload(
return response.location(uri).build();
}

@Path("/gc")
@Path("targets/{targetId}/gc")
@RolesAllowed("write")
@Blocking
@POST
Expand Down
41 changes: 27 additions & 14 deletions src/main/java/io/cryostat/diagnostic/DiagnosticsHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.MediaType;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
Expand Down Expand Up @@ -78,13 +77,16 @@ public class DiagnosticsHelper {
@Inject StorageBuckets buckets;

void onStart(@Observes StartupEvent evt) {
log.warn("Creating thread dump bucket: " + bucket);
buckets.createIfNecessary(bucket);
}

public ThreadDump dumpThreads(String format, long targetId) {
if (!(format.equals(DUMP_THREADS) || format.equals(DUMP_THREADS_TO_FIlE))) {
throw new BadRequestException();
}
log.warn(
"Thread Dump request received for Target: " + targetId + " with format: " + format);
Object[] params = new Object[1];
String[] signature = new String[] {String[].class.getName()};
return targetConnectionManager.executeConnectedTask(
Expand All @@ -97,8 +99,8 @@ public ThreadDump dumpThreads(String format, long targetId) {
});
}

public List<ThreadDump> getThreadDumps(String jvmId) {
return listThreadDumps(jvmId).stream()
public List<ThreadDump> getThreadDumps(long targetId) {
return listThreadDumps(targetId).stream()
.map(
item -> {
try {
Expand All @@ -108,6 +110,13 @@ public List<ThreadDump> getThreadDumps(String jvmId) {
return null;
}
})
.filter(
item -> {
log.warn("Item jvmID: " + item.jvmId());
log.warn("Item key: " + item.uuid());
log.warn("Item download URL: " + item.downloadUrl());
return Target.<Target>findById(targetId).jvmId.equals(item.jvmId());
})
.toList();
}

Expand Down Expand Up @@ -166,6 +175,9 @@ public ThreadDump addThreadDump(String content, String jvmId) {
.tagging(createTagging(jvmId, uuid))
.build(),
RequestBody.fromString(content));
log.warn("Putting Thread dump into storage with key: " + uuid);
log.warn("jvmID: " + jvmId);
log.warn("Bucket: " + bucket);
return new ThreadDump(content, jvmId, downloadUrl(jvmId, uuid), uuid);
}

Expand Down Expand Up @@ -195,18 +207,19 @@ private Tagging createTagging(String jvmId, String uuid) {
}

public String downloadUrl(String jvmId, String filename) {
return String.format("/threaddump/download/%s", encodedKey(jvmId, filename));
return String.format(
"/api/beta/diagnostics/threaddump/download/%s", encodedKey(jvmId, filename));
}

public String encodedKey(String jvmId, String filename) {
public String encodedKey(String jvmId, String uuid) {
Objects.requireNonNull(jvmId);
Objects.requireNonNull(filename);
Objects.requireNonNull(uuid);
return base64Url.encodeAsString(
(threadDumpKey(jvmId, filename)).getBytes(StandardCharsets.UTF_8));
(threadDumpKey(jvmId, uuid)).getBytes(StandardCharsets.UTF_8));
}

public String threadDumpKey(String jvmId, String filename) {
return (jvmId + "/" + filename).strip();
public String threadDumpKey(String jvmId, String uuid) {
return (jvmId + "/" + uuid).strip();
}

public String threadDumpKey(Pair<String, String> pair) {
Expand All @@ -218,7 +231,8 @@ public InputStream getThreadDumpStream(String jvmId, String threadDumpID) {
}

public InputStream getThreadDumpStream(String encodedKey) {
String key = new String(base64Url.decode(encodedKey), StandardCharsets.UTF_8);
Pair<String, String> decodedKey = decodedKey(encodedKey);
var key = decodedKey.getValue().strip();

GetObjectRequest getRequest = GetObjectRequest.builder().bucket(bucket).key(key).build();

Expand All @@ -234,11 +248,10 @@ public Pair<String, String> decodedKey(String encodedKey) {
return Pair.of(parts[0], parts[1]);
}

public List<S3Object> listThreadDumps(String jvmId) {
public List<S3Object> listThreadDumps(long targetId) {
var builder = ListObjectsV2Request.builder().bucket(bucket);
if (StringUtils.isNotBlank(jvmId)) {
builder = builder.prefix(jvmId);
}
var jvmId = Target.<Target>findById(targetId).jvmId;
log.warn("Listing thread dumps for jvmId: " + jvmId);
return storage.listObjectsV2(builder.build()).contents().stream().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ public void onMessage(ThreadDumpRequest request) {
try {
var target = Target.<Target>findById(request.jvmId);
var dump = diagnosticsHelper.dumpThreads(request.format, target.id);
logger.trace("Thread Dump complete, firing notification");
bus.publish(
MessagingServer.class.getName(),
new Notification(
Expand Down
Loading