Path: blob/master/test/jdk/java/lang/ProcessHandle/InfoTest.java
41149 views
/*1* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.BufferedReader;24import java.io.File;25import java.io.IOException;26import java.io.UncheckedIOException;27import java.nio.file.Files;28import java.nio.file.Path;29import java.nio.file.Paths;30import java.nio.file.attribute.UserPrincipal;31import java.time.Duration;32import java.time.Instant;33import java.util.ArrayList;34import java.util.Arrays;35import java.util.List;36import java.util.Objects;37import java.util.Optional;38import java.util.Random;39import java.util.concurrent.TimeUnit;4041import jdk.test.lib.Platform;42import jdk.test.lib.Utils;43import org.testng.Assert;44import org.testng.TestNG;45import org.testng.annotations.Test;4647/*48* @test49* @bug 8077350 8081566 8081567 8098852 813659750* @library /test/lib51* @modules java.base/jdk.internal.misc52* jdk.management53* @build jdk.test.lib.Utils54* jdk.test.lib.Asserts55* jdk.test.lib.JDKToolFinder56* jdk.test.lib.JDKToolLauncher57* jdk.test.lib.Platform58* jdk.test.lib.process.*59* @run testng InfoTest60* @summary Functions of ProcessHandle.Info61* @author Roger Riggs62*/6364public class InfoTest {6566static String whoami;6768static {69try {70// Create a file and take the username from the file71Path p = Paths.get("OwnerName.tmp");72Files.createFile(p);73UserPrincipal owner = Files.getOwner(p);74whoami = owner.getName();75Files.delete(p);76} catch (IOException ex) {77ex.printStackTrace();78throw new UncheckedIOException("tmp file", ex);79}80}8182// Main can be used to run the tests from the command line with only testng.jar.83@SuppressWarnings("raw_types")84public static void main(String[] args) {85Class<?>[] testclass = {InfoTest.class};86TestNG testng = new TestNG();87testng.setTestClasses(testclass);88testng.run();89}9091/**92* Test that cputime used shows up in ProcessHandle.info93*/94@Test95public static void test1() {96System.out.println("Note: when run in samevm mode the cputime of the " +97"test runner is included.");98ProcessHandle self = ProcessHandle.current();99100Duration someCPU = Duration.ofMillis(200L);101Instant end = Instant.now().plus(someCPU);102while (Instant.now().isBefore(end)) {103// waste the cpu104}105ProcessHandle.Info info = self.info();106System.out.printf(" info: %s%n", info);107Optional<Duration> totalCpu = info.totalCpuDuration();108if (totalCpu.isPresent() && (totalCpu.get().compareTo(someCPU) < 0)) {109Assert.fail("reported cputime less than expected: " + someCPU + ", " +110"actual: " + info.totalCpuDuration());111}112}113114/**115* Spawn a child with arguments and check they are visible via the ProcessHandle.116*/117@Test118public static void test2() {119try {120long cpuLoopTime = 100; // 100 ms121String[] extraArgs = {"pid", "parent", "stdin"};122JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs);123Instant afterStart = null;124125try (BufferedReader lines = p1.outputReader()) {126// Read the args line to know the subprocess has started127lines.readLine();128afterStart = Instant.now();129130Duration lastCpu = Duration.ofMillis(0L);131for (int j = 0; j < 10; j++) {132133p1.sendAction("cpuloop", cpuLoopTime);134p1.sendAction("cputime", "");135136// Read cputime from child137Duration childCpuTime = null;138// Read lines from the child until the result from cputime is returned139for (String s; (s = lines.readLine()) != null;) {140String[] split = s.trim().split(" ");141if (split.length == 3 && split[1].equals("cputime")) {142long nanos = Long.valueOf(split[2]);143childCpuTime = Duration.ofNanos(nanos);144break; // found the result we're looking for145}146}147148if (Platform.isAix()) {149// Unfortunately, on AIX the usr/sys times reported through150// /proc/<pid>/psinfo which are used by ProcessHandle.Info151// are running slow compared to the corresponding times reported152// by the times()/getrusage() system calls which are used by153// OperatingSystemMXBean.getProcessCpuTime() and returned by154// the JavaChild for the "cputime" command.155// This is because /proc/<pid>/status is only updated once a second.156// So we better wait a little bit to get plausible values here.157Thread.sleep(1000);158}159ProcessHandle.Info info = p1.info();160System.out.printf(" info: %s%n", info);161162if (info.user().isPresent()) {163String user = info.user().get();164Assert.assertNotNull(user, "User name");165Assert.assertEquals(user, whoami, "User name");166}167168Optional<String> command = info.command();169if (command.isPresent()) {170String javaExe = System.getProperty("test.jdk") +171File.separator + "bin" + File.separator + "java";172String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;173Path expectedPath = Paths.get(expected);174Path actualPath = Paths.get(command.get());175Assert.assertTrue(Files.isSameFile(expectedPath, actualPath),176"Command: expected: " + javaExe + ", actual: " + command.get());177}178179if (info.arguments().isPresent()) {180String[] args = info.arguments().get();181182int offset = args.length - extraArgs.length;183for (int i = 0; i < extraArgs.length; i++) {184Assert.assertEquals(args[offset + i], extraArgs[i],185"Actual argument mismatch, index: " + i);186}187188// Now check that the first argument is not the same as the executed command189if (args.length > 0) {190Assert.assertNotEquals(args[0], command,191"First argument should not be the executable: args[0]: "192+ args[0] + ", command: " + command);193}194}195196if (command.isPresent() && info.arguments().isPresent()) {197// If both, 'command' and 'arguments' are present,198// 'commandLine' is just the concatenation of the two.199Assert.assertTrue(info.commandLine().isPresent(),200"commandLine() must be available");201202String javaExe = System.getProperty("test.jdk") +203File.separator + "bin" + File.separator + "java";204String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;205Path expectedPath = Paths.get(expected);206String commandLine = info.commandLine().get();207String commandLineCmd = commandLine.split(" ")[0];208Path commandLineCmdPath = Paths.get(commandLineCmd);209Assert.assertTrue(Files.isSameFile(commandLineCmdPath, expectedPath),210"commandLine() should start with: " + expectedPath +211" but starts with " + commandLineCmdPath);212213Assert.assertTrue(commandLine.contains(command.get()),214"commandLine() must contain the command: " + command.get());215List<String> allArgs = p1.getArgs();216for (int i = 1; i < allArgs.size(); i++) {217Assert.assertTrue(commandLine.contains(allArgs.get(i)),218"commandLine() must contain argument: " + allArgs.get(i));219}220} else if (info.commandLine().isPresent() &&221command.isPresent() &&222command.get().length() < info.commandLine().get().length()) {223// If we only have the commandLine() we can only do some basic checks...224String commandLine = info.commandLine().get();225String javaExe = "java" + (Platform.isWindows() ? ".exe" : "");226int pos = commandLine.indexOf(javaExe);227Assert.assertTrue(pos > 0, "commandLine() should at least contain 'java'");228229pos += javaExe.length() + 1; // +1 for the space after the command230List<String> allArgs = p1.getArgs();231// First argument is the command - skip it here as we've already checked that.232for (int i = 1; (i < allArgs.size()) &&233(pos + allArgs.get(i).length() < commandLine.length()); i++) {234Assert.assertTrue(commandLine.contains(allArgs.get(i)),235"commandLine() must contain argument: " + allArgs.get(i));236pos += allArgs.get(i).length() + 1;237}238}239240if (info.totalCpuDuration().isPresent()) {241Duration totalCPU = info.totalCpuDuration().get();242Duration epsilon = Duration.ofMillis(200L);243if (childCpuTime != null) {244System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n",245totalCPU.toNanos(), childCpuTime.toNanos(),246childCpuTime.toNanos() - totalCPU.toNanos());247Assert.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon),248childCpuTime + " should be within " +249epsilon + " of " + totalCPU);250}251Assert.assertTrue(totalCPU.toNanos() > 0L,252"total cpu time expected > 0ms, actual: " + totalCPU);253long t = Utils.adjustTimeout(10L); // Adjusted timeout seconds254Assert.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + t * 1_000_000_000L,255"total cpu time expected < " + t256+ " seconds more than previous iteration, actual: "257+ (totalCPU.toNanos() - lastCpu.toNanos()));258lastCpu = totalCPU;259}260261if (info.startInstant().isPresent()) {262Instant startTime = info.startInstant().get();263Assert.assertTrue(startTime.isBefore(afterStart),264"startTime after process spawn completed"265+ startTime + " + > " + afterStart);266}267}268}269p1.sendAction("exit");270Assert.assertTrue(p1.waitFor(Utils.adjustTimeout(30L), TimeUnit.SECONDS),271"timeout waiting for process to terminate");272} catch (IOException | InterruptedException ie) {273ie.printStackTrace(System.out);274Assert.fail("unexpected exception", ie);275} finally {276// Destroy any children that still exist277ProcessUtil.destroyProcessTree(ProcessHandle.current());278}279}280281/**282* Spawn a child with arguments and check they are visible via the ProcessHandle.283*/284@Test285public static void test3() {286try {287for (long sleepTime : Arrays.asList(Utils.adjustTimeout(30), Utils.adjustTimeout(32))) {288Process p = spawn("sleep", String.valueOf(sleepTime));289290ProcessHandle.Info info = p.info();291System.out.printf(" info: %s%n", info);292293if (info.user().isPresent()) {294String user = info.user().get();295Assert.assertNotNull(user);296Assert.assertEquals(user, whoami);297}298if (info.command().isPresent()) {299String command = info.command().get();300String expected = "sleep";301if (Platform.isWindows()) {302expected = "sleep.exe";303} else if (Platform.isBusybox("/bin/sleep")) {304// With busybox sleep is just a sym link to busybox.305// The busbox executable is seen as ProcessHandle.Info command.306expected = "busybox";307}308Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" +309expected + "\', actual: " + command);310311// Verify the command exists and is executable312File exe = new File(command);313Assert.assertTrue(exe.exists(), "command must exist: " + exe);314Assert.assertTrue(exe.canExecute(), "command must be executable: " + exe);315}316if (info.arguments().isPresent()) {317String[] args = info.arguments().get();318if (args.length > 0) {319Assert.assertEquals(args[0], String.valueOf(sleepTime));320}321}322p.destroy();323Assert.assertTrue(p.waitFor(Utils.adjustTimeout(30), TimeUnit.SECONDS),324"timeout waiting for process to terminate");325}326} catch (IOException | InterruptedException ex) {327ex.printStackTrace(System.out);328} finally {329// Destroy any children that still exist330ProcessUtil.destroyProcessTree(ProcessHandle.current());331}332}333334/**335* Cross check the cputime reported from java.management with that for the current process.336*/337@Test338public static void test4() {339Duration myCputime1 = ProcessUtil.MXBeanCpuTime();340341if (Platform.isAix()) {342// Unfortunately, on AIX the usr/sys times reported through343// /proc/<pid>/psinfo which are used by ProcessHandle.Info344// are running slow compared to the corresponding times reported345// by the times()/getrusage() system calls which are used by346// OperatingSystemMXBean.getProcessCpuTime() and returned by347// the JavaChild for the "cputime" command.348// So we better wait a little bit to get plausible values here.349try {350Thread.sleep(1000);351} catch (InterruptedException ex) {}352}353Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration();354355Duration myCputime2 = ProcessUtil.MXBeanCpuTime();356357if (Platform.isAix()) {358try {359Thread.sleep(1000);360} catch (InterruptedException ex) {}361}362Optional<Duration> dur2 = ProcessHandle.current().info().totalCpuDuration();363364if (dur1.isPresent() && dur2.isPresent()) {365Duration total1 = dur1.get();366Duration total2 = dur2.get();367System.out.printf(" total1 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",368Objects.toString(total1), myCputime1, myCputime1.minus(total1));369System.out.printf(" total2 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",370Objects.toString(total2), myCputime2, myCputime2.minus(total2));371372Duration epsilon = Duration.ofMillis(200L); // Epsilon is 200ms.373Assert.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon),374myCputime1.toNanos() + " should be within " + epsilon375+ " of " + myCputime2.toNanos());376Assert.assertTrue(checkEpsilon(total1, total2, epsilon),377total1.toNanos() + " should be within " + epsilon378+ " of " + total2.toNanos());379Assert.assertTrue(checkEpsilon(myCputime1, total1, epsilon),380myCputime1.toNanos() + " should be within " + epsilon381+ " of " + total1.toNanos());382Assert.assertTrue(checkEpsilon(total1, myCputime2, epsilon),383total1.toNanos() + " should be within " + epsilon384+ " of " + myCputime2.toNanos());385Assert.assertTrue(checkEpsilon(myCputime2, total2, epsilon),386myCputime2.toNanos() + " should be within " + epsilon387+ " of " + total2.toNanos());388}389}390391@Test392public static void test5() {393ProcessHandle self = ProcessHandle.current();394Random r = new Random();395for (int i = 0; i < 30; i++) {396Instant end = Instant.now().plusMillis(500L);397while (end.isBefore(Instant.now())) {398// burn the cpu time checking the time399long x = r.nextLong();400}401if (self.info().totalCpuDuration().isPresent()) {402Duration totalCpu = self.info().totalCpuDuration().get();403long infoTotalCputime = totalCpu.toNanos();404long beanCputime = ProcessUtil.MXBeanCpuTime().toNanos();405System.out.printf(" infoTotal: %12d, beanCpu: %12d, diff: %12d%n",406infoTotalCputime, beanCputime, beanCputime - infoTotalCputime);407} else {408break; // nothing to compare; continue409}410}411}412/**413* Check two Durations, the second should be greater than the first or414* within the supplied Epsilon.415* @param d1 a Duration - presumed to be shorter416* @param d2 a 2nd Duration - presumed to be greater (or within Epsilon)417* @param epsilon Epsilon the amount of overlap allowed418* @return true if d2 is greater than d1 or within epsilon, false otherwise419*/420static boolean checkEpsilon(Duration d1, Duration d2, Duration epsilon) {421if (d1.toNanos() <= d2.toNanos()) {422return true;423}424Duration diff = d1.minus(d2).abs();425return diff.compareTo(epsilon) <= 0;426}427428/**429* Spawn a native process with the provided arguments.430* @param command the executable of native process431* @param args to start a new process432* @return the Process that was started433* @throws IOException thrown by ProcessBuilder.start434*/435static Process spawn(String command, String... args) throws IOException {436ProcessBuilder pb = new ProcessBuilder();437pb.inheritIO();438List<String> list = new ArrayList<>();439list.add(command);440for (String arg : args)441list.add(arg);442pb.command(list);443return pb.start();444}445}446447448