test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java
changeset 57954 fd09c637dedb
parent 57502 650335128b9d
child 58550 e98509cb3867
equal deleted inserted replaced
57953:d78c910f9069 57954:fd09c637dedb
    35  *          java.management
    35  *          java.management
    36  *          jdk.jartool/sun.tools.jar
    36  *          jdk.jartool/sun.tools.jar
    37  * @build EventGeneratorLoop
    37  * @build EventGeneratorLoop
    38  * @run driver TestJcmdWithSideCar
    38  * @run driver TestJcmdWithSideCar
    39  */
    39  */
    40 import java.nio.file.Path;
       
    41 import java.nio.file.Paths;
    40 import java.nio.file.Paths;
    42 import java.util.Arrays;
    41 import java.util.Arrays;
    43 import java.util.ArrayList;
    42 import java.util.ArrayList;
    44 import java.util.List;
    43 import java.util.List;
       
    44 import java.util.concurrent.TimeUnit;
       
    45 import java.util.function.Consumer;
    45 import java.util.stream.Collectors;
    46 import java.util.stream.Collectors;
    46 import jdk.test.lib.Container;
    47 import jdk.test.lib.Container;
    47 import jdk.test.lib.Utils;
    48 import jdk.test.lib.Utils;
    48 import jdk.test.lib.containers.docker.Common;
    49 import jdk.test.lib.containers.docker.Common;
    49 import jdk.test.lib.containers.docker.DockerRunOptions;
    50 import jdk.test.lib.containers.docker.DockerRunOptions;
    50 import jdk.test.lib.containers.docker.DockerTestUtils;
    51 import jdk.test.lib.containers.docker.DockerTestUtils;
    51 import jdk.test.lib.process.OutputAnalyzer;
    52 import jdk.test.lib.process.OutputAnalyzer;
       
    53 import jdk.test.lib.process.ProcessTools;
    52 
    54 
    53 
    55 
    54 public class TestJcmdWithSideCar {
    56 public class TestJcmdWithSideCar {
    55     private static final String IMAGE_NAME = Common.imageName("jfr-jcmd");
    57     private static final String IMAGE_NAME = Common.imageName("jfr-jcmd");
    56     private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds
    58     private static final int TIME_TO_RUN_MAIN_PROCESS = (int) (30 * Utils.TIMEOUT_FACTOR); // seconds
       
    59     private static final long TIME_TO_WAIT_FOR_MAIN_METHOD_START = 5 * 1000; // milliseconds
    57     private static final String MAIN_CONTAINER_NAME = "test-container-main";
    60     private static final String MAIN_CONTAINER_NAME = "test-container-main";
    58 
    61 
    59     public static void main(String[] args) throws Exception {
    62     public static void main(String[] args) throws Exception {
    60         if (!DockerTestUtils.canTestDocker()) {
    63         if (!DockerTestUtils.canTestDocker()) {
    61             return;
    64             return;
    64         DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker");
    67         DockerTestUtils.buildJdkDockerImage(IMAGE_NAME, "Dockerfile-BasicTest", "jdk-docker");
    65 
    68 
    66         try {
    69         try {
    67             // Start the loop process in the "main" container, then run test cases
    70             // Start the loop process in the "main" container, then run test cases
    68             // using a sidecar container.
    71             // using a sidecar container.
    69             DockerThread t = startMainContainer();
    72             MainContainer mainContainer = new MainContainer();
    70 
    73             mainContainer.start();
    71             waitForMainContainerToStart(500, 10);
    74             mainContainer.waitForMainMethodStart(TIME_TO_WAIT_FOR_MAIN_METHOD_START);
    72             t.checkForErrors();
    75 
    73 
    76             long mainProcPid = testCase01();
    74             OutputAnalyzer jcmdOut = testCase01();
    77 
    75             long mainProcPid = findProcess(jcmdOut, "EventGeneratorLoop");
    78             // Excluding the test case below until JDK-8228850 is fixed
    76 
    79             // JDK-8228850: jhsdb jinfo fails with ClassCastException:
    77             t.assertIsAlive();
    80             // s.j.h.oops.TypeArray cannot be cast to s.j.h.oops.Instance
    78             testCase02(mainProcPid);
    81             // mainContainer.assertIsAlive();
       
    82             // testCase02(mainProcPid);
    79 
    83 
    80             // JCMD does not work in sidecar configuration, except for "jcmd -l".
    84             // JCMD does not work in sidecar configuration, except for "jcmd -l".
    81             // Including this test case to assist in reproduction of the problem.
    85             // Including this test case to assist in reproduction of the problem.
    82             // t.assertIsAlive();
    86             // mainContainer.assertIsAlive();
    83             // testCase03(mainProcPid);
    87             // testCase03(mainProcPid);
    84 
    88 
    85             t.join(TIME_TO_RUN_MAIN_PROCESS * 1000);
    89             mainContainer.waitForAndCheck(TIME_TO_RUN_MAIN_PROCESS * 1000);
    86             t.checkForErrors();
       
    87         } finally {
    90         } finally {
    88             DockerTestUtils.removeDockerImage(IMAGE_NAME);
    91             DockerTestUtils.removeDockerImage(IMAGE_NAME);
    89         }
    92         }
    90     }
    93     }
    91 
    94 
    92 
    95 
    93     // Run "jcmd -l" in a sidecar container and find a process that runs EventGeneratorLoop
    96     // Run "jcmd -l" in a sidecar container, find a target process.
    94     private static OutputAnalyzer testCase01() throws Exception {
    97     private static long testCase01() throws Exception {
    95         return runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l")
    98         OutputAnalyzer out = runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "-l")
    96             .shouldHaveExitValue(0)
    99             .shouldHaveExitValue(0)
    97             .shouldContain("sun.tools.jcmd.JCmd")
   100             .shouldContain("sun.tools.jcmd.JCmd");
    98             .shouldContain("EventGeneratorLoop");
   101         long pid = findProcess(out, "EventGeneratorLoop");
       
   102         if (pid == -1) {
       
   103             throw new RuntimeException("Could not find specified process");
       
   104         }
       
   105 
       
   106         return pid;
    99     }
   107     }
   100 
   108 
   101     // run jhsdb jinfo <PID> (jhsdb uses PTRACE)
   109     // run jhsdb jinfo <PID> (jhsdb uses PTRACE)
   102     private static void testCase02(long pid) throws Exception {
   110     private static void testCase02(long pid) throws Exception {
   103         runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid)
   111         runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jhsdb", "jinfo", "--pid", "" + pid)
   115         runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start")
   123         runSideCar(MAIN_CONTAINER_NAME, "/jdk/bin/jcmd", "" + pid, "JFR.start")
   116             .shouldHaveExitValue(0)
   124             .shouldHaveExitValue(0)
   117             .shouldContain("Started recording");
   125             .shouldContain("Started recording");
   118     }
   126     }
   119 
   127 
   120     private static DockerThread startMainContainer() throws Exception {
       
   121         // start "main" container (the observee)
       
   122         DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop");
       
   123         opts.addDockerOpts("--cap-add=SYS_PTRACE")
       
   124             .addDockerOpts("--name", MAIN_CONTAINER_NAME)
       
   125             .addDockerOpts("-v", "/tmp")
       
   126             .addJavaOpts("-XX:+UsePerfData")
       
   127             .addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS);
       
   128         DockerThread t = new DockerThread(opts);
       
   129         t.start();
       
   130 
       
   131         return t;
       
   132     }
       
   133 
       
   134     private static void waitForMainContainerToStart(int delayMillis, int count) throws Exception {
       
   135         boolean started = false;
       
   136         for(int i=0; i < count; i++) {
       
   137             try {
       
   138                 Thread.sleep(delayMillis);
       
   139             } catch (InterruptedException e) {}
       
   140             if (isMainContainerRunning()) {
       
   141                 started = true;
       
   142                 break;
       
   143             }
       
   144         }
       
   145         if (!started) {
       
   146             throw new RuntimeException("Main container did not start");
       
   147         }
       
   148     }
       
   149 
       
   150     private static boolean isMainContainerRunning() throws Exception {
       
   151         OutputAnalyzer out =
       
   152             DockerTestUtils.execute(Container.ENGINE_COMMAND,
       
   153                                     "ps", "--no-trunc",
       
   154                                     "--filter", "name=" + MAIN_CONTAINER_NAME);
       
   155         return out.getStdout().contains(MAIN_CONTAINER_NAME);
       
   156     }
       
   157 
   128 
   158     // JCMD relies on the attach mechanism (com.sun.tools.attach),
   129     // JCMD relies on the attach mechanism (com.sun.tools.attach),
   159     // which in turn relies on JVMSTAT mechanism, which puts its mapped
   130     // which in turn relies on JVMSTAT mechanism, which puts its mapped
   160     // buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar
   131     // buffers in /tmp directory (hsperfdata_<user>). Thus, in sidecar
   161     // we mount /tmp via --volumes-from from the main container.
   132     // we mount /tmp via --volumes-from from the main container.
   162     private static OutputAnalyzer runSideCar(String MAIN_CONTAINER_NAME, String whatToRun,
   133     private static OutputAnalyzer runSideCar(String mainContainerName, String whatToRun,
   163                                              String... args) throws Exception {
   134                                              String... args) throws Exception {
   164         List<String> cmd = new ArrayList<>();
   135         List<String> cmd = new ArrayList<>();
   165         String[] command = new String[] {
   136         String[] command = new String[] {
   166             Container.ENGINE_COMMAND, "run",
   137             Container.ENGINE_COMMAND, "run",
   167             "--tty=true", "--rm",
   138             "--tty=true", "--rm",
   168             "--cap-add=SYS_PTRACE", "--sig-proxy=true",
   139             "--cap-add=SYS_PTRACE", "--sig-proxy=true",
   169             "--pid=container:" + MAIN_CONTAINER_NAME,
   140             "--pid=container:" + mainContainerName,
   170             "--volumes-from", MAIN_CONTAINER_NAME,
   141             "--volumes-from", mainContainerName,
   171             IMAGE_NAME, whatToRun
   142             IMAGE_NAME, whatToRun
   172         };
   143         };
   173 
   144 
   174         cmd.addAll(Arrays.asList(command));
   145         cmd.addAll(Arrays.asList(command));
   175         cmd.addAll(Arrays.asList(args));
   146         cmd.addAll(Arrays.asList(args));
   176         return DockerTestUtils.execute(cmd);
   147         return DockerTestUtils.execute(cmd);
   177     }
   148     }
   178 
   149 
       
   150     // Returns PID of a matching process, or -1 if not found.
   179     private static long findProcess(OutputAnalyzer out, String name) throws Exception {
   151     private static long findProcess(OutputAnalyzer out, String name) throws Exception {
   180         List<String> l = out.asLines()
   152         List<String> l = out.asLines()
   181             .stream()
   153             .stream()
   182             .filter(s -> s.contains(name))
   154             .filter(s -> s.contains(name))
   183             .collect(Collectors.toList());
   155             .collect(Collectors.toList());
   184         if (l.isEmpty()) {
   156         if (l.isEmpty()) {
   185             throw new RuntimeException("Could not find matching process");
   157             return -1;
   186         }
   158         }
   187         String psInfo = l.get(0);
   159         String psInfo = l.get(0);
   188         System.out.println("findProcess(): psInfo: " + psInfo);
   160         System.out.println("findProcess(): psInfo: " + psInfo);
   189         String pid = psInfo.substring(0, psInfo.indexOf(' '));
   161         String pid = psInfo.substring(0, psInfo.indexOf(' '));
   190         System.out.println("findProcess(): pid: " + pid);
   162         System.out.println("findProcess(): pid: " + pid);
   195         return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className)
   167         return new DockerRunOptions(IMAGE_NAME, "/jdk/bin/java", className)
   196             .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
   168             .addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/")
   197             .addJavaOpts("-cp", "/test-classes/");
   169             .addJavaOpts("-cp", "/test-classes/");
   198     }
   170     }
   199 
   171 
   200 
   172     private static void sleep(long delay) {
   201     static class DockerThread extends Thread {
   173         try {
   202         DockerRunOptions runOpts;
   174             Thread.sleep(delay);
   203         Exception exception;
   175         } catch (InterruptedException e) {
   204 
   176             System.out.println("InterruptedException" + e.getMessage());
   205         DockerThread(DockerRunOptions opts) {
   177         }
   206             runOpts = opts;
   178     }
   207         }
   179 
   208 
   180 
   209         public void run() {
   181     static class MainContainer {
   210             try {
   182         boolean mainMethodStarted;
   211                 DockerTestUtils.dockerRunJava(runOpts);
   183         Process p;
   212             } catch (Exception e) {
   184 
   213                 exception = e;
   185         private Consumer<String> outputConsumer = s -> {
       
   186             if (!mainMethodStarted && s.contains(EventGeneratorLoop.MAIN_METHOD_STARTED)) {
       
   187                 System.out.println("MainContainer: setting mainMethodStarted");
       
   188                 mainMethodStarted = true;
   214             }
   189             }
       
   190         };
       
   191 
       
   192         public Process start() throws Exception {
       
   193             // start "main" container (the observee)
       
   194             DockerRunOptions opts = commonDockerOpts("EventGeneratorLoop");
       
   195             opts.addDockerOpts("--cap-add=SYS_PTRACE")
       
   196                 .addDockerOpts("--name", MAIN_CONTAINER_NAME)
       
   197                 .addDockerOpts("--volume", "/tmp")
       
   198                 .addDockerOpts("--volume", Paths.get(".").toAbsolutePath() + ":/workdir/")
       
   199                 .addJavaOpts("-XX:+UsePerfData")
       
   200                 .addClassOptions("" + TIME_TO_RUN_MAIN_PROCESS);
       
   201 
       
   202             List<String> cmd = DockerTestUtils.buildJavaCommand(opts);
       
   203             ProcessBuilder pb = new ProcessBuilder(cmd);
       
   204             p = ProcessTools.startProcess("main-container-process",
       
   205                                           pb,
       
   206                                           outputConsumer);
       
   207             return p;
       
   208         }
       
   209 
       
   210         public void waitForMainMethodStart(long howLong) {
       
   211             long expiration = System.currentTimeMillis() + howLong;
       
   212 
       
   213             do {
       
   214                 if (mainMethodStarted) {
       
   215                     return;
       
   216                 }
       
   217                 sleep(200);
       
   218             } while (System.currentTimeMillis() < expiration);
       
   219 
       
   220             throw new RuntimeException("Timed out while waiting for main() to start");
   215         }
   221         }
   216 
   222 
   217         public void assertIsAlive() throws Exception {
   223         public void assertIsAlive() throws Exception {
   218             if (!isAlive()) {
   224             if (!p.isAlive()) {
       
   225                 throw new RuntimeException("Main container process stopped unexpectedly, exit value: "
       
   226                                            + p.exitValue());
       
   227             }
       
   228         }
       
   229 
       
   230         public void waitFor(long timeout) throws Exception {
       
   231             p.waitFor(timeout, TimeUnit.MILLISECONDS);
       
   232         }
       
   233 
       
   234         public void waitForAndCheck(long timeout) throws Exception {
       
   235             waitFor(timeout);
       
   236             if (p.exitValue() != 0) {
   219                 throw new RuntimeException("DockerThread stopped unexpectedly");
   237                 throw new RuntimeException("DockerThread stopped unexpectedly");
   220             }
   238             }
   221         }
   239         }
   222 
   240 
   223         public void checkForErrors() throws Exception {
       
   224             if (exception != null) {
       
   225                 throw new RuntimeException("DockerThread threw exception"
       
   226                                            + exception.getMessage());
       
   227             }
       
   228         }
       
   229     }
   241     }
   230 
   242 
   231 }
   243 }