Path: blob/master/test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java
40942 views
/*1* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/222324/*25* @test26* @summary Test JFR recording controlled via JMX across container boundary.27* @requires docker.support28* @library /test/lib29* @modules java.base/jdk.internal.misc30* java.management31* jdk.jartool/sun.tools.jar32* @build EventProducer33* @run main TestJFRWithJMX34*/3536import java.io.BufferedOutputStream;37import java.io.File;38import java.io.FileOutputStream;39import java.io.IOException;40import java.lang.management.ManagementFactory;41import java.time.Instant;42import java.util.concurrent.atomic.AtomicReference;43import java.util.function.Consumer;4445import javax.management.MBeanServerConnection;46import javax.management.remote.JMXServiceURL;47import javax.management.remote.JMXConnectorFactory;48import javax.management.remote.JMXConnector;4950import jdk.jfr.consumer.RecordedEvent;51import jdk.jfr.consumer.RecordingFile;52import jdk.management.jfr.FlightRecorderMXBean;5354import jdk.test.lib.Asserts;55import jdk.test.lib.Container;56import jdk.test.lib.Platform;57import jdk.test.lib.Utils;58import jdk.test.lib.containers.docker.Common;59import jdk.test.lib.containers.docker.DockerRunOptions;60import jdk.test.lib.containers.docker.DockerTestUtils;61import jdk.test.lib.process.ProcessTools;6263import jtreg.SkippedException;646566public class TestJFRWithJMX {67static final String imageName = Common.imageName("jfr-jmx");68static final int PORT = 9010;69static final int HOW_LONG_TO_RECORD_SECONDS = 10;7071static final AtomicReference<String> ipAddr = new AtomicReference();7273public static void main(String[] args) throws Exception {74if (!DockerTestUtils.canTestDocker()) {75throw new SkippedException("Docker is not supported on this host");76}7778if (isPodman() & !Platform.isRoot()) {79throw new SkippedException("test cannot be run under rootless podman configuration");80}8182DockerTestUtils.buildJdkDockerImage(imageName, "Dockerfile-BasicTest", "jdk-docker");8384try {85test();86} finally {87DockerTestUtils.removeDockerImage(imageName);88}89}9091static void test() throws Exception {92String containerName = "jmx-jfr-observee" + Instant.now().toString().replace(':', '-');93ProcessBuilder pb = buildDockerJavaProcess(containerName);94Process p = ProcessTools.startProcess("monitored-container", pb, outputConsumer);9596// wait for the target process to communicate the IP address97while(ipAddr.get() == null) {98Thread.sleep(100);99}100101File transferredRecording = null;102try {103try ( JMXConnector connector = waitForJmxConnection(ipAddr.get(), PORT) ) {104FlightRecorderMXBean bean = getJfrBean(connector);105106long recordingId = record(bean, HOW_LONG_TO_RECORD_SECONDS * 1000);107long streamId = bean.openStream(recordingId, null);108transferredRecording = transferRecording(bean, streamId);109110bean.closeStream(streamId);111bean.closeRecording(recordingId);112}113} finally {114killContainer(containerName);115p.waitFor();116}117118System.out.println("Recording was transferred to: " + transferredRecording.getPath());119verifyRecording(transferredRecording);120}121122static ProcessBuilder buildDockerJavaProcess(String containerName) throws Exception {123DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "EventProducer")124.addDockerOpts("--name", containerName)125.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")126.addDockerOpts("--hostname", "jmx-jfr-test")127.addDockerOpts("-p", "" + PORT + ":" + PORT)128.addJavaOpts("-cp", "/test-classes/")129.addJavaOpts("-Dcom.sun.management.jmxremote", "-Dcom.sun.management.jmxremote.port=" + PORT)130.addJavaOpts("-Dcom.sun.management.jmxremote.local.only=false")131.addJavaOpts("-Dcom.sun.management.jmxremote.authenticate=false")132.addJavaOpts("-Dcom.sun.management.jmxremote.ssl=false");133134return new ProcessBuilder(DockerTestUtils.buildJavaCommand(opts));135}136137static long record(FlightRecorderMXBean bean, int howLong) throws Exception {138long id = bean.newRecording();139bean.setPredefinedConfiguration(id, "default");140bean.startRecording(id);141Thread.sleep(howLong);142143bean.stopRecording(id);144145String fn = "/tmp/recording-" + ProcessHandle.current().pid() + ".jfr";146bean.copyTo(id, fn);147System.out.println("Wrote recording to: " + fn);148return id;149}150151static void verifyRecording(File f) throws Exception {152boolean foundExpectedEvent = false;153String expectedEventName = "EventProducer$SimpleEvent";154155try (RecordingFile recordingFile = new RecordingFile(f.toPath())) {156while (recordingFile.hasMoreEvents()) {157RecordedEvent event = recordingFile.readEvent();158if(event.getEventType().getName().equals(expectedEventName)) {159foundExpectedEvent = true;160break;161}162}163164Asserts.assertTrue(foundExpectedEvent,165"Could not find the expected event in the recording: " +166expectedEventName);167}168}169170static void killContainer(String containerName) throws Exception {171new ProcessBuilder(Container.ENGINE_COMMAND, "kill", containerName)172.start()173.waitFor();174}175176static Consumer<String> outputConsumer = s -> {177if (ipAddr.get() != null) {178return;179}180181if (s.contains(EventProducer.HOST_ADDR_TAG)) {182String ip = s.replace(EventProducer.HOST_ADDR_TAG, "");183System.out.println("Observee ip: " + ip);184ipAddr.set(ip);185}186};187188// try connecting in a loop, it may take some time for target process to be ready for JMX connection189static JMXConnector waitForJmxConnection(String host, int port) throws Exception {190String urlPath = "/jndi/rmi://" + host + ":" + port + "/jmxrmi";191JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);192while (true) {193try {194return JMXConnectorFactory.connect(url);195} catch (IOException e) {196System.out.println("establishJmxConnection() thrown IOException: " + e.getMessage());197}198Thread.sleep(1000);199}200}201202static FlightRecorderMXBean getJfrBean(JMXConnector connector) throws Exception {203MBeanServerConnection connection = connector.getMBeanServerConnection();204return ManagementFactory.newPlatformMXBeanProxy(connection,205"jdk.management.jfr:type=FlightRecorder",206FlightRecorderMXBean.class);207}208209static File transferRecording(FlightRecorderMXBean bean, long streamId) throws Exception {210File f = Utils.createTempFile("recording-" + streamId + "-", ".jfr").toFile();211try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f))) {212while (true) {213byte[] data = bean.readStream(streamId);214if (data == null) {215bos.flush();216return f;217}218bos.write(data);219}220}221}222223static boolean isPodman() {224String[] parts = Container.ENGINE_COMMAND225.toLowerCase()226.split(File.pathSeparator);227return "podman".equals(parts[parts.length - 1]);228}229}230231232