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 } |