1 /*
   2  * Copyright (c) 2014, 2015, 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.File;
  25 import java.io.BufferedReader;
  26 import java.io.IOException;
  27 import java.io.UncheckedIOException;
  28 import java.lang.ProcessBuilder;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 import java.nio.file.Paths;
  32 import java.nio.file.attribute.UserPrincipal;
  33 import java.time.Duration;
  34 import java.time.Instant;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 import java.util.List;
  38 import java.util.Objects;
  39 import java.util.Optional;
  40 import java.util.Random;
  41 import java.util.concurrent.TimeUnit;
  42 
  43 import jdk.testlibrary.Platform;
  44 import jdk.testlibrary.Utils;
  45 
  46 import org.testng.Assert;
  47 import org.testng.annotations.Test;
  48 import org.testng.TestNG;
  49 
  50 /*
  51  * @test
  52  * @build jdk.testlibrary.*
  53  * @library /lib/testlibrary
  54  * @summary Functions of ProcessHandle.Info
  55  * @author Roger Riggs
  56  */
  57 
  58 public class InfoTest {
  59 
  60     static String whoami;
  61 
  62     static {
  63         try {
  64             // Create a file and take the username from the file
  65             Path p = Paths.get("OwnerName.tmp");
  66             Files.createFile(p);
  67             UserPrincipal owner = Files.getOwner(p);
  68             whoami = owner.getName();
  69             Files.delete(p);
  70         } catch (IOException ex) {
  71             ex.printStackTrace();
  72             throw new UncheckedIOException("tmp file", ex);
  73         }
  74     }
  75 
  76     // Main can be used to run the tests from the command line with only testng.jar.
  77     @SuppressWarnings("raw_types")
  78     public static void main(String[] args) {
  79         Class<?>[] testclass = {InfoTest.class};
  80         TestNG testng = new TestNG();
  81         testng.setTestClasses(testclass);
  82         testng.run();
  83     }
  84 
  85     /**
  86      * Test that cputime used shows up in ProcessHandle.info
  87      */
  88     @Test
  89     public static void test1() {
  90         System.out.println("Note: when run in samevm mode the cputime of the " +
  91                 "test runner is included.");
  92         ProcessHandle self = ProcessHandle.current();
  93 
  94         Duration someCPU = Duration.ofMillis(200L);
  95         Instant end = Instant.now().plus(someCPU);
  96         while (Instant.now().isBefore(end)) {
  97             // waste the cpu
  98         }
  99         ProcessHandle.Info info = self.info();
 100         System.out.printf(" info: %s%n", info);
 101         Optional<Duration> totalCpu = info.totalCpuDuration();
 102         if (totalCpu.isPresent() && (totalCpu.get().compareTo(someCPU) < 0)) {
 103             Assert.fail("reported cputime less than expected: " + someCPU + ", " +
 104                     "actual: " + info.totalCpuDuration());
 105         }
 106     }
 107 
 108     /**
 109      * Spawn a child with arguments and check they are visible via the ProcessHandle.
 110      */
 111     @Test
 112     public static void test2() {
 113         try {
 114             long cpuLoopTime = 100;             // 100 ms
 115             String[] extraArgs = {"pid", "parent", "stdin"};
 116             JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs);
 117             Instant afterStart = Instant.now();
 118 
 119             try (BufferedReader lines = p1.outputReader()) {
 120                 Duration lastCpu = Duration.ofMillis(0L);
 121                 for (int j = 0; j < 10; j++) {
 122 
 123                     p1.sendAction("cpuloop", cpuLoopTime);
 124                     p1.sendAction("cputime", "");
 125 
 126                     // Read cputime from child
 127                     Duration childCpuTime = null;
 128                     // Read lines from the child until the result from cputime is returned
 129                     String s;
 130                     while ((s = lines.readLine()) != null) {
 131                         String[] split = s.trim().split(" ");
 132                         if (split.length == 3 && split[1].equals("cputime")) {
 133                             long nanos = Long.valueOf(split[2]);
 134                             childCpuTime = Duration.ofNanos(nanos);
 135                             break;      // found the result we're looking for
 136                         }
 137                     }
 138 
 139 
 140                     ProcessHandle.Info info = p1.info();
 141                     System.out.printf(" info: %s%n", info);
 142 
 143                     if (info.user().isPresent()) {
 144                         String user = info.user().get();
 145                         Assert.assertNotNull(user, "User name");
 146                         Assert.assertEquals(user, whoami, "User name");
 147                     }
 148 
 149                     Optional<String> command = info.command();
 150                     if (command.isPresent()) {
 151                         String javaExe = System.getProperty("test.jdk") +
 152                                 File.separator + "bin" + File.separator + "java";
 153                         String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;
 154                         Path expectedPath = Paths.get(expected);
 155                         Path actualPath = Paths.get(command.get());
 156                         Assert.assertTrue(Files.isSameFile(expectedPath, actualPath),
 157                                 "Command: expected: " + javaExe + ", actual: " + command.get());
 158                     }
 159 
 160                     if (info.arguments().isPresent()) {
 161                         String[] args = info.arguments().get();
 162 
 163                         if (Platform.isLinux() || Platform.isOSX()) {
 164                             int offset = args.length - extraArgs.length;
 165                             for (int i = 0; i < extraArgs.length; i++) {
 166                                 Assert.assertEquals(args[offset + i], extraArgs[i],
 167                                         "Actual argument mismatch, index: " + i);
 168                             }
 169                         } else if (Platform.isSolaris()) {
 170                             Assert.assertEquals(args.length, 1,
 171                                     "Expected argument list length: 1");
 172                             Assert.assertNotNull(args[0],
 173                                     "Expected an argument");
 174                         } else {
 175                             System.out.printf("No argument test for OS: %s%n", Platform.getOsName());
 176                         }
 177 
 178                         // Now check that the first argument is not the same as the executed command
 179                         if (args.length > 0) {
 180                             Assert.assertNotEquals(args[0], command,
 181                                     "First argument should not be the executable: args[0]: "
 182                                             + args[0] + ", command: " + command);
 183                         }
 184                     }
 185 
 186                     if (info.totalCpuDuration().isPresent()) {
 187                         Duration totalCPU = info.totalCpuDuration().get();
 188                         Duration epsilon = Duration.ofMillis(200L);
 189                         if (childCpuTime != null) {
 190                             System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n",
 191                                     totalCPU.toNanos(), childCpuTime.toNanos(),
 192                                     childCpuTime.toNanos() - totalCPU.toNanos());
 193                             Assert.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon),
 194                                     childCpuTime + " should be within " +
 195                                             epsilon + " of " + totalCPU);
 196                         }
 197                         Assert.assertTrue(totalCPU.toNanos() > 0L,
 198                                 "total cpu time expected > 0ms, actual: " + totalCPU);
 199                         long t = Utils.adjustTimeout(10L);  // Adjusted timeout seconds
 200                         Assert.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + t * 1_000_000_000L,
 201                                 "total cpu time expected < " + t
 202                                         + " seconds more than previous iteration, actual: "
 203                                         + (totalCPU.toNanos() - lastCpu.toNanos()));
 204                         lastCpu = totalCPU;
 205                     }
 206 
 207                     if (info.startInstant().isPresent()) {
 208                         Instant startTime = info.startInstant().get();
 209                         Assert.assertTrue(startTime.isBefore(afterStart),
 210                                 "startTime after process spawn completed"
 211                                         + startTime + " + > " + afterStart);
 212                     }
 213                 }
 214             }
 215             p1.waitFor(Utils.adjustTimeout(5), TimeUnit.SECONDS);
 216         } catch (IOException | InterruptedException ie) {
 217             ie.printStackTrace(System.out);
 218             Assert.fail("unexpected exception", ie);
 219         }
 220     }
 221 
 222     /**
 223      * Spawn a child with arguments and check they are visible via the ProcessHandle.
 224      */
 225     @Test
 226     public static void test3() {
 227         try {
 228             for (int sleepTime : Arrays.asList(1, 2)) {
 229                 Process p = spawn("sleep", String.valueOf(sleepTime));
 230                 ProcessHandle.Info info = p.info();
 231                 System.out.printf(" info: %s%n", info);
 232 
 233                 if (info.user().isPresent()) {
 234                     String user = info.user().get();
 235                     Assert.assertNotNull(user);
 236                     Assert.assertEquals(user, whoami);
 237                 }
 238                 if (info.command().isPresent()) {
 239                     String command = info.command().get();
 240                     String expected = Platform.isWindows() ? "sleep.exe" : "sleep";
 241                     Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" +
 242                             expected + "\', actual: " + command);
 243 
 244                     // Verify the command exists and is executable
 245                     File exe = new File(command);
 246                     Assert.assertTrue(exe.exists(), "command must exist: " + exe);
 247                     Assert.assertTrue(exe.canExecute(), "command must be executable: " + exe);
 248                 }
 249                 if (info.arguments().isPresent()) {
 250                     String[] args = info.arguments().get();
 251                     if (args.length > 0) {
 252                         Assert.assertEquals(args[0], String.valueOf(sleepTime));
 253                     }
 254                 }
 255                 Assert.assertTrue(p.waitFor(15, TimeUnit.SECONDS));
 256             }
 257         } catch (IOException | InterruptedException ex) {
 258             ex.printStackTrace(System.out);
 259         } finally {
 260             // Destroy any children that still exist
 261             ProcessUtil.destroyProcessTree(ProcessHandle.current());
 262         }
 263     }
 264 
 265     /**
 266      * Cross check the cputime reported from java.management with that for the current process.
 267      */
 268     @Test
 269     public static void test4() {
 270         Duration myCputime1 = ProcessUtil.MXBeanCpuTime();
 271 
 272         Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration();
 273 
 274         Duration myCputime2 = ProcessUtil.MXBeanCpuTime();
 275 
 276         Optional<Duration> dur2 = ProcessHandle.current().info().totalCpuDuration();
 277 
 278         if (dur1.isPresent() && dur2.isPresent()) {
 279             Duration total1 = dur1.get();
 280             Duration total2 = dur2.get();
 281             System.out.printf(" total1 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
 282                     Objects.toString(total1), myCputime1, myCputime1.minus(total1));
 283             System.out.printf(" total2 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
 284                     Objects.toString(total2), myCputime2, myCputime2.minus(total2));
 285 
 286             Duration epsilon = Duration.ofMillis(200L);      // Epsilon is 200ms.
 287             Assert.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon),
 288                     myCputime1.toNanos() + " should be within " + epsilon
 289                             + " of " + myCputime2.toNanos());
 290             Assert.assertTrue(checkEpsilon(total1, total2, epsilon),
 291                     total1.toNanos() + " should be within " + epsilon
 292                             + " of " + total2.toNanos());
 293             Assert.assertTrue(checkEpsilon(myCputime1, total1, epsilon),
 294                     myCputime1.toNanos() + " should be within " + epsilon
 295                             + " of " + total1.toNanos());
 296             Assert.assertTrue(checkEpsilon(total1, myCputime2, epsilon),
 297                     total1.toNanos() + " should be within " + epsilon
 298                             + " of " + myCputime2.toNanos());
 299             Assert.assertTrue(checkEpsilon(myCputime2, total2, epsilon),
 300                     myCputime2.toNanos() + " should be within " + epsilon
 301                             + " of " + total2.toNanos());
 302         }
 303     }
 304 
 305     @Test
 306     public static void test5() {
 307         ProcessHandle self = ProcessHandle.current();
 308         Random r = new Random();
 309         for (int i = 0; i < 30; i++) {
 310             Instant end = Instant.now().plusMillis(500L);
 311             while (end.isBefore(Instant.now())) {
 312                 // burn the cpu time checking the time
 313                 long x = r.nextLong();
 314             }
 315             if (self.info().totalCpuDuration().isPresent()) {
 316                 Duration totalCpu = self.info().totalCpuDuration().get();
 317                 long infoTotalCputime = totalCpu.toNanos();
 318                 long beanCputime = ProcessUtil.MXBeanCpuTime().toNanos();
 319                 System.out.printf(" infoTotal: %12d, beanCpu: %12d, diff: %12d%n",
 320                         infoTotalCputime, beanCputime, beanCputime - infoTotalCputime);
 321             } else {
 322                 break;  // nothing to compare; continue
 323             }
 324         }
 325     }
 326     /**
 327      * Check two Durations, the second should be greater than the first or
 328      * within the supplied Epsilon.
 329      * @param d1 a Duration - presumed to be shorter
 330      * @param d2 a 2nd Duration - presumed to be greater (or within Epsilon)
 331      * @param epsilon Epsilon the amount of overlap allowed
 332      * @return true if d2 is greater than d1 or within epsilon, false otherwise
 333      */
 334     static boolean checkEpsilon(Duration d1, Duration d2, Duration epsilon) {
 335         if (d1.toNanos() <= d2.toNanos()) {
 336             return true;
 337         }
 338         Duration diff = d1.minus(d2).abs();
 339         return diff.compareTo(epsilon) <= 0;
 340     }
 341 
 342     /**
 343      * Spawn a native process with the provided arguments.
 344      * @param command the executable of native process
 345      * @param args to start a new process
 346      * @return the Process that was started
 347      * @throws IOException thrown by ProcessBuilder.start
 348      */
 349     static Process spawn(String command, String... args) throws IOException {
 350         ProcessBuilder pb = new ProcessBuilder();
 351         pb.inheritIO();
 352         List<String> list = new ArrayList<>();
 353         list.add(command);
 354         for (String arg : args)
 355             list.add(arg);
 356         pb.command(list);
 357         return pb.start();
 358     }
 359 }