Skip to content

Commit dadeacf

Browse files
committed
softdelete experimentation
1 parent b6bfd97 commit dadeacf

File tree

10 files changed

+190
-20
lines changed

10 files changed

+190
-20
lines changed

src/main/java/io/cryostat/discovery/ContainerDiscovery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,8 @@ private Target toTarget(ContainerSpec desc) {
259259
return null;
260260
}
261261

262-
Target target = new Target();
262+
Target target = Target.createOrUndelete(connectUrl);
263263
target.activeRecordings = new ArrayList<>();
264-
target.connectUrl = connectUrl;
265264
target.alias = Optional.ofNullable(desc.Names.get(0)).orElse(desc.Id);
266265
target.labels = desc.Labels;
267266
target.annotations =

src/main/java/io/cryostat/discovery/CustomDiscovery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ public RestResponse<Target> createForm(
115115
@RestForm String password,
116116
@RestQuery boolean dryrun,
117117
@RestQuery boolean storeCredentials) {
118-
var target = new Target();
119-
target.connectUrl = connectUrl;
118+
var target = Target.createOrUndelete(connectUrl);
120119
target.alias = alias;
121120

122121
Credential credential = null;

src/main/java/io/cryostat/discovery/JDPDiscovery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ void handleJdpEvent(JvmDiscoveryEvent evt) {
113113

114114
switch (evt.getEventKind()) {
115115
case FOUND:
116-
Target target = new Target();
116+
Target target = Target.createOrUndelete(connectUrl);
117117
target.activeRecordings = new ArrayList<>();
118-
target.connectUrl = connectUrl;
119118
target.alias = evt.getJvmDescriptor().getMainClass();
120119
target.annotations =
121120
new Annotations(

src/main/java/io/cryostat/discovery/KubeApiDiscovery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,8 @@ public Target toTarget() {
626626
"/jndi/rmi://" + host + ':' + port.getPort() + "/jmxrmi");
627627
URI connectUrl = URI.create(jmxUrl.toString());
628628

629-
Target target = new Target();
629+
Target target = Target.createOrUndelete(connectUrl);
630630
target.activeRecordings = new ArrayList<>();
631-
target.connectUrl = connectUrl;
632631
target.alias = objRef.getName();
633632
target.labels = (obj != null ? obj.getMetadata().getLabels() : new HashMap<>());
634633
target.annotations =

src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ public List<ProgramOption> getProgramOptions() {
302302
}
303303

304304
private String[] getJfrEventTypeIds(SimplifiedTarget st) {
305-
Target target = Target.find("id", st.id()).singleResult();
305+
var target = Target.getTargetById(st.id());
306306
try {
307307
return connectionManager.executeConnectedTask(
308308
target,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright The Cryostat Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.cryostat.reports;
17+
18+
import java.util.ArrayList;
19+
import java.util.Map;
20+
import java.util.Stack;
21+
import java.util.UUID;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
24+
import io.cryostat.core.reports.InterruptibleReportGenerator.AnalysisResult;
25+
import io.cryostat.discovery.DiscoveryNode;
26+
import io.cryostat.discovery.NodeType.BaseNodeType;
27+
import io.cryostat.recordings.ActiveRecordings;
28+
import io.cryostat.recordings.ArchivedRecordings.ArchivedRecording;
29+
import io.cryostat.recordings.LongRunningRequestGenerator;
30+
import io.cryostat.recordings.LongRunningRequestGenerator.ArchivedReportRequest;
31+
import io.cryostat.targets.Target;
32+
33+
import io.quarkus.vertx.ConsumeEvent;
34+
import io.smallrye.common.annotation.Blocking;
35+
import io.vertx.mutiny.core.eventbus.EventBus;
36+
import jakarta.inject.Inject;
37+
import jakarta.transaction.Transactional;
38+
import jakarta.ws.rs.GET;
39+
import jakarta.ws.rs.Path;
40+
import jakarta.ws.rs.Produces;
41+
import jakarta.ws.rs.core.MediaType;
42+
import org.apache.commons.lang3.tuple.Pair;
43+
import org.jboss.logging.Logger;
44+
import org.jboss.resteasy.reactive.RestQuery;
45+
46+
@Path("/metrics/reports")
47+
public class AnalysisReportAggregator {
48+
49+
@Inject EventBus bus;
50+
@Inject Logger logger;
51+
52+
private final Map<Target, Map<String, AnalysisResult>> map = new ConcurrentHashMap<>();
53+
54+
@ConsumeEvent(value = ActiveRecordings.ARCHIVED_RECORDING_CREATED, blocking = true)
55+
@Transactional
56+
public void onMessage(ArchivedRecording recording) {
57+
// TODO extract these to constants, and/or use other labelling
58+
var key = "origin";
59+
var value = "automated-analysis";
60+
var origin = recording.metadata().labels().get(key);
61+
if (value.equals(origin)) {
62+
var id = UUID.randomUUID();
63+
var jvmId = recording.jvmId();
64+
var target = Target.getTargetByJvmId(jvmId).orElseThrow();
65+
var filename = recording.name();
66+
logger.tracev(
67+
"Archived recording with {0}={1} label observed. Triggering batch report"
68+
+ " processing for {2}/{3}.",
69+
key, value, jvmId, filename);
70+
var request = new ArchivedReportRequest(id.toString(), Pair.of(jvmId, filename));
71+
try {
72+
var report =
73+
bus.<Map<String, AnalysisResult>>requestAndAwait(
74+
LongRunningRequestGenerator.ARCHIVE_REPORT_ADDRESS, request)
75+
.body();
76+
update(target, report);
77+
} catch (Exception e) {
78+
logger.warn(e);
79+
}
80+
}
81+
}
82+
83+
@GET
84+
@Produces(MediaType.TEXT_PLAIN)
85+
@Transactional
86+
@Blocking
87+
public String scrape(@RestQuery boolean includeDeleted) {
88+
var sb = new StringBuilder();
89+
map.forEach(
90+
(t, r) -> {
91+
var target = Target.getTargetById(t.id, includeDeleted);
92+
r.forEach(
93+
(k, v) ->
94+
// TODO do this on batch processing update, not on scrape, to
95+
// save on db queries.
96+
sb.append(k.replaceAll("[\\.\\s]+", "_"))
97+
.append('{')
98+
.append(nodeLabels(target))
99+
.append('}')
100+
.append('=')
101+
.append(v.getScore())
102+
.append('\n'));
103+
});
104+
return sb.toString();
105+
}
106+
107+
private void update(Target target, Map<String, AnalysisResult> report) {
108+
map.put(target, report);
109+
}
110+
111+
private static String nodeLabels(Target target) {
112+
var ownerChain = new Stack<DiscoveryNode>();
113+
var node = target.discoveryNode;
114+
while (node != null && !node.nodeType.equals(BaseNodeType.UNIVERSE.getKind())) {
115+
ownerChain.push(node);
116+
node = node.parent;
117+
}
118+
var list = new ArrayList<String>();
119+
while (!ownerChain.isEmpty()) {
120+
var n = ownerChain.pop();
121+
list.add(String.format("%s=\"%s\"", n.nodeType, n.name));
122+
}
123+
list.add(String.format("jvmId=\"%s\"", target.jvmId));
124+
return String.join(", ", list);
125+
}
126+
}

src/main/java/io/cryostat/targets/Target.java

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import io.cryostat.discovery.DiscoveryNode;
3535
import io.cryostat.recordings.ActiveRecording;
36+
import io.cryostat.recordings.RecordingHelper;
3637
import io.cryostat.util.URIUtil;
3738
import io.cryostat.ws.MessagingServer;
3839
import io.cryostat.ws.Notification;
@@ -119,6 +120,37 @@ public String targetId() {
119120
return this.connectUrl.toString();
120121
}
121122

123+
public static Target createOrUndelete(URI connectUrl) {
124+
Objects.requireNonNull(connectUrl);
125+
var target =
126+
Panache.getSession()
127+
// ignore soft deletion field
128+
.createNativeQuery(
129+
"select * from Target where connectUrl = :connectUrl", Target.class)
130+
.setParameter("connectUrl", connectUrl.toString().getBytes())
131+
.uniqueResult();
132+
if (target == null) {
133+
target = new Target();
134+
target.connectUrl = connectUrl;
135+
} else {
136+
int updates =
137+
Panache.getSession()
138+
.createNativeQuery(
139+
"update target set deleted = false where id = :id",
140+
Target.class)
141+
.setParameter("id", target.id)
142+
.executeUpdate();
143+
if (updates != 1) {
144+
Logger.getLogger(Target.class)
145+
.warnv(
146+
"Attempted to undelete Target {0} with connectUrl={1}, but update"
147+
+ " affected {2} rows",
148+
target.id, connectUrl, updates);
149+
}
150+
}
151+
return target;
152+
}
153+
122154
public static List<Target> getTargetsIncludingDeleted() {
123155
return Panache.getSession()
124156
// ignore soft deletion field
@@ -127,27 +159,34 @@ public static List<Target> getTargetsIncludingDeleted() {
127159
}
128160

129161
public static Target getTargetById(long targetId) {
130-
return Panache.getSession()
131-
// ignore soft deletion field
132-
.createNativeQuery("select * from Target where id = :id", Target.class)
133-
.setParameter("id", targetId)
134-
.getSingleResult();
162+
return getTargetById(targetId, false);
163+
}
164+
165+
public static Target getTargetById(long targetId, boolean includeDeleted) {
166+
if (includeDeleted) {
167+
return Panache.getSession()
168+
.createNativeQuery("select * from Target where id = :id", Target.class)
169+
.setParameter("id", targetId)
170+
.getSingleResult();
171+
} else {
172+
return Target.find("id", targetId).singleResult();
173+
}
135174
}
136175

137176
public static Target getTargetByConnectUrl(URI connectUrl) {
138177
return Panache.getSession()
139178
// ignore soft deletion field
140179
.createNativeQuery(
141180
"select * from Target where connectUrl = :connectUrl", Target.class)
142-
.setParameter("connectUrl", connectUrl.toString())
181+
.setParameter("connectUrl", connectUrl.toString().getBytes())
143182
.getSingleResult();
144183
}
145184

146185
public static Optional<Target> getTargetByJvmId(String jvmId) {
147186
return Panache.getSession()
148187
// ignore soft deletion field
149188
.createNativeQuery("select * from Target where jvmId = :jvmId", Target.class)
150-
.setParameter("jvmId", jvmId)
189+
.setParameter("jvmId", jvmId.getBytes())
151190
.uniqueResultOptional();
152191
}
153192

@@ -302,8 +341,9 @@ public record TargetDiscovery(EventKind kind, Target serviceRef, String jvmId) {
302341
static class Listener {
303342

304343
@Inject URIUtil uriUtil;
305-
@Inject Logger logger;
344+
@Inject RecordingHelper recordingHelper;
306345
@Inject EventBus bus;
346+
@Inject Logger logger;
307347

308348
@PrePersist
309349
void prePersist(Target target) {
@@ -349,6 +389,7 @@ void postUpdate(Target target) {
349389
@PostRemove
350390
void postRemove(Target target) {
351391
notify(EventKind.LOST, target);
392+
target.activeRecordings.forEach(recordingHelper::deleteRecording);
352393
}
353394

354395
private void notify(EventKind eventKind, Target target) {

src/main/java/io/cryostat/targets/TargetUpdateJob.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
4848
List<Target> targets;
4949
Long targetId = (Long) context.getJobDetail().getJobDataMap().get("targetId");
5050
if (targetId != null) {
51-
targets = List.of(Target.getTargetById(targetId));
51+
Target target = Target.findById(targetId);
52+
if (target == null) {
53+
return;
54+
}
55+
targets = List.of();
5256
} else {
5357
targets = Target.<Target>find("#Target.unconnected").list();
5458
}

src/main/resources/db/migration/V4.1.0__cryostat.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ add column deleted boolean default false;
44
delete from DiscoveryNode where nodeType not in ('Universe', 'Realm');
55

66
delete from Target where true;
7+
8+
alter table Target drop constraint FKl0dhd7qeayg54dcoblpww6x34;
9+
alter table Target drop constraint target_connecturl_key;

src/test/java/itest/TargetRecordingPatchTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package itest;
1717

18+
import java.util.List;
1819
import java.util.concurrent.CompletableFuture;
1920
import java.util.concurrent.CountDownLatch;
2021
import java.util.concurrent.ExecutorService;
@@ -34,7 +35,6 @@
3435
import itest.util.ITestCleanupFailedException;
3536
import org.hamcrest.MatcherAssert;
3637
import org.hamcrest.Matchers;
37-
import org.junit.jupiter.api.Assertions;
3838
import org.junit.jupiter.api.Test;
3939

4040
@QuarkusTest
@@ -121,7 +121,7 @@ void testSaveEmptyRecordingDoesNotArchiveRecordingFile() throws Exception {
121121
}
122122
});
123123
JsonArray listResp = listRespFuture1.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
124-
Assertions.assertTrue(listResp.isEmpty());
124+
MatcherAssert.assertThat(listResp.getList(), Matchers.equalTo(List.of()));
125125

126126
} finally {
127127
// Clean up recording

0 commit comments

Comments
 (0)