ActionHelper.java revision 1870:4aa2e64eff30
1117397Skan/* 2117397Skan * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3117397Skan * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4117397Skan * 5117397Skan * This code is free software; you can redistribute it and/or modify it 6117397Skan * under the terms of the GNU General Public License version 2 only, as 7117397Skan * published by the Free Software Foundation. 8117397Skan * 9117397Skan * This code is distributed in the hope that it will be useful, but WITHOUT 10117397Skan * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11117397Skan * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12117397Skan * version 2 for more details (a copy is included in the LICENSE file that 13117397Skan * accompanied this code). 14117397Skan * 15117397Skan * You should have received a copy of the GNU General Public License version 16117397Skan * 2 along with this work; if not, write to the Free Software Foundation, 17117397Skan * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18169691Skan * 19117397Skan * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20117397Skan * or visit www.oracle.com if you need additional information or have any 21117397Skan * questions. 22117397Skan */ 23117397Skan 24117397Skanpackage jdk.test.failurehandler.action; 25117397Skan 26117397Skanimport com.sun.tools.attach.VirtualMachine; 27117397Skanimport com.sun.tools.attach.VirtualMachineDescriptor; 28117397Skanimport jdk.test.failurehandler.value.InvalidValueException; 29117397Skanimport jdk.test.failurehandler.value.Value; 30169691Skanimport jdk.test.failurehandler.value.ValueHandler; 31169691Skanimport jdk.test.failurehandler.HtmlSection; 32169691Skanimport jdk.test.failurehandler.Stopwatch; 33169691Skanimport jdk.test.failurehandler.Utils; 34169691Skan 35117397Skanimport java.io.BufferedReader; 36117397Skanimport java.io.CharArrayReader; 37117397Skanimport java.io.CharArrayWriter; 38117397Skanimport java.io.IOException; 39117397Skanimport java.io.InputStreamReader; 40117397Skanimport java.io.PrintWriter; 41117397Skanimport java.io.Reader; 42117397Skanimport java.io.Writer; 43117397Skanimport java.io.File; 44117397Skanimport java.nio.file.Path; 45117397Skanimport java.nio.file.Paths; 46117397Skanimport java.util.ArrayList; 47132720Skanimport java.util.Arrays; 48117397Skanimport java.util.Collections; 49117397Skanimport java.util.Date; 50132720Skanimport java.util.List; 51132720Skanimport java.util.Properties; 52132720Skanimport java.util.Timer; 53132720Skanimport java.util.TimerTask; 54132720Skanimport java.util.concurrent.TimeUnit; 55132720Skan 56117397Skanpublic class ActionHelper { 57117397Skan private final Path workDir; 58132720Skan @Value(name = "execSuffix") 59117397Skan private String executableSuffix = ""; 60117397Skan private Path[] paths; 61132720Skan 62132720Skan private final PatternAction getChildren; 63132720Skan 64132720Skan public ActionHelper(Path workDir, String prefix, Properties properties, 65132720Skan Path... jdks) throws InvalidValueException { 66132720Skan this.workDir = workDir.toAbsolutePath(); 67117397Skan getChildren = new PatternAction("children", 68117397Skan Utils.prependPrefix(prefix, "getChildren"), properties); 69117397Skan ValueHandler.apply(this, properties, prefix); 70117397Skan String[] pathStrings = System.getenv("PATH").split(File.pathSeparator); 71117397Skan paths = new Path[pathStrings.length]; 72117397Skan for (int i = 0; i < paths.length; ++i) { 73117397Skan paths[i] = Paths.get(pathStrings[i]); 74117397Skan } 75117397Skan addJdks(jdks); 76117397Skan } 77117397Skan 78117397Skan public List<Long> getChildren(HtmlSection section, long pid) { 79117397Skan String pidStr = "" + pid; 80117397Skan ProcessBuilder pb = getChildren.prepareProcess(section, this, pidStr); 81117397Skan PrintWriter log = getChildren.getSection(section).getWriter(); 82117397Skan CharArrayWriter writer = new CharArrayWriter(); 83117397Skan ExitCode code = run(log, writer, pb, getChildren.getParameters()); 84117397Skan Reader output = new CharArrayReader(writer.toCharArray()); 85117397Skan 86117397Skan if (!ExitCode.OK.equals(code)) { 87117397Skan log.println("WARNING: get children pids action failed"); 88117397Skan try { 89117397Skan Utils.copyStream(output, log); 90117397Skan } catch (IOException e) { 91117397Skan e.printStackTrace(log); 92117397Skan } 93117397Skan return Collections.emptyList(); 94117397Skan } 95117397Skan 96117397Skan List<Long> result = new ArrayList<>(); 97 try { 98 try (BufferedReader reader = new BufferedReader(output)) { 99 String line; 100 while ((line = reader.readLine()) != null) { 101 String value = line.trim(); 102 if (value.isEmpty()) { 103 // ignore empty lines 104 continue; 105 } 106 try { 107 result.add(Long.valueOf(value)); 108 } catch (NumberFormatException e) { 109 log.printf("WARNING: can't parse child pid %s : %s%n", 110 line, e.getMessage()); 111 e.printStackTrace(log); 112 } 113 } 114 } 115 } catch (IOException e) { 116 e.printStackTrace(log); 117 } 118 return result; 119 } 120 121 public ProcessBuilder prepareProcess(PrintWriter log, String app, 122 String... args) { 123 File appBin = findApp(app); 124 if (appBin == null) { 125 log.printf("ERROR: can't find %s in %s.%n", 126 app, Arrays.toString(paths)); 127 return null; 128 } 129 List<String> command = new ArrayList<>(args.length + 1); 130 command.add(appBin.toString()); 131 Collections.addAll(command, args); 132 return new ProcessBuilder() 133 .command(command) 134 .directory(workDir.toFile()); 135 } 136 137 private File findApp(String app) { 138 String name = app + executableSuffix; 139 for (Path pathElem : paths) { 140 File result = pathElem.resolve(name).toFile(); 141 if (result.exists()) { 142 return result; 143 } 144 } 145 return null; 146 } 147 148 private void addJdks(Path[] jdkPaths) { 149 if (jdkPaths != null && jdkPaths.length != 0) { 150 Path[] result = new Path[jdkPaths.length + paths.length]; 151 for (int i = 0; i < jdkPaths.length; ++i) { 152 result[i] = jdkPaths[i].resolve("bin"); 153 } 154 System.arraycopy(paths, 0, result, jdkPaths.length, paths.length); 155 paths = result; 156 } 157 } 158 159 private ExitCode run(PrintWriter log, Writer out, ProcessBuilder pb, 160 ActionParameters params) { 161 char[] lineChars = new char[40]; 162 Arrays.fill(lineChars, '-'); 163 String line = new String(lineChars); 164 Stopwatch stopwatch = new Stopwatch(); 165 stopwatch.start(); 166 167 log.printf("%s%n[%tF %<tT] %s%n%1$s%n", line, new Date(), pb.command()); 168 Process process; 169 KillerTask killer; 170 171 ExitCode result = ExitCode.NEVER_STARTED; 172 173 try { 174 process = pb.start(); 175 killer = new KillerTask(process); 176 killer.schedule(params.timeout); 177 Utils.copyStream(new InputStreamReader(process.getInputStream()), 178 out); 179 try { 180 result = new ExitCode(process.waitFor()); 181 killer.cancel(); 182 } catch (InterruptedException e) { 183 Thread.currentThread().interrupt(); 184 if (!killer.cancel()) { 185 log.println( 186 "WARNING: interrupted when waiting for the tool:"); 187 e.printStackTrace(log); 188 } 189 } 190 if (killer.hasTimedOut()) { 191 log.printf( 192 "WARNING: tool timed out: killed process after %d ms%n", 193 TimeUnit.MILLISECONDS.toMicros(params.timeout)); 194 result = ExitCode.TIMED_OUT; 195 } 196 } catch (IOException e) { 197 e.printStackTrace(log); 198 result = ExitCode.LAUNCH_ERROR; 199 } 200 201 stopwatch.stop(); 202 log.printf("%s%n[%tF %<tT] exit code : %d time : %d ms%n%1$s%n", 203 line, new Date(), result.value, 204 TimeUnit.MILLISECONDS.toSeconds(stopwatch.getElapsedTimeNs())); 205 return result; 206 } 207 208 public void runPatternAction(SimpleAction action, HtmlSection section) { 209 if (action != null) { 210 HtmlSection subSection = action.getSection(section); 211 PrintWriter log = subSection.getWriter(); 212 ProcessBuilder pb = action.prepareProcess(log, this); 213 exec(subSection, pb, action.getParameters()); 214 } 215 } 216 217 public void runPatternAction(PatternAction action, HtmlSection section, 218 String value) { 219 if (action != null) { 220 ProcessBuilder pb = action.prepareProcess(section, this, value); 221 HtmlSection subSection = action.getSection(section); 222 exec(subSection, pb, action.getParameters()); 223 } 224 } 225 226 public boolean isJava(long pid, PrintWriter log) { 227 ProcessBuilder pb = prepareProcess(log, "jps", "-q"); 228 if (pb == null) { 229 return false; 230 } 231 pb.redirectErrorStream(true); 232 boolean result = false; 233 String pidStr = "" + pid; 234 try { 235 Process process = pb.start(); 236 try (BufferedReader reader = new BufferedReader( 237 new InputStreamReader(process.getInputStream()))) { 238 String line; 239 while ((line = reader.readLine()) != null){ 240 if (pidStr.equals(line)) { 241 result = true; 242 } 243 } 244 } 245 process.waitFor(); 246 } catch (IOException e) { 247 log.printf("WARNING: can't run jps : %s%n", e.getMessage()); 248 e.printStackTrace(log); 249 } catch (InterruptedException e) { 250 Thread.currentThread().interrupt(); 251 e.printStackTrace(log); 252 } 253 return result; 254 } 255 256 private static class KillerTask extends TimerTask { 257 private static final Timer WATCHDOG = new Timer("WATCHDOG", true); 258 private final Process process; 259 private boolean timedOut; 260 261 public KillerTask(Process process) { 262 this.process = process; 263 } 264 265 public void run() { 266 try { 267 process.exitValue(); 268 } catch (IllegalThreadStateException e) { 269 // !prepareProcess.isAlive() 270 process.destroy(); 271 timedOut = true; 272 } 273 } 274 275 public boolean hasTimedOut() { 276 return timedOut; 277 } 278 279 public void schedule(long timeout) { 280 if (timeout > 0) { 281 WATCHDOG.schedule(this, timeout); 282 } 283 } 284 } 285 286 private void exec(HtmlSection section, ProcessBuilder process, 287 ActionParameters params) { 288 if (process == null) { 289 return; 290 } 291 PrintWriter sectionWriter = section.getWriter(); 292 if (params.repeat > 1) { 293 for (int i = 0, n = params.repeat; i < n; ++i) { 294 HtmlSection iteration = section.createChildren( 295 String.format("iteration_%d", i)); 296 PrintWriter writer = iteration.getWriter(); 297 ExitCode exitCode = run(writer, writer, process, params); 298 if (params.stopOnError && !ExitCode.OK.equals(exitCode)) { 299 sectionWriter.printf( 300 "ERROR: non zero exit code[%d] -- break.", 301 exitCode.value); 302 break; 303 } 304 try { 305 Thread.sleep(params.pause); 306 } catch (InterruptedException e) { 307 Thread.currentThread().interrupt(); 308 e.printStackTrace(sectionWriter); 309 } 310 } 311 } else { 312 run(section.getWriter(), section.getWriter(), process, params); 313 } 314 } 315 316 /** 317 * Special values for prepareProcess exit code. 318 * 319 * <p>Can we clash with normal codes? 320 * On Solaris and Linux, only [0..255] are returned. 321 * On Windows, prepareProcess exit codes are stored in unsigned int. 322 * On MacOSX no limits (except it should fit C int type) 323 * are defined in the exit() man pages. 324 */ 325 private static class ExitCode { 326 /** Process exits gracefully */ 327 public static final ExitCode OK = new ExitCode(0); 328 /** Error launching prepareProcess */ 329 public static final ExitCode LAUNCH_ERROR = new ExitCode(-1); 330 /** Application prepareProcess has been killed by watchdog due to timeout */ 331 public static final ExitCode TIMED_OUT = new ExitCode(-2); 332 /** Application prepareProcess has never been started due to program logic */ 333 public static final ExitCode NEVER_STARTED = new ExitCode(-3); 334 335 public final int value; 336 337 private ExitCode(int value) { 338 this.value = value; 339 } 340 341 @Override 342 public boolean equals(Object o) { 343 if (this == o) { 344 return true; 345 } 346 if (o == null || getClass() != o.getClass()) { 347 return false; 348 } 349 350 ExitCode exitCode = (ExitCode) o; 351 return value == exitCode.value; 352 } 353 354 @Override 355 public int hashCode() { 356 return value; 357 } 358 } 359 360} 361