Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlvaro Sanchez-Leon2016-10-04 02:01:24 +0000
committerMarc Khouzam2016-11-15 15:34:55 +0000
commit36fd1269194d1dcfba24ef338710b6eb4ca2a9f3 (patch)
tree61d4459e17de626ab3a5e027caeea14d7392b085 /dsf-gdb/org.eclipse.cdt.dsf.gdb.ui
parentb4cc24e31ea022ccffc3235ee77bbf41348768a3 (diff)
downloadorg.eclipse.cdt-36fd1269194d1dcfba24ef338710b6eb4ca2a9f3.tar.gz
org.eclipse.cdt-36fd1269194d1dcfba24ef338710b6eb4ca2a9f3.tar.xz
org.eclipse.cdt-36fd1269194d1dcfba24ef338710b6eb4ca2a9f3.zip
Bug 303808: Add a GDB CLI Console history buffer
Diffstat (limited to 'dsf-gdb/org.eclipse.cdt.dsf.gdb.ui')
-rw-r--r--dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java94
-rw-r--r--dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbTerminalConnector.java335
2 files changed, 335 insertions, 94 deletions
diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java
index e1c0daf854c..252f0e3a4a8 100644
--- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java
+++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java
@@ -7,23 +7,12 @@
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.console;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.HashSet;
-import java.util.Set;
-
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsoleView;
import org.eclipse.cdt.utils.pty.PTY;
import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.ui.DebugUITools;
-import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.AbstractConsole;
import org.eclipse.ui.console.IConsoleView;
@@ -62,89 +51,6 @@ public class GdbFullCliConsole extends AbstractConsole implements IGDBDebuggerCo
super.dispose();
}
- /**
- * This class will read from the GDB process output and error streams and will
- * write it to any registered ITerminalControl.
- * It must continue reading from the streams, even if there are no ITerminalControl
- * to write to. This is important to prevent GDB's output buffer from getting full
- * and then completely stopping.
- */
- private final class GdbTerminalConnector implements IGdbTerminalControlConnector {
- private final Set<ITerminalControl> fTerminalPageControls = new HashSet<>();
- private final Process fProcess;
- private final Job fOutputStreamJob;
- private final Job fErrorStreamJob;
-
- public GdbTerminalConnector(Process process) {
- fProcess = process;
-
- fOutputStreamJob = new OutputReadJob(process.getInputStream());
- fOutputStreamJob.schedule();
- fErrorStreamJob = new OutputReadJob(process.getErrorStream());
- fErrorStreamJob.schedule();
- }
-
- public void dispose() {
- fOutputStreamJob.cancel();
- fErrorStreamJob.cancel();
- }
-
- @Override
- public void addPageTerminalControl(ITerminalControl terminalControl) {
- fTerminalPageControls.add(terminalControl);
- }
-
- @Override
- public void removePageTerminalControl(ITerminalControl terminalControl) {
- if (terminalControl != null) {
- fTerminalPageControls.remove(terminalControl);
- }
- }
-
- @Override
- public OutputStream getTerminalToRemoteStream() {
- // When the user writes to the terminal, it should be sent
- // directly to GDB
- return fProcess.getOutputStream();
- }
-
- private class OutputReadJob extends Job {
- {
- setSystem(true);
- }
-
- private InputStream fInputStream;
-
- private OutputReadJob(InputStream inputStream) {
- super("GDB CLI output Job"); //$NON-NLS-1$
- fInputStream = inputStream;
- }
-
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- byte[] b = new byte[1024];
- int read = 0;
- do {
- if (monitor.isCanceled()) {
- break;
- }
-
- read = fInputStream.read(b);
- if (read > 0) {
- for (ITerminalControl control : fTerminalPageControls) {
- control.getRemoteToTerminalOutputStream().write(b, 0, read);
- }
- }
- } while (read >= 0);
- } catch (IOException e) {
- }
-
- return Status.OK_STATUS;
- }
- }
- }
-
@Override
public ILaunch getLaunch() { return fLaunch; }
diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbTerminalConnector.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbTerminalConnector.java
new file mode 100644
index 00000000000..119cac1f189
--- /dev/null
+++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbTerminalConnector.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson 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
+ *******************************************************************************/
+package org.eclipse.cdt.dsf.gdb.internal.ui.console;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
+import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl;
+
+/**
+ * This class will read from the GDB process output and error streams and will write it to any registered
+ * ITerminalControl. It must continue reading from the streams, even if there are no ITerminalControl to write
+ * to. This is important to prevent GDB's output buffer from getting full and then completely stopping.
+ *
+ * In addition this class manages a history buffer which will be used to populate a new console with history
+ * information already collected for the same session. Used for example when closing an re-opening a console.
+ */
+public class GdbTerminalConnector implements IGdbTerminalControlConnector {
+ /**
+ * The maximum number of lines the internal history buffer can hold
+ */
+ private static final int HIST_BUFFER_MAX_SIZE = 1000; /* lines */
+
+ /**
+ * The History buffer is written out in chunks of lines, this chunks are taken from the total history buffer
+ * and written out sequentially.
+ * This constant determines the writing size in number of lines (i.e. chunk size)
+ */
+ private static final int HIST_BUFFER_WRITE_SIZE = 100; /* lines */
+ private final Process fProcess;
+ private final Set<ITerminalControl> fTerminalPageControls = Collections.synchronizedSet(new HashSet<>());
+ private final Job fOutputStreamJob;
+ private final Job fErrorStreamJob;
+ private final ConsoleHistoryLinesBuffer fHistoryBuffer;
+
+ public GdbTerminalConnector(Process process) {
+ fProcess = process;
+
+ // Using a history buffer size aligned with the preferences for console buffering
+ // but not exceeding the internal maximum
+ // We cap the history buffer to an internal maximum in order to prevent excessive use
+ // of memory, the preference value applies to the console (not the history buffer) and can be specified
+ // to billions of lines.
+ // Handling billion of lines for the history buffer would require a completely different approach
+ // to this implementation, possibly making use of the hard disk instead of in memory.
+ IPreferenceStore store = GdbUIPlugin.getDefault().getPreferenceStore();
+ int prefBufferLines = store.getInt(IGdbDebugPreferenceConstants.PREF_CONSOLE_BUFFERLINES);
+ int history_buffer_size = prefBufferLines < HIST_BUFFER_MAX_SIZE ? prefBufferLines
+ : HIST_BUFFER_MAX_SIZE;
+
+ fHistoryBuffer = new ConsoleHistoryLinesBuffer(history_buffer_size);
+
+ // Start the jobs that read the GDB process output streams
+ String jobSuffix = ""; //$NON-NLS-1$
+ fOutputStreamJob = new OutputReadJob(process.getInputStream(), jobSuffix);
+ fOutputStreamJob.schedule();
+
+ jobSuffix = "-Error"; //$NON-NLS-1$
+ fErrorStreamJob = new OutputReadJob(process.getErrorStream(), jobSuffix);
+ fErrorStreamJob.schedule();
+ }
+
+ /**
+ * This class will hold a buffer of history lines, it uses a queue to easily pop out the oldest lines once
+ * the maximum is being exceeded.</br>
+ * It also keeps track of partial text at the end of the receiving input i.e. not yet forming a complete
+ * line, once it forms a complete line it gets integrated in the queue
+ *
+ * In addition the API used in this implementation are synchronized to allow consistent information among
+ * the Jobs using it
+ */
+ private class ConsoleHistoryLinesBuffer extends ArrayDeque<String> {
+
+ private static final long serialVersionUID = 1L;
+ /**
+ * Holds the last characters received but not yet forming a complete line, The HistoryBuffer contains
+ * complete lines to be able to keep a proper line count that can be then be dimensioned by e.g.
+ * preferences
+ */
+ private final StringBuilder fHistoryRemainder = new StringBuilder();
+
+ public ConsoleHistoryLinesBuffer(int size) {
+ super(size);
+ }
+
+ /**
+ * A simple container holding consistent information of the history lines and accumulated remainder
+ * at a particular point in time
+ */
+ private class HistorySnapShot {
+ private final String[] fHistoryLinesSnapShot;
+ private final String fHistoryRemainderSnapShot;
+ private HistorySnapShot(String[] historyLines, String historyRemainder) {
+ fHistoryLinesSnapShot = historyLines;
+ fHistoryRemainderSnapShot = historyRemainder;
+ }
+ }
+
+ @Override
+ public synchronized int size() {
+ return super.size();
+ }
+
+ /**
+ * @param text
+ * Accumulate the text not yet forming a line
+ */
+ private synchronized void appendRemainder(String text) {
+ fHistoryRemainder.append(text);
+ }
+
+ /**
+ * @return Returns the accumulated text and clears its internal value
+ */
+ private synchronized String popRemainder() {
+ String remainder = fHistoryRemainder.toString();
+ fHistoryRemainder.setLength(0);
+ return remainder;
+ }
+
+ /**
+ * @return The history information at a specific point in time
+ */
+ private synchronized HistorySnapShot getHistorySnapShot() {
+ return new HistorySnapShot(toArray(), fHistoryRemainder.toString());
+ }
+
+ /**
+ * Writes complete lines to the history buffer, and accumulates incomplete lines "remainder" until
+ * they form a full line.
+ *
+ * Adding complete lines to the buffer is needed to respect a specified maximum number of buffered
+ * lines
+ */
+ public synchronized void appendHistory(byte[] b, int read) {
+ // Read this new input
+ StringBuilder info = new StringBuilder(new String(b, StandardCharsets.UTF_8));
+ info.setLength(read);
+
+ // Separate by lines but keep the separator character
+ String regEx = "(?<=\\n)"; //$NON-NLS-1$
+ String[] chunks = info.toString().split(regEx);
+
+ for (int i = 0; i < chunks.length; i++) {
+ StringBuilder lineBuilder = new StringBuilder();
+ if (i == 0) {
+ // Add the previous incomplete line info ("remainder") first
+ lineBuilder.append(popRemainder());
+ }
+
+ lineBuilder.append(chunks[i]);
+ String line = lineBuilder.toString();
+
+ if (line.endsWith("\n")) { //$NON-NLS-1$
+ // We have build a complete line, So lets add it to the history
+ // Make sure we don't exceed the maximum buffer size
+ while (this.size() >= HIST_BUFFER_MAX_SIZE) {
+ this.remove();
+ }
+
+ this.offer(line);
+ } else {
+ // The only line with no separator shall be the last one
+ // otherwise it should have been split
+ assert i == (chunks.length - 1);
+ appendRemainder(line);
+ }
+ }
+ }
+
+ @Override
+ public synchronized String[] toArray() {
+ return super.toArray(new String[size()]);
+ }
+ }
+
+ public void dispose() {
+ fOutputStreamJob.cancel();
+ fErrorStreamJob.cancel();
+ }
+
+ @Override
+ public void addPageTerminalControl(ITerminalControl terminalControl) {
+ // write the currently available buffered history to this new terminal
+ new WriteHistoryJob(terminalControl).schedule();
+ }
+
+ @Override
+ public void removePageTerminalControl(ITerminalControl terminalControl) {
+ if (terminalControl != null) {
+ fTerminalPageControls.remove(terminalControl);
+ }
+ }
+
+ @Override
+ public OutputStream getTerminalToRemoteStream() {
+ // When the user writes to the terminal, it should be sent
+ // directly to GDB
+ return fProcess.getOutputStream();
+ }
+
+ private class OutputReadJob extends Job {
+ {
+ setSystem(true);
+ }
+
+ private InputStream fInputStream;
+
+ private OutputReadJob(InputStream procStream, String nameSuffix) {
+ super("GDB CLI output Job" + nameSuffix); //$NON-NLS-1$
+ fInputStream = procStream;
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ byte[] b = new byte[1024];
+ int read = 0;
+
+ do {
+ if (monitor.isCanceled()) {
+ break;
+ }
+
+ read = fInputStream.read(b);
+ if (read > 0) {
+ // Write fresh output to the existing consoles
+ synchronized (fTerminalPageControls) {
+ for (ITerminalControl control : fTerminalPageControls) {
+ control.getRemoteToTerminalOutputStream().write(b, 0, read);
+ }
+
+ // Add this input to the history buffer
+ fHistoryBuffer.appendHistory(b, read);
+ }
+ }
+ } while (read >= 0);
+ } catch (IOException e) {
+ }
+
+ return Status.OK_STATUS;
+ }
+ }
+
+ private class WriteHistoryJob extends Job {
+ {
+ setSystem(true);
+ }
+
+ private final ITerminalControl fTerminalControl;
+
+ public WriteHistoryJob(ITerminalControl terminalControl) {
+ super("GDB CLI write history job"); //$NON-NLS-1$
+ fTerminalControl = terminalControl;
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ OutputStream terminalOutputStream = fTerminalControl.getRemoteToTerminalOutputStream();
+ if (terminalOutputStream == null) {
+ return Status.OK_STATUS;
+ }
+
+ // Append the buffered lines to the terminal control instance
+ synchronized (fTerminalPageControls) {
+ // First get a snapshot of the current information in the history buffer
+ ConsoleHistoryLinesBuffer.HistorySnapShot history = fHistoryBuffer.getHistorySnapShot();
+ String[] buffLines = history.fHistoryLinesSnapShot;
+
+ // Writing the current buffer in chunks of data
+ // Calculate the initial limits
+ // The position pointed by 'end' is not written out on the iteration, but used as the limit
+ int start = 0;
+ int end = buffLines.length <= HIST_BUFFER_WRITE_SIZE ? buffLines.length : HIST_BUFFER_WRITE_SIZE;
+
+ // Write the history in chunks of lines
+ StringBuilder sb = new StringBuilder(HIST_BUFFER_WRITE_SIZE);
+
+ while (start < buffLines.length) {
+ // Prepare the data chunk to write
+ String[] chunk = Arrays.copyOfRange(buffLines, start, end);
+
+ for (String line : chunk) {
+ sb.append(line);
+ }
+
+ // Calculate limits for next iteration
+ start = end;
+ int linesLeft = buffLines.length - end;
+ end = start + (linesLeft <= HIST_BUFFER_WRITE_SIZE ? linesLeft : HIST_BUFFER_WRITE_SIZE);
+
+ // if this is the last write,
+ if (!(start < buffLines.length)) {
+ // Add the accumulated remainder value (i.e. not yet a complete line) as the last line
+ sb.append(history.fHistoryRemainderSnapShot);
+ }
+
+ // Write to Output Stream
+ if (sb.length() > 0) {
+ byte[] bytes = sb.toString().getBytes();
+ try {
+ terminalOutputStream.write(bytes, 0, bytes.length);
+ } catch (IOException e) {
+ }
+ sb.setLength(0);
+ }
+ }
+ // Add it to the list so it can now receive new input
+ fTerminalPageControls.add(fTerminalControl);
+ }
+
+ return Status.OK_STATUS;
+ }
+ }
+
+} \ No newline at end of file

Back to the top