ActionHelper.java revision 2327:281a14e87a3b
1/*
2 * Copyright (c) 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
24package jdk.test.failurehandler.action;
25
26import com.sun.tools.attach.VirtualMachine;
27import com.sun.tools.attach.VirtualMachineDescriptor;
28import jdk.test.failurehandler.value.InvalidValueException;
29import jdk.test.failurehandler.value.Value;
30import jdk.test.failurehandler.value.ValueHandler;
31import jdk.test.failurehandler.HtmlSection;
32import jdk.test.failurehandler.Stopwatch;
33import jdk.test.failurehandler.Utils;
34
35import java.io.BufferedReader;
36import java.io.CharArrayReader;
37import java.io.CharArrayWriter;
38import java.io.IOException;
39import java.io.InputStreamReader;
40import java.io.PrintWriter;
41import java.io.Reader;
42import java.io.Writer;
43import java.io.File;
44import java.nio.file.Path;
45import java.nio.file.Paths;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.Collections;
49import java.util.Date;
50import java.util.List;
51import java.util.Properties;
52import java.util.Timer;
53import java.util.TimerTask;
54import java.util.concurrent.TimeUnit;
55
56public class ActionHelper {
57    private final Path workDir;
58    @Value(name = "execSuffix")
59    private String executableSuffix = "";
60    private Path[] paths;
61
62    private final PatternAction getChildren;
63
64    public ActionHelper(Path workDir, String prefix, Properties properties,
65                        Path... jdks) throws InvalidValueException {
66        this.workDir = workDir.toAbsolutePath();
67        getChildren = new PatternAction("children",
68                Utils.prependPrefix(prefix, "getChildren"), properties);
69        ValueHandler.apply(this, properties, prefix);
70        String[] pathStrings = System.getenv("PATH").split(File.pathSeparator);
71        paths = new Path[pathStrings.length];
72        for (int i = 0; i < paths.length; ++i) {
73            paths[i] = Paths.get(pathStrings[i]);
74        }
75        addJdks(jdks);
76    }
77
78    public List<Long> getChildren(HtmlSection section, long pid) {
79        String pidStr = "" + pid;
80        ProcessBuilder pb = getChildren.prepareProcess(section, this, pidStr);
81        PrintWriter log = getChildren.getSection(section).getWriter();
82        CharArrayWriter writer = new CharArrayWriter();
83        ExitCode code = run(log, writer, pb, getChildren.getParameters());
84        Reader output = new CharArrayReader(writer.toCharArray());
85
86        if (!ExitCode.OK.equals(code)) {
87            log.println("WARNING: get children pids action failed");
88            try {
89                Utils.copyStream(output, log);
90            } catch (IOException e) {
91                e.printStackTrace(log);
92            }
93            return Collections.emptyList();
94        }
95
96        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 timeout=%s%n%1$s%n", line, new Date(), pb.command(), params.timeout);
168
169        Process process;
170        KillerTask killer;
171
172        ExitCode result = ExitCode.NEVER_STARTED;
173
174        try {
175            process = pb.start();
176            killer = new KillerTask(process);
177            killer.schedule(params.timeout);
178            Utils.copyStream(new InputStreamReader(process.getInputStream()),
179                    out);
180            try {
181                result = new ExitCode(process.waitFor());
182            } catch (InterruptedException e) {
183                log.println("WARNING: interrupted when waiting for the tool:%n");
184                e.printStackTrace(log);
185            } finally {
186                killer.cancel();
187            }
188            if (killer.hasTimedOut()) {
189                log.printf(
190                        "WARNING: tool timed out: killed process after %d ms%n",
191                        params.timeout);
192                result = ExitCode.TIMED_OUT;
193            }
194        } catch (IOException e) {
195            log.printf("WARNING: caught IOException while running tool%n");
196            e.printStackTrace(log);
197            result = ExitCode.LAUNCH_ERROR;
198        }
199
200        stopwatch.stop();
201        log.printf("%s%n[%tF %<tT] exit code: %d time: %d ms%n%1$s%n",
202                line, new Date(), result.value,
203                TimeUnit.NANOSECONDS.toMillis(stopwatch.getElapsedTimeNs()));
204        return result;
205    }
206
207    public void runPatternAction(SimpleAction action, HtmlSection section) {
208        if (action != null) {
209            HtmlSection subSection = action.getSection(section);
210            PrintWriter log = subSection.getWriter();
211            ProcessBuilder pb = action.prepareProcess(log, this);
212            exec(subSection, pb, action.getParameters());
213        }
214    }
215
216    public void runPatternAction(PatternAction action, HtmlSection section,
217                                 String value) {
218        if (action != null) {
219            ProcessBuilder pb = action.prepareProcess(section, this, value);
220            HtmlSection subSection = action.getSection(section);
221            exec(subSection, pb, action.getParameters());
222        }
223    }
224
225    public boolean isJava(long pid, PrintWriter log) {
226        ProcessBuilder pb = prepareProcess(log, "jps", "-q");
227        if (pb == null) {
228            return false;
229        }
230        pb.redirectErrorStream(true);
231        boolean result = false;
232        String pidStr = "" + pid;
233        try {
234            Process process = pb.start();
235            try (BufferedReader reader = new BufferedReader(
236                    new InputStreamReader(process.getInputStream()))) {
237                String line;
238                while ((line = reader.readLine()) != null){
239                    if (pidStr.equals(line)) {
240                        result = true;
241                    }
242                }
243            }
244            process.waitFor();
245        } catch (IOException e) {
246            log.printf("WARNING: can't run jps : %s%n", e.getMessage());
247            e.printStackTrace(log);
248        } catch (InterruptedException e) {
249            log.printf("WARNING: interrupted%n");
250            e.printStackTrace(log);
251        }
252        return result;
253    }
254
255    private static class KillerTask extends TimerTask {
256        private static final Timer WATCHDOG = new Timer("WATCHDOG", true);
257        private final Process process;
258        private boolean timedOut;
259
260        public KillerTask(Process process) {
261            this.process = process;
262        }
263
264        public void run() {
265            try {
266                process.exitValue();
267            } catch (IllegalThreadStateException e) {
268                process.destroyForcibly();
269                timedOut = true;
270            }
271        }
272
273        public boolean hasTimedOut() {
274            return timedOut;
275        }
276
277        public void schedule(long timeout) {
278            if (timeout > 0) {
279                WATCHDOG.schedule(this, timeout);
280            }
281        }
282    }
283
284    private void exec(HtmlSection section, ProcessBuilder process,
285                      ActionParameters params) {
286        if (process == null) {
287            return;
288        }
289        PrintWriter sectionWriter = section.getWriter();
290        if (params.repeat > 1) {
291            for (int i = 0, n = params.repeat; i < n; ++i) {
292                HtmlSection iteration = section.createChildren(
293                        String.format("iteration_%d", i));
294                PrintWriter writer = iteration.getWriter();
295                ExitCode exitCode = run(writer, writer, process, params);
296                if (params.stopOnError && !ExitCode.OK.equals(exitCode)) {
297                    sectionWriter.printf(
298                            "ERROR: non zero exit code[%d] -- break.",
299                            exitCode.value);
300                    break;
301                }
302                // sleep, if this is not the last iteration
303                if (i < n - 1) {
304                    try {
305                        Thread.sleep(params.pause);
306                    } catch (InterruptedException e) {
307                        sectionWriter.printf(
308                                "WARNING: interrupted while sleeping between invocations");
309                        e.printStackTrace(sectionWriter);
310                    }
311                }
312            }
313        } else {
314            run(section.getWriter(), section.getWriter(), process, params);
315        }
316    }
317
318    /**
319     * Special values for prepareProcess exit code.
320     *
321     * <p>Can we clash with normal codes?
322     * On Solaris and Linux, only [0..255] are returned.
323     * On Windows, prepareProcess exit codes are stored in unsigned int.
324     * On MacOSX no limits (except it should fit C int type)
325     * are defined in the exit() man pages.
326     */
327    private static class ExitCode {
328        /** Process exits gracefully */
329        public static final ExitCode OK = new ExitCode(0);
330        /** Error launching prepareProcess */
331        public static final ExitCode LAUNCH_ERROR = new ExitCode(-1);
332        /** Application prepareProcess has been killed by watchdog due to timeout */
333        public static final ExitCode TIMED_OUT = new ExitCode(-2);
334        /** Application prepareProcess has never been started due to program logic */
335        public static final ExitCode NEVER_STARTED = new ExitCode(-3);
336
337        public final int value;
338
339        private ExitCode(int value) {
340            this.value = value;
341        }
342
343        @Override
344        public boolean equals(Object o) {
345            if (this == o) {
346                return true;
347            }
348            if (o == null || getClass() != o.getClass()) {
349                return false;
350            }
351
352            ExitCode exitCode = (ExitCode) o;
353            return value == exitCode.value;
354        }
355
356        @Override
357        public int hashCode() {
358            return value;
359        }
360    }
361
362}
363