Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
104 changes: 104 additions & 0 deletions schema/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,18 @@ components:
- CUSTOM
- PRESET
type: string
ThreadDump:
properties:
downloadUrl:
type: string
jvmId:
type: string
lastModified:
format: int64
type: integer
uuid:
type: string
type: object
UUID:
format: uuid
pattern: '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'
Expand Down Expand Up @@ -713,6 +725,98 @@ paths:
summary: Initiate a garbage collection on the specified target
tags:
- Diagnostics
/api/beta/diagnostics/targets/{targetId}/threaddump:
get:
parameters:
- in: path
name: targetId
required: true
schema:
format: int64
type: integer
responses:
"200":
content:
application/json:
schema:
items:
$ref: '#/components/schemas/ThreadDump'
type: array
description: OK
summary: Get Thread Dumps
tags:
- Diagnostics
post:
parameters:
- in: path
name: targetId
required: true
schema:
format: int64
type: integer
- in: query
name: format
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/HttpServerResponse'
required: true
responses:
"200":
content:
text/plain:
schema:
type: string
description: OK
"400":
description: Bad Request
summary: Thread Dump
tags:
- Diagnostics
/api/beta/diagnostics/targets/{targetId}/threaddump/{threadDumpId}:
delete:
parameters:
- in: path
name: targetId
required: true
schema:
format: int64
type: integer
- in: path
name: threadDumpId
required: true
schema:
type: string
responses:
"204":
description: No Content
summary: Delete Thread Dump
tags:
- Diagnostics
/api/beta/diagnostics/threaddump/download/{encodedKey}:
get:
parameters:
- in: path
name: encodedKey
required: true
schema:
type: string
- in: query
name: filename
schema:
type: string
responses:
"200":
content:
application/json:
schema: {}
description: OK
summary: Handle Storage Download
tags:
- Diagnostics
/api/beta/fs/recordings:
get:
responses:
Expand Down
2 changes: 1 addition & 1 deletion smoketest.bash
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ PULL_IMAGES=${PULL_IMAGES:-true}
KEEP_VOLUMES=${KEEP_VOLUMES:-false}
OPEN_TABS=${OPEN_TABS:-false}

PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-archivedrecordings,archivedreports,eventtemplates,probes}
PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-archivedrecordings,archivedreports,eventtemplates,probes,threaddumps}

LOG_LEVEL=0
CRYOSTAT_HTTP_HOST=${CRYOSTAT_HTTP_HOST:-cryostat}
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/cryostat/ConfigProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class ConfigProperties {
"storage.buckets.event-templates.name";
public static final String AWS_BUCKET_NAME_PROBE_TEMPLATES =
"storage.buckets.probe-templates.name";
public static final String AWS_BUCKET_NAME_THREAD_DUMPS = "storage.buckets.thread-dumps.name";
public static final String AWS_METADATA_PREFIX_THREAD_DUMPS =
"storage.metadata.prefix.thread-dumps";
public static final String AWS_METADATA_PREFIX_RECORDINGS =
"storage.metadata.prefix.recordings";
public static final String AWS_METADATA_PREFIX_EVENT_TEMPLATES =
Expand Down Expand Up @@ -67,6 +70,9 @@ public class ConfigProperties {
public static final String STORAGE_PRESIGNED_DOWNLOADS_ENABLED =
"storage.presigned-downloads.enabled";

public static final String STORAGE_METADATA_THREAD_DUMPS_STORAGE_MODE =
"storage.metadata.thread-dumps.storage-mode";

public static final String CUSTOM_TEMPLATES_DIR = "templates-dir";
public static final String PRESET_TEMPLATES_DIR = "preset-templates-dir";
public static final String PROBE_TEMPLATES_DIR = "probe-templates-dir";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright The Cryostat Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cryostat.diagnostic;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import io.cryostat.ConfigProperties;
import io.cryostat.StorageBuckets;
import io.cryostat.diagnostic.Diagnostics.ThreadDump;
import io.cryostat.recordings.ArchivedRecordingMetadataService;
import io.cryostat.util.CRUDService;
import io.cryostat.util.HttpMimeType;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.arc.lookup.LookupIfProperty;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

@ApplicationScoped
@LookupIfProperty(
name = ConfigProperties.STORAGE_METADATA_THREAD_DUMPS_STORAGE_MODE,
stringValue = ArchivedRecordingMetadataService.METADATA_STORAGE_MODE_BUCKET)
public class BucketedDiagnosticsMetadataService
implements CRUDService<String, ThreadDump, ThreadDump> {

@ConfigProperty(name = ConfigProperties.STORAGE_METADATA_THREAD_DUMPS_STORAGE_MODE)
String storageMode;

@ConfigProperty(name = ConfigProperties.AWS_BUCKET_NAME_METADATA)
String bucket;

@ConfigProperty(name = ConfigProperties.AWS_METADATA_PREFIX_THREAD_DUMPS)
String prefix;

@Inject S3Client storage;
@Inject StorageBuckets buckets;

@Inject ObjectMapper mapper;

@Inject Logger logger;

void onStart(@Observes StartupEvent evt) {
if (!ArchivedRecordingMetadataService.METADATA_STORAGE_MODE_BUCKET.equals(storageMode)) {
return;
}
buckets.createIfNecessary(bucket);
}

@Override
public List<ThreadDump> list() throws IOException {
var builder = ListObjectsV2Request.builder().bucket(bucket).prefix(prefix);
var objs = storage.listObjectsV2(builder.build()).contents();
return objs.stream()
.map(
t -> {
// TODO this entails a remote file read over the network and then some
// minor processing of the received file. More time will be spent
// retrieving the data than processing it, so this should be
// parallelized.
try {
return read(t.key()).orElseThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
})
.filter(Objects::nonNull)
.toList();
}

@Override
public void create(String k, ThreadDump threadDump) throws IOException {
storage.putObject(
PutObjectRequest.builder()
.bucket(bucket)
.key(prefix(k))
.contentType(HttpMimeType.PLAINTEXT.mime())
.build(),
RequestBody.fromBytes(mapper.writeValueAsBytes(threadDump)));
}

@Override
public Optional<ThreadDump> read(String k) throws IOException {
try (var stream =
new BufferedInputStream(
storage.getObject(
GetObjectRequest.builder()
.bucket(bucket)
.key(prefix(k))
.build()))) {
return Optional.of(mapper.readValue(stream, ThreadDump.class));
}
}

@Override
public void delete(String k) throws IOException {
storage.deleteObject(DeleteObjectRequest.builder().bucket(bucket).key(prefix(k)).build());
}

private String prefix(String key) {
return String.format("%s/%s", prefix, key);
}
}
Loading
Loading