Skip to content

Commit

Permalink
Use Groovysh set builtin for result peek sizes
Browse files Browse the repository at this point in the history
When a gremlin.sh command evaluates to an iterable, the number of
initial elements in the iterable printed to the console is now
controlled by a pair of Groovysh settings:

* `set tp-olap-result-peek <int>` (default 50)
* `set tp-oltp-result-peek <int>` (default 500)

The OLAP setting controls the number of initial lines read out of
result files produced by executing a HadoopPipeline (i.e. a
titan-hadoop/Faunus job).

The OLTP setting controls the number of initial elements read out of
any other iterable result.

Negative ints are treated as if Integer.MAX_VALUE were specified.
Zero has slightly special interpretation; it completely disables
iterator peeking.  Zero means that the shell will not even call
hasNext to decide whether to print "...".  Positive ints are used
exactly as specified.

Settings are persistent across gremlin.sh sessions.  They're stored in
the Java user Preferences system under the node
"/org/codehaus/groovy/tools/shell".  On *NIX systems, this is usually
in ~/.java/.userPrefs/org/codehaus/groovy/tools/shell/prefs.xml.  You
may notice that the implementation in this commit does not actually
call Preferences.flush anywhere to guarantee persistence.  It relies
instead on this guarantee from the Preferences javadoc: "Normal
termination of the Java Virtual Machine will not result in the loss of
pending updates -- an explicit flush invocation is not required upon
termination to ensure that pending updates are made persistent."

For thinkaurelius#924
  • Loading branch information
dalaro committed Feb 11, 2015
1 parent dcf2924 commit 71bd1d7
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import org.codehaus.groovy.tools.shell.Groovysh;
import org.codehaus.groovy.tools.shell.IO;
import org.codehaus.groovy.tools.shell.InteractiveShellRunner;
import org.codehaus.groovy.tools.shell.util.Preferences;

import java.io.*;
import java.nio.charset.Charset;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;

/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
Expand Down Expand Up @@ -44,8 +47,13 @@ public Console(final IO io, final String inputPrompt, final String resultPrompt,
groovy.execute(evs);
}

// Instantiate console objects: the ResultHook, History handler, ErrorHook, and InteractiveShellRunner
groovy.setResultHook(new ResultHookClosure(groovy, io, resultPrompt));
// Instantiate console objects: the ResultHook, History handler, ErrorHook, ConsolePreferenceChangeListener and InteractiveShellRunner
ConsolePreferenceChangeListener prefListener = new ConsolePreferenceChangeListener();
Preferences.addChangeListener(prefListener);

ResultHookClosure resultHook = new ResultHookClosure(groovy, io, resultPrompt);
resultHook.setConsolePreferenceConsumers(prefListener);
groovy.setResultHook(resultHook);
groovy.setHistory(new History());

final InteractiveShellRunner runner = new InteractiveShellRunner(groovy, new PromptClosure(groovy, inputPrompt));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.thinkaurelius.titan.hadoop.tinkerpop.gremlin;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import org.codehaus.groovy.tools.shell.util.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;

