diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2021-09-15 12:25:59 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2021-09-15 12:53:31 +0200 |
commit | c9cd995047022d19e416109a21d6ef45eaa33eaf (patch) | |
tree | 2d17ad5ef9b47b4427f32bbc653e51956690d5f0 /node-admin | |
parent | 2d2372a26535b38ebc013b6416a6c138cd993b7c (diff) |
Add artifact type for JFR recording
Diffstat (limited to 'node-admin')
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); |