From 80ed0593bf4a8b60bf6af4c7119ab64474488f7f Mon Sep 17 00:00:00 2001 From: Ming Wang Date: Wed, 16 Aug 2023 11:46:06 -0400 Subject: [PATCH] accept snapshot --- .../agent/remote/RecordingsContext.java | 86 +++++++++++++------ 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/cryostat/agent/remote/RecordingsContext.java b/src/main/java/io/cryostat/agent/remote/RecordingsContext.java index fbd7b0ca..6c481417 100644 --- a/src/main/java/io/cryostat/agent/remote/RecordingsContext.java +++ b/src/main/java/io/cryostat/agent/remote/RecordingsContext.java @@ -62,8 +62,7 @@ class RecordingsContext implements RemoteContext { private static final String PATH = "/recordings/"; - private static final Pattern PATH_ID_PATTERN = - Pattern.compile("^" + PATH + "(\\d+)$", Pattern.MULTILINE); + private static final Pattern PATH_ID_PATTERN = Pattern.compile("^" + PATH + "(\\d+)$", Pattern.MULTILINE); private final Logger log = LoggerFactory.getLogger(getClass()); private final SmallRyeConfig config; @@ -106,7 +105,7 @@ public void handle(HttpExchange exchange) throws IOException { } break; case "POST": - handleStart(exchange); + handleStartRecordingOrSnapshot(exchange); break; case "PATCH": id = extractId(exchange); @@ -193,13 +192,26 @@ private void handleGetRecording(HttpExchange exchange, long id) { }); } - private void handleStart(HttpExchange exchange) throws IOException { + private void handleStartRecordingOrSnapshot(HttpExchange exchange) throws IOException { try (InputStream body = exchange.getRequestBody()) { StartRecordingRequest req = mapper.readValue(body, StartRecordingRequest.class); if (!req.isValid()) { exchange.sendResponseHeaders(HttpStatus.SC_BAD_REQUEST, BODY_LENGTH_NONE); return; } + if (req.requestSnapshot()) { + try { + SerializableRecordingDescriptor snapshot = startSnapshot(req, exchange); + exchange.sendResponseHeaders(HttpStatus.SC_CREATED, BODY_LENGTH_UNKNOWN); + try (OutputStream response = exchange.getResponseBody()) { + mapper.writeValue(response, snapshot); + } + return; + } catch (IOException e) { + log.error("Failed to start snapshot", e); + exchange.sendResponseHeaders(HttpStatus.SC_SERVICE_UNAVAILABLE, BODY_LENGTH_NONE); + } + } SerializableRecordingDescriptor recording = startRecording(req); exchange.sendResponseHeaders(HttpStatus.SC_CREATED, BODY_LENGTH_UNKNOWN); try (OutputStream response = exchange.getResponseBody()) { @@ -283,35 +295,31 @@ private List getRecordings() { private SerializableRecordingDescriptor startRecording(StartRecordingRequest req) throws QuantityConversionException, ServiceNotAvailableException, - FlightRecorderException, - org.openjdk.jmc.rjmx.services.jfr.FlightRecorderException, - InvalidEventTemplateException, InvalidXmlException, IOException { + FlightRecorderException, + org.openjdk.jmc.rjmx.services.jfr.FlightRecorderException, + InvalidEventTemplateException, InvalidXmlException, IOException { Runnable cleanup = () -> {}; try { - JFRConnection conn = - jfrConnectionToolkit.connect( - jfrConnectionToolkit.createServiceURL("localhost", 0)); + JFRConnection conn = jfrConnectionToolkit.connect( + jfrConnectionToolkit.createServiceURL("localhost", 0)); IConstrainedMap events; if (req.requestsCustomTemplate()) { - Template template = - localStorageTemplateService.addTemplate( - new ByteArrayInputStream( - req.template.getBytes(StandardCharsets.UTF_8))); + Template template = localStorageTemplateService.addTemplate( + new ByteArrayInputStream( + req.template.getBytes(StandardCharsets.UTF_8))); events = localStorageTemplateService.getEvents(template).orElseThrow(); - cleanup = - () -> { - try { - localStorageTemplateService.deleteTemplate(template); - } catch (InvalidEventTemplateException | IOException e) { - log.error("Failed to clean up template " + template.getName(), e); - } - }; + cleanup = () -> { + try { + localStorageTemplateService.deleteTemplate(template); + } catch (InvalidEventTemplateException | IOException e) { + log.error("Failed to clean up template " + template.getName(), e); + } + }; } else { - events = - new RemoteTemplateService(conn) - .getEvents(req.localTemplateName, TemplateType.TARGET).stream() - .findFirst() - .orElseThrow(); + events = new RemoteTemplateService(conn) + .getEvents(req.localTemplateName, TemplateType.TARGET).stream() + .findFirst() + .orElseThrow(); } IFlightRecorderService svc = conn.getService(); return new SerializableRecordingDescriptor( @@ -329,6 +337,21 @@ private SerializableRecordingDescriptor startRecording(StartRecordingRequest req } } + private SerializableRecordingDescriptor startSnapshot(StartRecordingRequest req, HttpExchange exchange) + throws IOException { + Runnable cleanup = () -> {}; + try { + Recording snapshot = FlightRecorder.getFlightRecorder().takeSnapshot(); + if (snapshot.getSize() == 0) { + log.warn("No active recordings"); + exchange.sendResponseHeaders(HttpStatus.SC_SERVICE_UNAVAILABLE, BODY_LENGTH_NONE); + } + return new SerializableRecordingDescriptor(snapshot); + } finally { + cleanup.run(); + } + } + static class StartRecordingRequest { public String name; @@ -346,12 +369,19 @@ boolean requestsBundledTemplate() { return !StringUtils.isBlank(localTemplateName); } + boolean requestSnapshot() { + boolean snapshotName = name.equals("snapshot"); + boolean snapshotTemplate = StringUtils.isBlank(template) && StringUtils.isBlank(localTemplateName); + boolean snapshotFeatures = duration == 0 && maxSize == 0 && maxAge == 0; + return snapshotName && snapshotTemplate && snapshotFeatures; + } + boolean isValid() { boolean requestsCustomTemplate = requestsCustomTemplate(); boolean requestsBundledTemplate = requestsBundledTemplate(); boolean requestsEither = requestsCustomTemplate || requestsBundledTemplate; boolean requestsBoth = requestsCustomTemplate && requestsBundledTemplate; - return requestsEither && !requestsBoth; + return requestsEither && !requestsBoth && !requestSnapshot(); } } }