1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.BufferedReader; 25 import java.io.File; 26 import java.io.IOException; 27 import java.io.UncheckedIOException; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.nio.file.attribute.UserPrincipal; 32 import java.time.Duration; 33 import java.time.Instant; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Objects; 38 import java.util.Optional; 39 import java.util.Random; 40 import java.util.concurrent.TimeUnit; 41 42 import jdk.test.lib.Platform; 43 import jdk.test.lib.Utils; 44 import org.testng.Assert; 45 import org.testng.TestNG; 46 import org.testng.annotations.Test; 47 48 /* 49 * @test 50 * @bug 8077350 8081566 8081567 8098852 8136597 51 * @library /test/lib 52 * @modules java.base/jdk.internal.misc 53 * jdk.management 54 * @run testng InfoTest 55 * @summary Functions of ProcessHandle.Info 56 * @author Roger Riggs 57 */ 58 59 public class InfoTest { 60 61 static String whoami; 62 63 static { 64 try { 65 // Create a file and take the username from the file 66 Path p = Paths.get("OwnerName.tmp"); 67 Files.createFile(p); 68 UserPrincipal owner = Files.getOwner(p); 69 whoami = owner.getName(); 70 Files.delete(p); 71 } catch (IOException ex) { 72 ex.printStackTrace(); 73 throw new UncheckedIOException("tmp file", ex); 74 } 75 } 76 77 // Main can be used to run the tests from the command line with only testng.jar. 78 @SuppressWarnings("raw_types") 79 public static void main(String[] args) { 80 Class<?>[] testclass = {InfoTest.class}; 81 TestNG testng = new TestNG(); 82 testng.setTestClasses(testclass); 83 testng.run(); 84 } 85 86 /** 87 * Test that cputime used shows up in ProcessHandle.info 88 */ 89 @Test 90 public static void test1() { 91 System.out.println("Note: when run in samevm mode the cputime of the " + 92 "test runner is included."); 93 ProcessHandle self = ProcessHandle.current(); 94 95 Duration someCPU = Duration.ofMillis(200L); 96 Instant end = Instant.now().plus(someCPU); 97 while (Instant.now().isBefore(end)) { 98 // waste the cpu 99 } 100 ProcessHandle.Info info = self.info(); 101 System.out.printf(" info: %s%n", info); 102 Optional<Duration> totalCpu = info.totalCpuDuration(); 103 if (totalCpu.isPresent() && (totalCpu.get().compareTo(someCPU) < 0)) { 104 Assert.fail("reported cputime less than expected: " + someCPU + ", " + 105 "actual: " + info.totalCpuDuration()); 106 } 107 } 108 109 /** 110 * Spawn a child with arguments and check they are visible via the ProcessHandle. 111 */ 112 @Test 113 public static void test2() { 114 try { 115 long cpuLoopTime = 100; // 100 ms 116 String[] extraArgs = {"pid", "parent", "stdin"}; 117 JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs); 118 Instant afterStart = null; 119 120 try (BufferedReader lines = p1.outputReader()) { 121 // Read the args line to know the subprocess has started 122 lines.readLine(); 123 afterStart = Instant.now(); 124 125 Duration lastCpu = Duration.ofMillis(0L); 126 for (int j = 0; j < 10; j++) { 127 128 p1.sendAction("cpuloop", cpuLoopTime); 129 p1.sendAction("cputime", ""); 130 131 // Read cputime from child 132 Duration childCpuTime = null; 133 // Read lines from the child until the result from cputime is returned 134 for (String s; (s = lines.readLine()) != null;) { 135 String[] split = s.trim().split(" "); 136 if (split.length == 3 && split[1].equals("cputime")) { 137 long nanos = Long.valueOf(split[2]); 138 childCpuTime = Duration.ofNanos(nanos); 139 break; // found the result we're looking for 140 } 141 } 142 143 if (Platform.isAix()) { 144 // Unfortunately, on AIX the usr/sys times reported through 145 // /proc/<pid>/psinfo which are used by ProcessHandle.Info 146 // are running slow compared to the corresponding times reported 147 // by the times()/getrusage() system calls which are used by 148 // OperatingSystemMXBean.getProcessCpuTime() and returned by 149 // the JavaChild for the "cputime" command. 150 // This is because /proc/<pid>/status is only updated once a second. 151 // So we better wait a little bit to get plausible values here. 152 Thread.sleep(1000); 153 } 154 ProcessHandle.Info info = p1.info(); 155 System.out.printf(" info: %s%n", info); 156 157 if (info.user().isPresent()) { 158 String user = info.user().get(); 159 Assert.assertNotNull(user, "User name"); 160 Assert.assertEquals(user, whoami, "User name"); 161 } 162 163 Optional<String> command = info.command(); 164 if (command.isPresent()) { 165 String javaExe = System.getProperty("test.jdk") + 166 File.separator + "bin" + File.separator + "java"; 167 String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe; 168 Path expectedPath = Paths.get(expected); 169 Path actualPath = Paths.get(command.get()); 170 Assert.assertTrue(Files.isSameFile(expectedPath, actualPath), 171 "Command: expected: " + javaExe + ", actual: " + command.get()); 172 } 173 174 if (info.arguments().isPresent()) { 175 String[] args = info.arguments().get(); 176 177 int offset = args.length - extraArgs.length; 178 for (int i = 0; i < extraArgs.length; i++) { 179 Assert.assertEquals(args[offset + i], extraArgs[i], 180 "Actual argument mismatch, index: " + i); 181 } 182 183 // Now check that the first argument is not the same as the executed command 184 if (args.length > 0) { 185 Assert.assertNotEquals(args[0], command, 186 "First argument should not be the executable: args[0]: " 187 + args[0] + ", command: " + command); 188 } 189 } 190 191 if (command.isPresent() && info.arguments().isPresent()) { 192 // If both, 'command' and 'arguments' are present, 193 // 'commandLine' is just the concatenation of the two. 194 Assert.assertTrue(info.commandLine().isPresent(), 195 "commandLine() must be available"); 196 197 String javaExe = System.getProperty("test.jdk") + 198 File.separator + "bin" + File.separator + "java"; 199 String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe; 200 Path expectedPath = Paths.get(expected); 201 String commandLine = info.commandLine().get(); 202 String commandLineCmd = commandLine.split(" ")[0]; 203 Path commandLineCmdPath = Paths.get(commandLineCmd); 204 Assert.assertTrue(Files.isSameFile(commandLineCmdPath, expectedPath), 205 "commandLine() should start with: " + expectedPath + 206 " but starts with " + commandLineCmdPath); 207 208 Assert.assertTrue(commandLine.contains(command.get()), 209 "commandLine() must contain the command: " + command.get()); 210 List<String> allArgs = p1.getArgs(); 211 for (int i = 1; i < allArgs.size(); i++) { 212 Assert.assertTrue(commandLine.contains(allArgs.get(i)), 213 "commandLine() must contain argument: " + allArgs.get(i)); 214 } 215 } else if (info.commandLine().isPresent() && 216 command.isPresent() && 217 command.get().length() < info.commandLine().get().length()) { 218 // If we only have the commandLine() we can only do some basic checks... 219 String commandLine = info.commandLine().get(); 220 String javaExe = "java" + (Platform.isWindows() ? ".exe" : ""); 221 int pos = commandLine.indexOf(javaExe); 222 Assert.assertTrue(pos > 0, "commandLine() should at least contain 'java'"); 223 224 pos += javaExe.length() + 1; // +1 for the space after the command 225 List<String> allArgs = p1.getArgs(); 226 // First argument is the command - skip it here as we've already checked that. 227 for (int i = 1; (i < allArgs.size()) && 228 (pos + allArgs.get(i).length() < commandLine.length()); i++) { 229 Assert.assertTrue(commandLine.contains(allArgs.get(i)), 230 "commandLine() must contain argument: " + allArgs.get(i)); 231 pos += allArgs.get(i).length() + 1; 232 } 233 } 234 235 if (info.totalCpuDuration().isPresent()) { 236 Duration totalCPU = info.totalCpuDuration().get(); 237 Duration epsilon = Duration.ofMillis(200L); 238 if (childCpuTime != null) { 239 System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n", 240 totalCPU.toNanos(), childCpuTime.toNanos(), 241 childCpuTime.toNanos() - totalCPU.toNanos()); 242 Assert.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon), 243 childCpuTime + " should be within " + 244 epsilon + " of " + totalCPU); 245 } 246 Assert.assertTrue(totalCPU.toNanos() > 0L, 247 "total cpu time expected > 0ms, actual: " + totalCPU); 248 long t = Utils.adjustTimeout(10L); // Adjusted timeout seconds 249 Assert.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + t * 1_000_000_000L, 250 "total cpu time expected < " + t 251 + " seconds more than previous iteration, actual: " 252 + (totalCPU.toNanos() - lastCpu.toNanos())); 253 lastCpu = totalCPU; 254 } 255 256 if (info.startInstant().isPresent()) { 257 Instant startTime = info.startInstant().get(); 258 Assert.assertTrue(startTime.isBefore(afterStart), 259 "startTime after process spawn completed" 260 + startTime + " + > " + afterStart); 261 } 262 } 263 } 264 p1.sendAction("exit"); 265 Assert.assertTrue(p1.waitFor(Utils.adjustTimeout(30L), TimeUnit.SECONDS), 266 "timeout waiting for process to terminate"); 267 } catch (IOException | InterruptedException ie) { 268 ie.printStackTrace(System.out); 269 Assert.fail("unexpected exception", ie); 270 } finally { 271 // Destroy any children that still exist 272 ProcessUtil.destroyProcessTree(ProcessHandle.current()); 273 } 274 } 275 276 /** 277 * Spawn a child with arguments and check they are visible via the ProcessHandle. 278 */ 279 @Test 280 public static void test3() { 281 try { 282 for (long sleepTime : Arrays.asList(Utils.adjustTimeout(30), Utils.adjustTimeout(32))) { 283 Process p = spawn("sleep", String.valueOf(sleepTime)); 284 285 ProcessHandle.Info info = p.info(); 286 System.out.printf(" info: %s%n", info); 287 288 if (info.user().isPresent()) { 289 String user = info.user().get(); 290 Assert.assertNotNull(user); 291 Assert.assertEquals(user, whoami); 292 } 293 if (info.command().isPresent()) { 294 String command = info.command().get(); 295 String expected = Platform.isWindows() ? "sleep.exe" : "sleep"; 296 Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" + 297 expected + "\', actual: " + command); 298 299 // Verify the command exists and is executable 300 File exe = new File(command); 301 Assert.assertTrue(exe.exists(), "command must exist: " + exe); 302 Assert.assertTrue(exe.canExecute(), "command must be executable: " + exe); 303 } 304 if (info.arguments().isPresent()) { 305 String[] args = info.arguments().get(); 306 if (args.length > 0) { 307 Assert.assertEquals(args[0], String.valueOf(sleepTime)); 308 } 309 } 310 p.destroy(); 311 Assert.assertTrue(p.waitFor(Utils.adjustTimeout(30), TimeUnit.SECONDS), 312 "timeout waiting for process to terminate"); 313 } 314 } catch (IOException | InterruptedException ex) { 315 ex.printStackTrace(System.out); 316 } finally { 317 // Destroy any children that still exist 318 ProcessUtil.destroyProcessTree(ProcessHandle.current()); 319 } 320 } 321 322 /** 323 * Cross check the cputime reported from java.management with that for the current process. 324 */ 325 @Test 326 public static void test4() { 327 Duration myCputime1 = ProcessUtil.MXBeanCpuTime(); 328 329 if (Platform.isAix()) { 330 // Unfortunately, on AIX the usr/sys times reported through 331 // /proc/<pid>/psinfo which are used by ProcessHandle.Info 332 // are running slow compared to the corresponding times reported 333 // by the times()/getrusage() system calls which are used by 334 // OperatingSystemMXBean.getProcessCpuTime() and returned by 335 // the JavaChild for the "cputime" command. 336 // So we better wait a little bit to get plausible values here. 337 try { 338 Thread.sleep(1000); 339 } catch (InterruptedException ex) {} 340 } 341 Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration(); 342 343 Duration myCputime2 = ProcessUtil.MXBeanCpuTime(); 344 345 if (Platform.isAix()) { 346 try { 347 Thread.sleep(1000); 348 } catch (InterruptedException ex) {} 349 } 350 Optional<Duration> dur2 = ProcessHandle.current().info().totalCpuDuration(); 351 352 if (dur1.isPresent() && dur2.isPresent()) { 353 Duration total1 = dur1.get(); 354 Duration total2 = dur2.get(); 355 System.out.printf(" total1 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n", 356 Objects.toString(total1), myCputime1, myCputime1.minus(total1)); 357 System.out.printf(" total2 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n", 358 Objects.toString(total2), myCputime2, myCputime2.minus(total2)); 359 360 Duration epsilon = Duration.ofMillis(200L); // Epsilon is 200ms. 361 Assert.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon), 362 myCputime1.toNanos() + " should be within " + epsilon 363 + " of " + myCputime2.toNanos()); 364 Assert.assertTrue(checkEpsilon(total1, total2, epsilon), 365 total1.toNanos() + " should be within " + epsilon 366 + " of " + total2.toNanos()); 367 Assert.assertTrue(checkEpsilon(myCputime1, total1, epsilon), 368 myCputime1.toNanos() + " should be within " + epsilon 369 + " of " + total1.toNanos()); 370 Assert.assertTrue(checkEpsilon(total1, myCputime2, epsilon), 371 total1.toNanos() + " should be within " + epsilon 372 + " of " + myCputime2.toNanos()); 373 Assert.assertTrue(checkEpsilon(myCputime2, total2, epsilon), 374 myCputime2.toNanos() + " should be within " + epsilon 375 + " of " + total2.toNanos()); 376 } 377 } 378 379 @Test 380 public static void test5() { 381 ProcessHandle self = ProcessHandle.current(); 382 Random r = new Random(); 383 for (int i = 0; i < 30; i++) { 384 Instant end = Instant.now().plusMillis(500L); 385 while (end.isBefore(Instant.now())) { 386 // burn the cpu time checking the time 387 long x = r.nextLong(); 388 } 389 if (self.info().totalCpuDuration().isPresent()) { 390 Duration totalCpu = self.info().totalCpuDuration().get(); 391 long infoTotalCputime = totalCpu.toNanos(); 392 long beanCputime = ProcessUtil.MXBeanCpuTime().toNanos(); 393 System.out.printf(" infoTotal: %12d, beanCpu: %12d, diff: %12d%n", 394 infoTotalCputime, beanCputime, beanCputime - infoTotalCputime); 395 } else { 396 break; // nothing to compare; continue 397 } 398 } 399 } 400 /** 401 * Check two Durations, the second should be greater than the first or 402 * within the supplied Epsilon. 403 * @param d1 a Duration - presumed to be shorter 404 * @param d2 a 2nd Duration - presumed to be greater (or within Epsilon) 405 * @param epsilon Epsilon the amount of overlap allowed 406 * @return true if d2 is greater than d1 or within epsilon, false otherwise 407 */ 408 static boolean checkEpsilon(Duration d1, Duration d2, Duration epsilon) { 409 if (d1.toNanos() <= d2.toNanos()) { 410 return true; 411 } 412 Duration diff = d1.minus(d2).abs(); 413 return diff.compareTo(epsilon) <= 0; 414 } 415 416 /** 417 * Spawn a native process with the provided arguments. 418 * @param command the executable of native process 419 * @param args to start a new process 420 * @return the Process that was started 421 * @throws IOException thrown by ProcessBuilder.start 422 */ 423 static Process spawn(String command, String... args) throws IOException { 424 ProcessBuilder pb = new ProcessBuilder(); 425 pb.inheritIO(); 426 List<String> list = new ArrayList<>(); 427 list.add(command); 428 for (String arg : args) 429 list.add(arg); 430 pb.command(list); 431 return pb.start(); 432 } 433 }