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