import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNullContents;
import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils.OutputReaderFunction;
import org.eclipse.tracecompass.internal.analysis.lami.core.Activator;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.LamiStrings;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.ShellUtils;
@VisibleForTesting
protected @Nullable String getOutputFromCommand(List<String> command) {
LOGGER.info(() -> LOG_RUNNING_MESSAGE + ' ' + command.toString());
-
- try {
- ProcessBuilder builder = new ProcessBuilder(command);
- builder.redirectErrorStream(true);
-
- Process p = builder.start();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
- int ret = p.waitFor();
- String output = br.lines().collect(Collectors.joining());
-
- return (ret == 0 ? output : null);
- }
- } catch (IOException | InterruptedException e) {
+ List<String> lines = ProcessUtils.getOutputFromCommand(command);
+ if (lines == null) {
return null;
}
+ return String.join("", lines); //$NON-NLS-1$
}
/**
@VisibleForTesting
protected String getResultsFromCommand(List<String> command, IProgressMonitor monitor)
throws CoreException {
+ List<String> lines = ProcessUtils.getOutputFromCommandCancellable(command, monitor, nullToEmptyString(Messages.LamiAnalysis_MainTaskName), OUTPUT_READER);
+ return checkNotNull(String.join("", lines)); //$NON-NLS-1$
+ }
- final int scale = 1000;
+ private static final OutputReaderFunction OUTPUT_READER = (reader, monitor) -> {
double workedSoFar = 0.0;
- ProcessCanceller cancellerRunnable = null;
- Thread cancellerThread = null;
-
- try {
- monitor.beginTask(Messages.LamiAnalysis_MainTaskName, scale);
-
- ProcessBuilder builder = new ProcessBuilder(command);
- builder.redirectErrorStream(false);
-
- Process p = checkNotNull(builder.start());
-
- cancellerRunnable = new ProcessCanceller(p, monitor);
- cancellerThread = new Thread(cancellerRunnable);
- cancellerThread.start();
+ String line = reader.readLine();
+ while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
+ /*
+ * This is a line indicating progress, it has the form:
+ *
+ * 0.123 3000 of 5000 events processed
+ *
+ * The first part indicates the estimated fraction (out of 1.0) of
+ * work done. The second part is status text.
+ */
- List<String> results = new ArrayList<>();
+ // Trim the line first to make sure the first character is
+ // significant
+ line = line.trim();
- try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
- String line = in.readLine();
- while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
- /*
- * This is a line indicating progress, it has the form:
- *
- * 0.123 3000 of 5000 events processed
- *
- * The first part indicates the estimated fraction (out of
- * 1.0) of work done. The second part is status text.
- */
+ // Split at the first space
+ String[] elems = line.split(" ", 2); //$NON-NLS-1$
- // Trim the line first to make sure the first character is
- // significant
- line = line.trim();
-
- // Split at the first space
- String[] elems = line.split(" ", 2); //$NON-NLS-1$
-
- if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
- // It looks like we have a progress indication
- try {
- // Try parsing the number
- double cumulativeWork = Double.parseDouble(elems[0]) * scale;
- double workedThisLoop = cumulativeWork - workedSoFar;
-
- // We're going backwards? Do not update the
- // monitor's value
- if (workedThisLoop > 0) {
- monitor.internalWorked(workedThisLoop);
- workedSoFar = cumulativeWork;
- }
-
- // There is a message: update the monitor's task name
- if (elems.length >= 2) {
- monitor.setTaskName(elems[1].trim());
- }
- } catch (NumberFormatException e) {
- // Continue reading progress lines anyway
- }
+ if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
+ // It looks like we have a progress indication
+ try {
+ // Try parsing the number
+ double cumulativeWork = Double.parseDouble(elems[0]) * 1000;
+ double workedThisLoop = cumulativeWork - workedSoFar;
+
+ // We're going backwards? Do not update the
+ // monitor's value
+ if (workedThisLoop > 0) {
+ monitor.internalWorked(workedThisLoop);
+ workedSoFar = cumulativeWork;
}
- line = in.readLine();
- }
- while (line != null) {
- /*
- * We have seen the first line containing a '{', this is our
- * JSON output!
- */
- results.add(line);
- line = in.readLine();
- }
- }
- int ret = p.waitFor();
-
- if (monitor.isCanceled()) {
- /* We were interrupted by the canceller thread. */
- IStatus status = new Status(IStatus.CANCEL, Activator.instance().getPluginId(), null);
- throw new CoreException(status);
- }
-
- if (ret != 0) {
- /*
- * Something went wrong running the external script. We will
- * gather the stderr and report it to the user.
- */
- BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
- List<String> stdErrOutput = br.lines().collect(Collectors.toList());
-
- MultiStatus status = new MultiStatus(Activator.instance().getPluginId(),
- IStatus.ERROR, Messages.LamiAnalysis_ErrorDuringExecution, null);
- for (String str : stdErrOutput) {
- status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), str));
- }
- if (stdErrOutput.isEmpty()) {
- /*
- * At least say "no output", so an error message actually
- * shows up.
- */
- status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.LamiAnalysis_ErrorNoOutput));
- }
- throw new CoreException(status);
- }
-
- /* External script ended successfully, all is fine! */
- String resultsStr = results.stream().collect(Collectors.joining());
- return checkNotNull(resultsStr);
-
- } catch (IOException | InterruptedException e) {
- IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.LamiAnalysis_ExecutionInterrupted, e);
- throw new CoreException(status);
-
- } finally {
- if (cancellerRunnable != null) {
- cancellerRunnable.setFinished();
- }
- if (cancellerThread != null) {
- try {
- cancellerThread.join();
- } catch (InterruptedException e) {
+ // There is a message: update the monitor's task name
+ if (elems.length >= 2) {
+ monitor.setTaskName(elems[1].trim());
+ }
+ } catch (NumberFormatException e) {
+ // Continue reading progress lines anyway
}
}
- monitor.done();
- }
- }
-
- private static class ProcessCanceller implements Runnable {
-
- private final Process fProcess;
- private final IProgressMonitor fMonitor;
-
- private boolean fIsFinished = false;
-
- public ProcessCanceller(Process process, IProgressMonitor monitor) {
- fProcess = process;
- fMonitor = monitor;
- }
-
- public void setFinished() {
- fIsFinished = true;
+ line = reader.readLine();
}
- @Override
- public void run() {
- try {
- while (!fIsFinished) {
- Thread.sleep(500);
- if (fMonitor.isCanceled()) {
- fProcess.destroy();
- return;
- }
- }
- } catch (InterruptedException e) {
- }
+ List<String> results = new ArrayList<>();
+ while (line != null) {
+ /*
+ * We have seen the first line containing a '{', this is our JSON
+ * output!
+ */
+ results.add(line);
+ line = reader.readLine();
}
+ return results;
+ };
- }
@Override
public @NonNull String getName() {
org.eclipse.tracecompass.common.core.format,
org.eclipse.tracecompass.common.core.log,
org.eclipse.tracecompass.common.core.math,
+ org.eclipse.tracecompass.common.core.process,
org.eclipse.tracecompass.internal.common.core;x-internal:=true
Import-Package: com.google.common.collect
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.process;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+// public static String LamiAnalysis_MainTaskName;
+// public static String LamiAnalysis_NoResults;
+ public static String ProcessUtils_ErrorDuringExecution;
+ public static String ProcessUtils_ErrorNoOutput;
+ public static String ProcessUtils_ExecutionInterrupted;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {}
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.process;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.common.core.Activator;
+
+/**
+ * Common utility methods for launching external processes and retrieving their
+ * output.
+ *
+ * @author Alexandre Montplaisir
+ * @since 2.2
+ */
+public final class ProcessUtils {
+
+ private ProcessUtils() {}
+
+ /**
+ * Simple output-getting command. Cannot be cancelled, and will return null
+ * if the external process exits with a non-zero return code.
+ *
+ * @param command
+ * The command (executable + arguments) to launch
+ * @return The process's standard output upon completion
+ */
+ public static @Nullable List<String> getOutputFromCommand(List<String> command) {
+ try {
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(true);
+
+ Process p = builder.start();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+ List<String> output = new LinkedList<>();
+
+ /*
+ * We must consume the output before calling Process.waitFor(),
+ * or else the buffers might fill and block the external program
+ * if there is a lot of output.
+ */
+ String line = br.readLine();
+ while (line != null) {
+ output.add(line);
+ line = br.readLine();
+ }
+
+ int ret = p.waitFor();
+ return (ret == 0 ? output : null);
+ }
+ } catch (IOException | InterruptedException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Interface defining what do to with a process's output. For use with
+ * {@link #getOutputFromCommandCancellable}.
+ */
+ @FunctionalInterface
+ public static interface OutputReaderFunction {
+
+ /**
+ * Handle the output of the process. This can include reporting progress
+ * to the monitor, and pre-processing the returned output.
+ *
+ * @param reader
+ * A buffered reader to the process's standard output.
+ * Managed internally, so you do not need to
+ * {@link BufferedReader#close()} it.
+ * @param monitor
+ * The progress monitor. Implementation should check
+ * periodically if it is cancelled to end processing early.
+ * The monitor's start and end will be managed, but progress
+ * can be reported via the {@link IProgressMonitor#worked}
+ * method. The total is 1000 work units.
+ * @return The process's output
+ * @throws IOException
+ * If there was a read error. Letting throw all exception
+ * from the {@link BufferedReader} is recommended.
+ */
+ List<String> readOutput(BufferedReader reader, IProgressMonitor monitor) throws IOException;
+ }
+
+ /**
+ * Cancellable output-getting command. The processing, as well as the
+ * external process itself, can be stopped by cancelling the passed progress
+ * monitor.
+ *
+ * @param command
+ * The command (executable + arguments) to execute
+ * @param monitor
+ * The progress monitor to check for cancellation and optionally
+ * progress
+ * @param mainTaskName
+ * The main task name of the job
+ * @param readerFunction
+ * What to do with the output. See {@link OutputReaderFunction}.
+ * @return The process's standard output, upon normal completion
+ * @throws CoreException
+ * If a problem happened with the execution of the external
+ * process. It can be reported to the user with the help of an
+ * ErrorDialog.
+ */
+ public static List<String> getOutputFromCommandCancellable(List<String> command,
+ IProgressMonitor monitor,
+ String mainTaskName,
+ OutputReaderFunction readerFunction)
+ throws CoreException {
+
+ CancellableRunnable cancellerRunnable = null;
+ Thread cancellerThread = null;
+
+ try {
+ monitor.beginTask(mainTaskName, 1000);
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(false);
+
+ Process p = checkNotNull(builder.start());
+
+ cancellerRunnable = new CancellableRunnable(p, monitor);
+ cancellerThread = new Thread(cancellerRunnable);
+ cancellerThread.start();
+
+ try (BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
+
+ List<String> lines = readerFunction.readOutput(stdoutReader, monitor);
+
+ int ret = p.waitFor();
+
+ if (monitor.isCanceled()) {
+ /* We were interrupted by the canceller thread. */
+ IStatus status = new Status(IStatus.CANCEL, Activator.instance().getPluginId(), null);
+ throw new CoreException(status);
+ }
+
+ if (ret != 0) {
+ /*
+ * Something went wrong running the external process. We
+ * will gather the stderr and report it to the user.
+ */
+ BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+ List<String> stderrOutput = stderrReader.lines().collect(Collectors.toList());
+
+ MultiStatus status = new MultiStatus(Activator.instance().getPluginId(),
+ IStatus.ERROR, Messages.ProcessUtils_ErrorDuringExecution, null);
+ for (String str : stderrOutput) {
+ status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), str));
+ }
+ if (stderrOutput.isEmpty()) {
+ /*
+ * At least say "no output", so an error message actually
+ * shows up.
+ */
+ status.add(new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ErrorNoOutput));
+ }
+ throw new CoreException(status);
+ }
+
+ return lines;
+ }
+
+ } catch (IOException | InterruptedException e) {
+ IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), Messages.ProcessUtils_ExecutionInterrupted, e);
+ throw new CoreException(status);
+
+ } finally {
+ if (cancellerRunnable != null) {
+ cancellerRunnable.setFinished();
+ }
+ if (cancellerThread != null) {
+ try {
+ cancellerThread.join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ monitor.done();
+ }
+ }
+
+ /**
+ * Internal wrapper class that allows forcibly stopping a {@link Process}
+ * when its corresponding progress monitor is cancelled.
+ */
+ private static class CancellableRunnable implements Runnable {
+
+ private final Process fProcess;
+ private final IProgressMonitor fMonitor;
+
+ private boolean fIsFinished = false;
+
+ public CancellableRunnable(Process process, IProgressMonitor monitor) {
+ fProcess = process;
+ fMonitor = monitor;
+ }
+
+ public void setFinished() {
+ fIsFinished = true;
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!fIsFinished) {
+ Thread.sleep(500);
+ if (fMonitor.isCanceled()) {
+ fProcess.destroy();
+ return;
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2016 EfficiOS Inc. and others
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+ProcessUtils_NoResults = No results were returned.
+ProcessUtils_ErrorDuringExecution = Error during execution of the script.
+ProcessUtils_ErrorNoOutput = (No output)
+ProcessUtils_ExecutionInterrupted = Execution was interrupted.
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.common.core.process;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
+import org.eclipse.tracecompass.common.core.process.ProcessUtils;
import org.eclipse.tracecompass.tmf.core.event.lookup.TmfCallsite;
import com.google.common.base.Objects;
List<Addr2lineInfo> callsites = new LinkedList<>();
// FIXME Could eventually use CDT's Addr2line class once it implements --inlines
- List<String> output = getOutputFromCommand(Arrays.asList(
- ADDR2LINE_EXECUTABLE, "-i", "-f", "-C", "-e", filePath, "0x" + Long.toHexString(offset))); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+ List<String> command = Arrays.asList(ADDR2LINE_EXECUTABLE, "-i", "-f", "-C", "-e", filePath, "0x" + Long.toHexString(offset)); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+ List<String> output = ProcessUtils.getOutputFromCommand(command);
if (output == null) {
/* Command returned an error */
return callsites;
}
-
- private static @Nullable List<String> getOutputFromCommand(List<String> command) {
- try {
- ProcessBuilder builder = new ProcessBuilder(command);
- builder.redirectErrorStream(true);
-
- Process p = builder.start();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));) {
- int ret = p.waitFor();
- List<String> lines = br.lines().collect(Collectors.toList());
-
- return (ret == 0 ? lines : null);
- }
- } catch (IOException | InterruptedException e) {
- return null;
- }
- }
}