InfoTest.java revision 14107:550572253bd8
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
24import java.io.BufferedReader;
25import java.io.File;
26import java.io.IOException;
27import java.io.UncheckedIOException;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.nio.file.attribute.UserPrincipal;
32import java.time.Duration;
33import java.time.Instant;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37import java.util.Objects;
38import java.util.Optional;
39import java.util.Random;
40import java.util.concurrent.TimeUnit;
41
42import jdk.test.lib.Platform;
43import jdk.test.lib.Utils;
44import org.testng.Assert;
45import org.testng.TestNG;
46import org.testng.annotations.Test;
47
48/*
49 * @test
50 * @bug 8077350 8081566 8081567 8098852 8136597
51 * @library /test/lib/share/classes
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
60public 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}
435