Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reports): allow API requests to filter rules to evaluate #977

Merged
merged 12 commits into from
Jun 7, 2022
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<com.google.dagger.version>2.34.1</com.google.dagger.version>
<com.google.dagger.compiler.version>2.26</com.google.dagger.compiler.version>

<io.cryostat.core.version>2.9.1</io.cryostat.core.version>
<io.cryostat.core.version>2.10.0</io.cryostat.core.version>

<org.apache.commons.lang3.version>3.12.0</org.apache.commons.lang3.version>
<org.apache.commons.codec.version>1.15</org.apache.commons.codec.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ protected AbstractReportGeneratorService(
}

@Override
public final CompletableFuture<Path> exec(RecordingDescriptor recordingDescriptor)
throws Exception {
public final CompletableFuture<Path> exec(
RecordingDescriptor recordingDescriptor, String filter) throws Exception {
Path recording =
getRecordingFromLiveTarget(
recordingDescriptor.recordingName,
recordingDescriptor.connectionDescriptor);
Path saveFile = fs.createTempFile(null, null);
CompletableFuture<Path> cf = exec(recording, saveFile);
CompletableFuture<Path> cf = exec(recording, saveFile, filter);
return cf.whenComplete(
(p, t) -> {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,22 @@ class ActiveRecordingReportCache {
.expireAfterWrite(30, TimeUnit.MINUTES)
.refreshAfterWrite(5, TimeUnit.MINUTES)
.softValues()
.build(k -> getReport(k));
.build((k) -> getReport(k));
}

Future<String> get(ConnectionDescriptor connectionDescriptor, String recordingName) {
Future<String> get(
ConnectionDescriptor connectionDescriptor, String recordingName, String filter) {
CompletableFuture<String> f = new CompletableFuture<>();
try {
f.complete(cache.get(new RecordingDescriptor(connectionDescriptor, recordingName)));
if (filter.isBlank()) {
f.complete(cache.get(new RecordingDescriptor(connectionDescriptor, recordingName)));
} else {
f.complete(
getReport(
new RecordingDescriptor(connectionDescriptor, recordingName),
filter));
}

} catch (Exception e) {
f.completeExceptionally(e);
}
Expand All @@ -112,14 +121,20 @@ boolean delete(ConnectionDescriptor connectionDescriptor, String recordingName)
}

protected String getReport(RecordingDescriptor recordingDescriptor) throws Exception {
return getReport(recordingDescriptor, "");
}

protected String getReport(RecordingDescriptor recordingDescriptor, String filter)
throws Exception {
Path saveFile = null;
try {
/* NOTE: Not always a cache miss since if a filter is specified, we do not even check the cache */
logger.trace("Active report cache miss for {}", recordingDescriptor.recordingName);
try {
saveFile =
reportGeneratorServiceProvider
.get()
.exec(recordingDescriptor)
.exec(recordingDescriptor, filter)
.get(generationTimeoutSeconds, TimeUnit.SECONDS);
return fs.readString(saveFile);
} catch (ExecutionException | CompletionException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,24 @@ class ArchivedRecordingReportCache {
this.logger = logger;
}

Future<Path> get(String recordingName) {
Future<Path> get(String recordingName, String filter) {
CompletableFuture<Path> f = new CompletableFuture<>();
Path dest = recordingArchiveHelper.getCachedReportPath(recordingName);
if (fs.isReadable(dest) && fs.isRegularFile(dest)) {
/* NOTE: This is just a temporary solution: If a request includes a filter,
* the report is never cached and just constructed on demand.
*/
if (fs.isReadable(dest) && fs.isRegularFile(dest) && filter.isBlank()) {
f.complete(dest);
return f;
}

try {
logger.trace("Archived report cache miss for {}", recordingName);

Path archivedRecording = recordingArchiveHelper.getRecordingPath(recordingName).get();
Path saveFile =
reportGeneratorServiceProvider
.get()
.exec(archivedRecording, dest)
.exec(archivedRecording, dest, filter)
.get(generationTimeoutSeconds, TimeUnit.SECONDS);
f.complete(saveFile);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,18 @@ class RemoteReportGenerator extends AbstractReportGeneratorService {

@Override
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public CompletableFuture<Path> exec(Path recording, Path destination) {
public CompletableFuture<Path> exec(Path recording, Path destination, String filter) {
String reportGenerator = env.getEnv(Variables.REPORT_GENERATOR_ENV);
logger.info("POSTing {} to {}", recording, reportGenerator);
var form =
MultipartForm.create()
.attribute("filter", filter)
.binaryFileUpload(
"file",
recording.getFileName().toString(),
recording.toAbsolutePath().toString(),
HttpMimeType.OCTET_STREAM.mime());

var f = new CompletableFuture<Path>();
this.http
.postAbs(String.format("%s/report", reportGenerator))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import java.util.concurrent.CompletableFuture;

interface ReportGeneratorService {
CompletableFuture<Path> exec(Path in, Path out) throws Exception;
CompletableFuture<Path> exec(Path in, Path out, String filter) throws Exception;

CompletableFuture<Path> exec(RecordingDescriptor rd) throws Exception;
CompletableFuture<Path> exec(RecordingDescriptor rd, String filter) throws Exception;
}
9 changes: 5 additions & 4 deletions src/main/java/io/cryostat/net/reports/ReportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ public class ReportService {
this.archivedCache = archivedCache;
}

public Future<Path> get(String recordingName) {
return archivedCache.get(recordingName);
public Future<Path> get(String recordingName, String filter) {
return archivedCache.get(recordingName, filter);
}

public boolean delete(String recordingName) {
return archivedCache.delete(recordingName);
}

public Future<String> get(ConnectionDescriptor connectionDescriptor, String recordingName) {
return activeCache.get(connectionDescriptor, recordingName);
public Future<String> get(
ConnectionDescriptor connectionDescriptor, String recordingName, String filter) {
return activeCache.get(connectionDescriptor, recordingName, filter);
}

public boolean delete(ConnectionDescriptor connectionDescriptor, String recordingName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@
import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import javax.inject.Named;
import javax.inject.Provider;

import org.openjdk.jmc.flightrecorder.rules.IRule;
import org.openjdk.jmc.rjmx.ConnectionException;

import io.cryostat.configuration.Variables;
Expand All @@ -73,6 +75,7 @@
import io.cryostat.net.TargetConnectionManager;
import io.cryostat.recordings.RecordingNotFoundException;
import io.cryostat.util.JavaProcess;
import io.cryostat.util.RuleFilterParser;

import com.google.gson.Gson;

Expand Down Expand Up @@ -102,7 +105,7 @@ public class SubprocessReportGenerator extends AbstractReportGeneratorService {
}

@Override
public synchronized CompletableFuture<Path> exec(Path recording, Path saveFile)
public synchronized CompletableFuture<Path> exec(Path recording, Path saveFile, String filter)
throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, IOException,
InterruptedException, ReportGenerationException {
Expand All @@ -112,6 +115,9 @@ public synchronized CompletableFuture<Path> exec(Path recording, Path saveFile)
if (saveFile == null) {
throw new IllegalArgumentException("Destination may not be null");
}
if (filter == null) {
throw new IllegalArgumentException("Filter may not be null");
}
fs.writeString(
saveFile,
serializeTransformersSet(),
Expand All @@ -128,7 +134,7 @@ public synchronized CompletableFuture<Path> exec(Path recording, Path saveFile)
Integer.parseInt(
env.getEnv(
Variables.SUBPROCESS_MAX_HEAP_ENV, "0"))))
.processArgs(createProcessArgs(recording, saveFile));
.processArgs(createProcessArgs(recording, saveFile, filter));
return CompletableFuture.supplyAsync(
() -> {
Process proc = null;
Expand Down Expand Up @@ -207,8 +213,11 @@ private List<String> createJvmArgs(int maxHeapMegabytes) throws IOException {
return args;
}

private List<String> createProcessArgs(Path recording, Path saveFile) {
return List.of(recording.toAbsolutePath().toString(), saveFile.toAbsolutePath().toString());
private List<String> createProcessArgs(Path recording, Path saveFile, String filter) {
return List.of(
recording.toAbsolutePath().toString(),
saveFile.toAbsolutePath().toString(),
filter);
}

private String serializeTransformersSet() {
Expand Down Expand Up @@ -274,12 +283,13 @@ public static void main(String[] args) {
System.exit(ExitStatus.OTHER.code);
}

if (args.length != 2) {
if (args.length != 3) {
throw new IllegalArgumentException(Arrays.asList(args).toString());
}
var recording = Paths.get(args[0]);
Set<ReportTransformer> transformers = Collections.emptySet();
var saveFile = Paths.get(args[1]);
String filter = args[2];
try {
transformers = deserializeTransformers(fs.readString(saveFile));
} catch (Exception e) {
Expand All @@ -289,7 +299,7 @@ public static void main(String[] args) {

try {
Logger.INSTANCE.info(SubprocessReportGenerator.class.getName() + " processing report");
ReportResult reportResult = generateReportFromFile(recording, transformers);
ReportResult reportResult = generateReportFromFile(recording, transformers, filter);
Logger.INSTANCE.info(
SubprocessReportGenerator.class.getName() + " writing report to file");

Expand Down Expand Up @@ -323,16 +333,17 @@ public static void main(String[] args) {
}
}

static ReportResult generateReportFromFile(Path recording, Set<ReportTransformer> transformers)
throws Exception {
static ReportResult generateReportFromFile(
Path recording, Set<ReportTransformer> transformers, String filter) throws Exception {
var fs = new FileSystem();
if (!fs.isRegularFile(recording)) {
throw new SubprocessReportGenerationException(ExitStatus.NO_SUCH_RECORDING);
}
Predicate<IRule> rulePr = RuleFilterParser.getPredicateRuleFilter(filter);
try (InputStream stream = fs.newInputStream(recording)) {
return new InterruptibleReportGenerator(
Logger.INSTANCE, transformers, ForkJoinPool.commonPool())
.generateReportInterruptibly(stream)
.generateReportInterruptibly(stream, rulePr)
.get();
} catch (IOException ioe) {
ioe.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -119,10 +120,13 @@ public boolean isOrdered() {
@Override
public void handleAuthenticated(RoutingContext ctx) throws Exception {
String recordingName = ctx.pathParam("recordingName");
List<String> queriedFilter = ctx.queryParam("filter");
String rawFilter = queriedFilter.isEmpty() ? "" : queriedFilter.get(0);
try {

Path report =
reportService
.get(recordingName)
.get(recordingName, rawFilter)
.get(reportGenerationTimeoutSeconds, TimeUnit.SECONDS);
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, HttpMimeType.HTML.mime());
ctx.response()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
package io.cryostat.net.web.http.api.v1;

import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -121,12 +122,17 @@ public boolean isOrdered() {
@Override
public void handleAuthenticated(RoutingContext ctx) throws Exception {
String recordingName = ctx.pathParam("recordingName");
List<String> queriedFilter = ctx.queryParam("filter");
String rawFilter = queriedFilter.isEmpty() ? "" : queriedFilter.get(0);
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, HttpMimeType.HTML.mime());
try {
ctx.response()
.end(
reportService
.get(getConnectionDescriptorFromContext(ctx), recordingName)
.get(
getConnectionDescriptorFromContext(ctx),
recordingName,
rawFilter)
.get(reportGenerationTimeoutSeconds, TimeUnit.SECONDS));
} catch (CompletionException | ExecutionException ee) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -119,10 +120,12 @@ public boolean isOrdered() {
@Override
public void handleWithValidJwt(RoutingContext ctx, JWT jwt) throws Exception {
String recordingName = ctx.pathParam("recordingName");
List<String> queriedFilter = ctx.queryParam("filter");
String rawFilter = queriedFilter.isEmpty() ? "" : queriedFilter.get(0);
try {
Path report =
reportService
.get(recordingName)
.get(recordingName, rawFilter)
.get(generationTimeoutSeconds, TimeUnit.SECONDS);
ctx.response().putHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, HttpMimeType.HTML.mime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
package io.cryostat.net.web.http.api.v2;

import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -121,13 +122,18 @@ public boolean isOrdered() {
@Override
public void handleWithValidJwt(RoutingContext ctx, JWT jwt) throws Exception {
String recordingName = ctx.pathParam("recordingName");
List<String> queriedFilter = ctx.queryParam("filter");
String rawFilter = queriedFilter.isEmpty() ? "" : queriedFilter.get(0);
ctx.response().putHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, HttpMimeType.HTML.mime());
try {
ctx.response()
.end(
reportService
.get(getConnectionDescriptorFromJwt(ctx, jwt), recordingName)
.get(
getConnectionDescriptorFromJwt(ctx, jwt),
recordingName,
rawFilter)
.get(reportGenerationTimeoutSeconds, TimeUnit.SECONDS));
} catch (CompletionException | ExecutionException ee) {

Expand Down
Loading