aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2021-09-15 12:25:59 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2021-09-15 12:53:31 +0200
commitc9cd995047022d19e416109a21d6ef45eaa33eaf (patch)
tree2d17ad5ef9b47b4427f32bbc653e51956690d5f0 /node-admin
parent2d2372a26535b38ebc013b6416a6c138cd993b7c (diff)
Add artifact type for JFR recording
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/JavaFlightRecorder.java51
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImpl.java8
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImplTest.java33
3 files changed, 87 insertions, 5 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/JavaFlightRecorder.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/JavaFlightRecorder.java
new file mode 100644
index 00000000000..289f5622aeb
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/JavaFlightRecorder.java
@@ -0,0 +1,51 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.maintenance.servicedump;
+
+import com.yahoo.concurrent.Sleeper;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
+import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * Creates a Java Flight Recorder dump.
+ *
+ * @author bjorncs
+ */
+class JavaFlightRecorder extends AbstractProducer {
+
+ private final Sleeper sleeper;
+
+ JavaFlightRecorder(ContainerOperations container, Sleeper sleeper) {
+ super(container);
+ this.sleeper = sleeper;
+ }
+
+ static String NAME = "jfr-recording";
+
+ @Override public String name() { return NAME; }
+
+ @Override
+ public void produceArtifact(NodeAgentContext ctx, String configId, ServiceDumpReport.DumpOptions options,
+ UnixPath resultDirectoryInNode) throws IOException {
+ int pid = findVespaServicePid(ctx, configId);
+ int seconds = (int) (duration(ctx, options, 30.0));
+ UnixPath outputFile = resultDirectoryInNode.resolve("recording.jfr");
+ List<String> startCommand = List.of("jcmd", Integer.toString(pid), "JFR.start", "name=host-admin",
+ "path-to-gc-roots=true", "settings=profile", "filename=" + outputFile, "duration=" + seconds + "s");
+ executeCommand(ctx, startCommand, true);
+ sleeper.sleep(Duration.ofSeconds(seconds).plusSeconds(1));
+ int maxRetries = 10;
+ List<String> checkCommand = List.of("jcmd", Integer.toString(pid), "JFR.check", "name=host-admin");
+ for (int i = 0; i < maxRetries; i++) {
+ boolean stillRunning = executeCommand(ctx, checkCommand, true).getOutputLines().stream()
+ .anyMatch(l -> l.contains("name=host-admin") && l.contains("running"));
+ if (!stillRunning) return;
+ sleeper.sleep(Duration.ofSeconds(1));
+ }
+ throw new IOException("Failed to wait for JFR dump to complete after " + maxRetries + " retries");
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImpl.java
index 83e661ec0fd..21964473cf1 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImpl.java
@@ -1,6 +1,7 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.servicedump;
+import com.yahoo.concurrent.Sleeper;
import com.yahoo.text.Lowercase;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeAttributes;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
@@ -42,19 +43,20 @@ public class VespaServiceDumperImpl implements VespaServiceDumper {
private final Map<String, ArtifactProducer> artifactProducers;
public VespaServiceDumperImpl(ContainerOperations container, SyncClient syncClient, NodeRepository nodeRepository) {
- this(container, syncClient, nodeRepository, Clock.systemUTC());
+ this(container, syncClient, nodeRepository, Clock.systemUTC(), Sleeper.DEFAULT);
}
// For unit testing
VespaServiceDumperImpl(ContainerOperations container, SyncClient syncClient, NodeRepository nodeRepository,
- Clock clock) {
+ Clock clock, Sleeper sleeper) {
this.container = container;
this.syncClient = syncClient;
this.nodeRepository = nodeRepository;
this.clock = clock;
List<AbstractProducer> producers = List.of(
new JvmDumpProducer(container),
- new PerfReportProducer(container));
+ new PerfReportProducer(container),
+ new JavaFlightRecorder(container, sleeper));
this.artifactProducers = producers.stream()
.collect(Collectors.toMap(ArtifactProducer::name, Function.identity()));
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImplTest.java
index 3b512130608..c42316774c2 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/servicedump/VespaServiceDumperImplTest.java
@@ -1,5 +1,6 @@
package com.yahoo.vespa.hosted.node.admin.maintenance.servicedump;// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+import com.yahoo.concurrent.Sleeper;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeState;
@@ -73,7 +74,7 @@ class VespaServiceDumperImplTest {
NodeSpec initialSpec = createNodeSpecWithDumpRequest(nodeRepository, JvmDumpProducer.NAME, null);
// Create dumper and invoke tested method
- VespaServiceDumper reporter = new VespaServiceDumperImpl(operations, syncClient, nodeRepository, clock);
+ VespaServiceDumper reporter = new VespaServiceDumperImpl(operations, syncClient, nodeRepository, clock, Sleeper.NOOP);
NodeAgentContextImpl context = new NodeAgentContextImpl.Builder(initialSpec)
.fileSystem(fileSystem)
.build();
@@ -106,7 +107,7 @@ class VespaServiceDumperImplTest {
ManualClock clock = new ManualClock(Instant.ofEpochMilli(1600001000000L));
NodeSpec nodeSpec = createNodeSpecWithDumpRequest(nodeRepository, PerfReportProducer.NAME, new ServiceDumpReport.DumpOptions(true, 45.0));
- VespaServiceDumper reporter = new VespaServiceDumperImpl(operations, syncClient, nodeRepository, clock);
+ VespaServiceDumper reporter = new VespaServiceDumperImpl(operations, syncClient, nodeRepository, clock, Sleeper.NOOP);
NodeAgentContextImpl context = new NodeAgentContextImpl.Builder(nodeSpec)
.fileSystem(fileSystem)
.build();
@@ -122,6 +123,34 @@ class VespaServiceDumperImplTest {
" > /opt/vespa/tmp/vespa-service-dump/perf-report/perf-report.txt");
}
+ @Test
+ void invokes_jcmd_commands_when_creating_jfr_recording() {
+ // Setup mocks
+ ContainerOperations operations = mock(ContainerOperations.class);
+ when(operations.executeCommandInContainerAsRoot(any(), any()))
+ .thenReturn(new CommandResult(null, 0, "12345"))
+ .thenReturn(new CommandResult(null, 0, "ok"))
+ .thenReturn(new CommandResult(null, 0, "name=host-admin success"));
+ SyncClient syncClient = createSyncClientMock();
+ NodeRepoMock nodeRepository = new NodeRepoMock();
+ ManualClock clock = new ManualClock(Instant.ofEpochMilli(1600001000000L));
+ NodeSpec nodeSpec = createNodeSpecWithDumpRequest(
+ nodeRepository, JavaFlightRecorder.NAME, new ServiceDumpReport.DumpOptions(null, null));
+
+ VespaServiceDumper reporter = new VespaServiceDumperImpl(operations, syncClient, nodeRepository, clock, Sleeper.NOOP);
+ NodeAgentContextImpl context = new NodeAgentContextImpl.Builder(nodeSpec)
+ .fileSystem(fileSystem)
+ .build();
+ reporter.processServiceDumpRequest(context);
+
+ verify(operations).executeCommandInContainerAsRoot(
+ context, "/opt/vespa/libexec/vespa/find-pid", "default/container.1");
+ verify(operations).executeCommandInContainerAsRoot(
+ context, "jcmd", "12345", "JFR.start", "name=host-admin", "path-to-gc-roots=true", "settings=profile",
+ "filename=/opt/vespa/tmp/vespa-service-dump/jfr-recording/recording.jfr", "duration=30s");
+ verify(operations).executeCommandInContainerAsRoot(context, "jcmd", "12345", "JFR.check", "name=host-admin");
+ }
+
private static NodeSpec createNodeSpecWithDumpRequest(NodeRepoMock repository, String artifactName, ServiceDumpReport.DumpOptions options) {
ServiceDumpReport request = ServiceDumpReport.createRequestReport(
Instant.ofEpochMilli(1600000000000L), null, "default/container.1", List.of(artifactName), options);