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