ActionHelper.java revision 1870:4aa2e64eff30
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%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