public class ConsolePreferenceChangeListener implements PreferenceChangeListener {

static final String PREF_TINKERPOP_PREFIX = "tp-";

private static final Logger log =
LoggerFactory.getLogger(ConsolePreferenceChangeListener.class);

private final ConcurrentHashMap<String, Function<PreferenceChangeEvent, ?>> prefChangeConsumers =
new ConcurrentHashMap<String, Function<PreferenceChangeEvent, ?>>();

/**
* Add a new console preference consumer, and, if the supplied key maps to
* a non-null console preference value, immediately fire a change event
*
*
* @param triggerPrefKey
* @param consumer
*/
public void setConsumer(String triggerPrefKey, Function<PreferenceChangeEvent, ?> consumer) {

Preconditions.checkNotNull(triggerPrefKey);
// Preferences javadoc mandates that no path contain successive slashes
Preconditions.checkArgument(!triggerPrefKey.startsWith("/"));

String k = PREF_TINKERPOP_PREFIX + triggerPrefKey;

Function<?, ?> oldConsumer = prefChangeConsumers.putIfAbsent(k, consumer);

if (null == oldConsumer) {
log.debug("Installing new preference consumer for key {}", k);
} else {
log.debug("Replacing existing preference consumer for key {} (old consumer: {})",
k, oldConsumer);
}

String currentValue = Preferences.get(k);
if (null != currentValue) {
log.debug("Resetting stored value to trigger consumer: {}={}", k, currentValue);
Preferences.put(k, currentValue);
} else {
log.debug("Read null for {}", k);
}
}

@Override
public void preferenceChange(PreferenceChangeEvent evt) {
// This is probably never null, but why not check
if (null == evt || null == evt.getKey())
return;

String k = evt.getKey();

Function<PreferenceChangeEvent, ?> consumer = prefChangeConsumers.get(k);

if (null == consumer) {
log.debug("Ignoring preference key {} (no consumer registered)", k);
return;
}

log.debug("Invoking consumer {} for key {}", consumer, k);

consumer.apply(evt); // TODO uncaught exception handling?
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.thinkaurelius.titan.hadoop.tinkerpop.gremlin;

import com.google.common.base.Function;
import com.thinkaurelius.titan.hadoop.HadoopPipeline;
import com.thinkaurelius.titan.hadoop.Tokens;
import com.thinkaurelius.titan.hadoop.hdfs.HDFSTools;
Expand All @@ -13,16 +14,29 @@
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.codehaus.groovy.tools.shell.IO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.prefs.PreferenceChangeEvent;

/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public class ResultHookClosure extends Closure {

private static final Logger log =
LoggerFactory.getLogger(ResultHookClosure.class);

private final String resultPrompt;
private final IO io;
private static final int LINES = 15;

// Defaults established in #924
private static final int OLAP_PEEK_DEFAULT = 50;
private static final int OLTP_PEEK_DEFAULT = 500;

private int olapPeek = OLAP_PEEK_DEFAULT;
private int oltpPeek = OLTP_PEEK_DEFAULT;

public ResultHookClosure(final Object owner, final IO io, final String resultPrompt) {
super(owner);
Expand All @@ -33,29 +47,88 @@ public ResultHookClosure(final Object owner, final IO io, final String resultPro
public Object call(final Object[] args) {
final Object result = args[0];
final Iterator itty;
final int peekLines;

if (result instanceof HadoopPipeline) {
peekLines = olapPeek;

if (0 == peekLines)
return null;

try {
final HadoopPipeline pipeline = (HadoopPipeline) result;
pipeline.submit();
final FileSystem hdfs = FileSystem.get(pipeline.getGraph().getConf());
final Path output = HDFSTools.getOutputsFinalJob(hdfs, pipeline.getGraph().getJobDir().toString());
itty = new TextFileLineIterator(hdfs, hdfs.globStatus(new Path(output.toString() + "/" + Tokens.SIDEEFFECT + "*")), LINES + 1);
// Avoid overflow; olapPeek will be Integer.MAX_VALUE if user asked for -1
// The point of +1 is to let us tell whether we ran out of lines or hit the peek limit
int peekPlusOne = peekLines == Integer.MAX_VALUE ? Integer.MAX_VALUE : peekLines + 1;
itty = new TextFileLineIterator(hdfs, hdfs.globStatus(new Path(output.toString() + "/" + Tokens.SIDEEFFECT + "*")), peekPlusOne);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
} else {
peekLines = oltpPeek;

if (0 == peekLines)
return null;

itty = new ToStringPipe();
((Pipe) itty).setStarts(new SingleIterator<Object>(result));
}

int linesPrinted = 0;
while (itty.hasNext() && linesPrinted < LINES) {
while (itty.hasNext() && linesPrinted < peekLines) {
this.io.out.println(this.resultPrompt + itty.next());
linesPrinted++;
}
if (linesPrinted == LINES && itty.hasNext())
if (linesPrinted == peekLines && itty.hasNext())
this.io.out.println(this.resultPrompt + "...");

return null;
}

void setConsolePreferenceConsumers(ConsolePreferenceChangeListener listener) {
listener.setConsumer("olap-result-peek", new OLAPPeekConsumer());
listener.setConsumer("oltp-result-peek", new OLTPPeekConsumer());
}

private int safeIntConversion(String raw, int defaultValue) {
int l = defaultValue;

try {
if (null != raw) {
try {
l = Integer.parseInt(raw);
log.debug("Parsed integer {} (original string: \"{}\")", l, raw);
if (l < 0) {
log.debug("Overwriting negative integer {} with {}", l, Integer.MAX_VALUE);
l = Integer.MAX_VALUE;
}
} catch (NumberFormatException e) {
log.warn("String {} could not be converted to a number", raw, e);
}
}
} catch (Throwable t) {
log.warn("Error parsing {}", raw, t);
}

return l;
}

private class OLTPPeekConsumer implements Function<PreferenceChangeEvent, Void> {
@Override
public Void apply(PreferenceChangeEvent input) {
oltpPeek = safeIntConversion(input.getNewValue(), OLTP_PEEK_DEFAULT);
return null;
}
}

private class OLAPPeekConsumer implements Function<PreferenceChangeEvent, Void> {
@Override
public Void apply(PreferenceChangeEvent input) {
olapPeek = safeIntConversion(input.getNewValue(), OLAP_PEEK_DEFAULT);
return null;
}
}
}

0 comments on commit 71bd1d7

Please sign in to comment.