# HG changeset patch # User Michael Van De Vanter # Date 1422419201 28800 # Node ID ac114ad31cddb13037c91991577810305a93277b # Parent 50b22daf6d53dd9c0362abb8394f53d1b4a1dda5 Truffle/Tools: a new framework for pluggable tools that gather Truffle execution data - Abstract com.oracle.truffle.api.instrument.TruffleTool defines, documents, and enforces a standard "life cycle": -- includes creation, installation, enabling/disabling, and disposing. -- data retrieval is tool dependent, but each is encouraged to provide a default print() method for demo & debugging - com.oracle.truffle.api.tools contains three instances, all language-agnostic: -- LineToProbesMap: existing utility used by the debugger, adapted to the frameowrk -- CoverageTracker: code coverage, tabulated by line -- NodeExecCounter: raw execution counts, tabulated by node type, can be filtered by SyntaxTag diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/TruffleTool.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/TruffleTool.java Tue Jan 27 20:26:41 2015 -0800 @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.instrument; + +/** + * {@linkplain Instrument Instrumentation}-based tools that gather data during Guest Language + * program execution. + *

+ * Tools share a common life cycle: + *

+ *

+ * Tool-specific methods that access data collected by the tool should: + *

+ * Note:
+ * Tool installation is currently global to the Truffle Execution environment. When + * language-agnostic management of individual execution environments is added to the platform, + * installation will be (optionally) specific to a single execution environment. + */ +public abstract class TruffleTool { + + private enum ToolState { + UNINSTALLED, + ENABLED_INSTALLED, + DISABLED_INSTALLED, + DISPOSED; + } + + private ToolState toolState = ToolState.UNINSTALLED; + + protected TruffleTool() { + + } + + /** + * Connect the tool to some part of the Truffle runtime, and enable data collection to start. + * Instrumentation will only be added to subsequently created ASTs. + * + * @throws IllegalStateException if the tool has previously been installed. + */ + public final void install() { + if (toolState != ToolState.UNINSTALLED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has already been installed"); + } + if (internalInstall()) { + toolState = ToolState.ENABLED_INSTALLED; + } + } + + /** + * @return whether the tool is currently collecting data. + */ + public final boolean isEnabled() { + return toolState == ToolState.ENABLED_INSTALLED; + } + + /** + * Switches tool state between enabled (collecting data) and disabled (not + * collecting data, but keeping data already collected). + * + * @throws IllegalStateException if not yet installed or disposed. + */ + public final void setEnabled(boolean isEnabled) { + if (toolState == ToolState.UNINSTALLED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed"); + } + if (toolState == ToolState.DISPOSED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed"); + } + internalSetEnabled(isEnabled); + toolState = isEnabled ? ToolState.ENABLED_INSTALLED : ToolState.DISABLED_INSTALLED; + } + + /** + * Clears any data already collected, but otherwise does not change the state of the tool. + * + * @throws IllegalStateException if not yet installed or disposed. + */ + public final void reset() { + if (toolState == ToolState.UNINSTALLED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed"); + } + if (toolState == ToolState.DISPOSED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed"); + } + internalReset(); + } + + /** + * Makes the tool permanently disabled, removes instrumentation, but keeps data already + * collected. + * + * @throws IllegalStateException if not yet installed or disposed. + */ + public final void dispose() { + if (toolState == ToolState.UNINSTALLED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " not yet installed"); + } + if (toolState == ToolState.DISPOSED) { + throw new IllegalStateException("Tool " + getClass().getSimpleName() + " has been disposed"); + } + internalDispose(); + toolState = ToolState.DISPOSED; + } + + /** + * @return whether the installation succeeded. + */ + protected abstract boolean internalInstall(); + + /** + * No subclass action required. + * + * @param isEnabled + */ + protected void internalSetEnabled(boolean isEnabled) { + } + + protected abstract void internalReset(); + + protected abstract void internalDispose(); + +} diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToProbesMap.java --- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToProbesMap.java Tue Jan 27 20:25:26 2015 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.api.instrument.impl; - -import java.io.*; -import java.util.*; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.Probe.ProbeListener; -import com.oracle.truffle.api.source.*; - -/** - * A mapping from {@link LineLocation} (a line number in a specific piece of {@link Source} code) to - * a collection of {@link Probe}s whose associated {@link SourceSection} starts on that line. - */ -public class LineToProbesMap implements ProbeListener { - - private static final boolean TRACE = false; - private static final PrintStream OUT = System.out; - - private static void trace(String msg) { - OUT.println("LineToProbesMap: " + msg); - } - - /** - * Map: Source line ==> probes associated with source sections starting on the line. - */ - private final Map> lineToProbesMap = new HashMap<>(); - - public LineToProbesMap() { - } - - public void startASTProbing(Source source) { - } - - public void newProbeInserted(Probe probe) { - final SourceSection sourceSection = probe.getProbedSourceSection(); - if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) { - final LineLocation lineLocation = sourceSection.getLineLocation(); - if (TRACE) { - trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription()); - } - this.addProbeToLine(lineLocation, probe); - } - } - - public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { - // This map ignores tags - } - - public void endASTProbing(Source source) { - } - - /** - * Returns the {@link Probe}, if any, associated with source that starts on a specified line; if - * there are more than one, return the one with the first starting character location. - */ - public Probe findLineProbe(LineLocation lineLocation) { - Probe probe = null; - final Collection probes = getProbesAtLine(lineLocation); - for (Probe probesOnLine : probes) { - if (probe == null) { - probe = probesOnLine; - } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) { - probe = probesOnLine; - } - } - return probe; - } - - /** - * Records creation of a probe whose associated source starts on the given line. - *

- * If the line already exists in the internal {@link #lineToProbesMap}, this probe will be added - * to the existing collection. If no line already exists in the internal map, then a new key is - * added along with a new collection containing the probe. - *

- * This class requires that each added line/probe pair hasn't been previously added. However, - * attaching the same probe to a new line location is allowed. - * - * @param line The {@link LineLocation} to attach the probe to. - * @param probe The {@link Probe} to attach for that line location. - */ - protected void addProbeToLine(LineLocation line, Probe probe) { - - if (!lineToProbesMap.containsKey(line)) { - // Key does not exist, add new probe list - final ArrayList newProbeList = new ArrayList<>(2); - newProbeList.add(probe); - lineToProbesMap.put(line, newProbeList); - } else { - // Probe list exists, add to existing - final Collection existingProbeList = lineToProbesMap.get(line); - assert !existingProbeList.contains(probe); - existingProbeList.add(probe); - } - } - - /** - * - * Returns a collection of {@link Probe}s whose associated source begins at the given - * {@link LineLocation}, an empty list if none. - * - * @param line The line to check. - * @return A collection of probes at the given line. - */ - public Collection getProbesAtLine(LineLocation line) { - Collection probesList = lineToProbesMap.get(line); - - if (probesList == null) { - return Collections.emptyList(); - } - return probesList; - } - - /** - * Convenience method to get probes according to a int line number. Returns a collection of - * {@link Probe}s at the given line number, an empty list if none. - * - * @param lineNumber The line number to check. - * @return A collection of probes at the given line. - */ - public Collection getProbesAtLineNumber(int lineNumber) { - - final Set keySet = lineToProbesMap.keySet(); - if (keySet.size() == 0) { - return Collections.emptyList(); - } - - ArrayList probes = new ArrayList<>(); - for (LineLocation line : keySet) { - if (line.getLineNumber() == lineNumber) { - probes.addAll(lineToProbesMap.get(line)); - } - } - - return probes; - } - - public void forget(Source source) { - final Set mappedLines = lineToProbesMap.keySet(); - if (mappedLines.size() > 0) { - List forgetLines = new ArrayList<>(); - for (LineLocation line : mappedLines) { - if (line.getSource().equals(source)) { - forgetLines.add(line); - } - } - for (LineLocation line : forgetLines) { - lineToProbesMap.remove(line); - } - } - } -} diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToSourceSectionMap.java --- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/instrument/impl/LineToSourceSectionMap.java Tue Jan 27 20:25:26 2015 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.truffle.api.instrument.impl; - -import java.io.*; -import java.util.*; - -import com.oracle.truffle.api.instrument.*; -import com.oracle.truffle.api.instrument.Probe.*; -import com.oracle.truffle.api.source.*; - -/** - * A mapping from {@link LineLocation} (a line number in a specific piece of {@link Source} code) to - * a collection of {@link SourceSection}s that exist on that line. This class assumes that all nodes - * are instrumented as it uses the {@link ProbeListener} interface to determine the source sections - * that exist in the file. - */ -public class LineToSourceSectionMap implements ProbeListener { - - private static final boolean TRACE = false; - private static final PrintStream OUT = System.out; - - private static void trace(String msg) { - OUT.println("LineToSourceSectionMap: " + msg); - } - - /** - * Map: Source line ==> source sections that exist on the line. - */ - private final Map> lineToSourceSectionsMap = new HashMap<>(); - - public LineToSourceSectionMap() { - } - - public void startASTProbing(Source source) { - } - - public void newProbeInserted(Probe probe) { - final SourceSection sourceSection = probe.getProbedSourceSection(); - if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) { - final LineLocation lineLocation = sourceSection.getLineLocation(); - if (TRACE) { - trace("NEW " + lineLocation.getShortDescription() + " Probe=" + probe); - } - this.addSourceSectionToLine(lineLocation, sourceSection); - } - } - - public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { - // This map ignores tags, but this subclasses can override this method to operate on tags. - } - - public void endASTProbing(Source source) { - } - - /** - * Adds a source section to the given line. - *

- * If the line already exists in the internal {@link #lineToSourceSectionsMap}, this source - * section will be added to the existing collection. If no line already exists in the internal - * map, then a new key is added along with a new collection containing the source section. - *

- * This class does not check if a source section has already been added to a line. - * - * @param line The {@link LineLocation} to attach the source section to. - * @param sourceSection The {@link SourceSection} to attach for that line location. - */ - protected void addSourceSectionToLine(LineLocation line, SourceSection sourceSection) { - if (!lineToSourceSectionsMap.containsKey(line)) { - // Key does not exist, add new source section list - final ArrayList newSourceSectionList = new ArrayList<>(2); - newSourceSectionList.add(sourceSection); - lineToSourceSectionsMap.put(line, newSourceSectionList); - } else { - // Source section list exists, add to existing - final Collection existingSourceSectionList = lineToSourceSectionsMap.get(line); - existingSourceSectionList.add(sourceSection); - } - } - - /** - * Returns a collection of {@link SourceSection}s at the given {@link LineLocation}, an empty - * list if none. - * - * @param line The line to check. - * @return the source sections at the given line. - */ - public Collection getSourceSectionsAtLine(LineLocation line) { - Collection sourceSectionList = lineToSourceSectionsMap.get(line); - - if (sourceSectionList == null) { - return Collections.emptyList(); - } - return sourceSectionList; - } - - /** - * Convenience method to get source sections according to a int line number. Returns a - * collection of {@link SourceSection}s at the given line number. If there are no source - * sections at that line, an empty list is returned. - * - * @param lineNumber The line number to check. - * @return A collection of source sections at the given line. - */ - public Collection getSourceSectionsAtLineNumber(int lineNumber) { - - final Set keySet = lineToSourceSectionsMap.keySet(); - if (keySet.size() == 0) { - return Collections.emptyList(); - } - - final ArrayList sourceSections = new ArrayList<>(); - for (LineLocation line : keySet) { - if (line.getLineNumber() == lineNumber) { - sourceSections.addAll(lineToSourceSectionsMap.get(line)); - } - } - - return sourceSections; - } -} diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java Tue Jan 27 20:26:41 2015 -0800 @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.tools; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.*; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.instrument.impl.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.nodes.Node.Child; +import com.oracle.truffle.api.source.*; + +/** + * A {@link TruffleTool} that counts interpreter execution calls to AST nodes that hold a + * specified {@linkplain SyntaxTag tag}, tabulated by source and line number associated with each + * node. Tags are presumed to be applied external to the tool. If no tag is specified, + * {@linkplain StandardSyntaxTag#STATEMENT STATEMENT} is used, corresponding to conventional + * behavior for code coverage tools. + *

+ * Tool Life Cycle + *

+ * See {@link TruffleTool} for the life cycle common to all such tools. + *

+ * Execution Counts + *

+ *

    + *
  • "Execution call" on a node is is defined as invocation of a node method that is instrumented + * to produce the event {@link TruffleEventReceiver#enter(Node, VirtualFrame)};
  • + *
  • Execution calls are tabulated only at instrumented nodes, i.e. those for which + * {@linkplain Node#isInstrumentable() isInstrumentable() == true};
  • + *
  • Execution calls are tabulated only at nodes present in the AST when originally created; + * dynamically added nodes will not be instrumented.
  • + *
+ *

+ * Results + *

+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time, + * without effect on the state of the tool. + *

+ *

+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at + * any time in a simple textual format, with no other effect on the state of the tool. + *

+ * + * @see Instrument + * @see SyntaxTag + */ +public final class CoverageTracker extends TruffleTool { + + /** Counting data. */ + private final Map counters = new HashMap<>(); + + /** For disposal. */ + private final List instruments = new ArrayList<>(); + + /** + * Counting is restricted to nodes holding this tag. + */ + private final SyntaxTag countingTag; + + private final ProbeListener probeListener; + + /** + * Create a per-line coverage tool for nodes tagged as {@linkplain StandardSyntaxTag#STATEMENT + * statements} in subsequently created ASTs. + */ + public CoverageTracker() { + this(StandardSyntaxTag.STATEMENT); + } + + /** + * Create a per-line coverage tool for nodes tagged as specified, presuming that tags applied + * outside this tool. + */ + public CoverageTracker(SyntaxTag tag) { + this.probeListener = new CoverageProbeListener(); + this.countingTag = tag; + } + + @Override + protected boolean internalInstall() { + Probe.addProbeListener(probeListener); + return true; + } + + @Override + protected void internalReset() { + counters.clear(); + } + + @Override + protected void internalDispose() { + Probe.removeProbeListener(probeListener); + for (Instrument instrument : instruments) { + instrument.dispose(); + } + } + + /** + * Gets a modification-safe summary of the current per-type node execution counts; does not + * affect the counts. + *

+ * The map holds an array for each source, and elements of the a array corresponding to the + * textual lines of that source. An array entry contains null if the corresponding line of + * source is associated with no nodes of the relevant type, i.e. where no count was made. A + * numeric entry represents the execution count at the corresponding line of source. + *

+ * Note: source line numbers are 1-based, so array index {@code i} corresponds to source + * line number {@code i + 1} + */ + public Map getCounts() { + + /** + * Counters for every {Source, line number} for which a counter was installed, i.e. for + * every line associated with an appropriately tagged AST node; iterable in order of source + * name, then line number. + */ + final TreeSet> entries = new TreeSet<>(new LineLocationEntryComparator()); + + final Map results = new HashMap<>(); + + for (Entry entry : counters.entrySet()) { + entries.add(entry); + } + Source curSource = null; + Long[] curLineTable = null; + for (Entry entry : entries) { + final LineLocation key = entry.getKey(); + final Source source = key.getSource(); + final int lineNo = key.getLineNumber(); + if (source != curSource) { + if (curSource != null) { + results.put(curSource, curLineTable); + } + curSource = source; + curLineTable = new Long[source.getLineCount()]; + } + curLineTable[lineNo - 1] = entry.getValue().count.longValue(); + } + if (curSource != null) { + results.put(curSource, curLineTable); + } + return results; + } + + /** + * A default printer for the current line counts, producing lines of the form " () : ", grouped by source. + */ + public void print(PrintStream out) { + out.println(); + out.println(countingTag.name() + " coverage:"); + + /** + * Counters for every {Source, line number} for which a counter was installed, i.e. for + * every line associated with an appropriately tagged AST node; iterable in order of source + * name, then line number. + */ + final TreeSet> entries = new TreeSet<>(new LineLocationEntryComparator()); + + for (Entry entry : counters.entrySet()) { + entries.add(entry); + } + Source curSource = null; + int curLineNo = 1; + for (Entry entry : entries) { + final LineLocation key = entry.getKey(); + final Source source = key.getSource(); + final int lineNo = key.getLineNumber(); + if (source != curSource) { + if (curSource != null) { + while (curLineNo <= curSource.getLineCount()) { + displayLine(out, null, curSource, curLineNo++); + } + } + curSource = source; + curLineNo = 1; + out.println(); + out.println(source.getPath()); + } + while (curLineNo < lineNo) { + displayLine(out, null, curSource, curLineNo++); + } + displayLine(out, entry.getValue().count, curSource, curLineNo++); + } + if (curSource != null) { + while (curLineNo <= curSource.getLineCount()) { + displayLine(out, null, curSource, curLineNo++); + } + } + } + + private static void displayLine(PrintStream out, AtomicLong value, Source source, int lineNo) { + if (value == null) { + out.format("%14s", " "); + } else { + out.format("(%12d)", value.longValue()); + } + out.format(" %3d: ", lineNo); + out.println(source.getCode(lineNo)); + } + + /** + * A receiver for events at each instrumented AST location. This receiver counts + * "execution calls" to the instrumented node and is stateful. State in receivers must + * be considered carefully since ASTs, along with all instrumentation (including event receivers + * such as this) are routinely cloned by the Truffle runtime. AST cloning is shallow + * (for non- {@link Child} nodes), so in this case the actual count is shared among all + * the clones; the count is also held in a table indexed by source line. + *

+ * In contrast, a primitive field would not be shared among clones and resulting counts + * would not be accurate. + */ + private final class CoverageEventReceiver extends DefaultEventReceiver { + + /** + * Shared by all clones of the associated instrument and by the table of counters for the + * line. + */ + private final AtomicLong count; + + CoverageEventReceiver(AtomicLong count) { + this.count = count; + } + + @Override + @TruffleBoundary + public void enter(Node node, VirtualFrame frame) { + if (isEnabled()) { + count.getAndIncrement(); + } + } + } + + private static final class LineLocationEntryComparator implements Comparator> { + + public int compare(Entry e1, Entry e2) { + return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey()); + } + } + + /** + * Attach a counting instrument to each node that is assigned a specified tag. + */ + private class CoverageProbeListener extends DefaultProbeListener { + + @Override + public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { + if (countingTag == tag) { + + final SourceSection srcSection = probe.getProbedSourceSection(); + if (srcSection == null) { + // TODO (mlvdv) report this? + } else { + final LineLocation lineLocation = srcSection.getLineLocation(); + CoverageCounter counter = counters.get(lineLocation); + if (counter != null) { + // Another node starts on same line; count only the first (textually) + if (srcSection.getCharIndex() > counter.srcSection.getCharIndex()) { + // Counter already in place, corresponds to code earlier on line + return; + } else { + // Counter already in place, corresponds to later code; replace it + counter.instrument.dispose(); + } + } + final AtomicLong count = new AtomicLong(); + final CoverageEventReceiver eventReceiver = new CoverageEventReceiver(count); + final Instrument instrument = Instrument.create(eventReceiver, CoverageTracker.class.getSimpleName()); + instruments.add(instrument); + probe.attach(instrument); + counters.put(lineLocation, new CoverageCounter(srcSection, instrument, count)); + } + } + } + } + + private class CoverageCounter { + final SourceSection srcSection; + final Instrument instrument; + final AtomicLong count; + + CoverageCounter(SourceSection srcSection, Instrument instrument, AtomicLong count) { + this.srcSection = srcSection; + this.instrument = instrument; + this.count = count; + } + } + +} diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/LineToProbesMap.java Tue Jan 27 20:26:41 2015 -0800 @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.tools; + +import java.io.*; +import java.util.*; + +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.instrument.impl.*; +import com.oracle.truffle.api.source.*; + +/** + * A {@link TruffleTool} that builds a map of every {@Probe} attached to some AST, indexed + * by {@link Source} and line number. + */ +public final class LineToProbesMap extends TruffleTool { + + private static final boolean TRACE = false; + private static final PrintStream OUT = System.out; + + private static void trace(String msg) { + OUT.println("LineToProbesMap: " + msg); + } + + /** + * Map: Source line ==> probes associated with source sections starting on the line. + */ + private final Map> lineToProbesMap = new HashMap<>(); + + private final ProbeListener probeListener; + + /** + * Create a map of {@link Probe}s that collects information on all probes added to subsequently + * created ASTs (once installed). + */ + public LineToProbesMap() { + this.probeListener = new LineToProbesListener(); + } + + @Override + protected boolean internalInstall() { + Probe.addProbeListener(probeListener); + return true; + } + + @Override + protected void internalReset() { + lineToProbesMap.clear(); + } + + @Override + protected void internalDispose() { + Probe.removeProbeListener(probeListener); + } + + /** + * Returns the {@link Probe}, if any, associated with a specific line of guest language code; if + * more than one, return the one with the first starting character location. + */ + public Probe findFirstProbe(LineLocation lineLocation) { + Probe probe = null; + final Collection probes = findProbes(lineLocation); + for (Probe probesOnLine : probes) { + if (probe == null) { + probe = probesOnLine; + } else if (probesOnLine.getProbedSourceSection().getCharIndex() < probe.getProbedSourceSection().getCharIndex()) { + probe = probesOnLine; + } + } + return probe; + } + + /** + * Returns all {@link Probe}s whose associated source begins at the given {@link LineLocation}, + * an empty list if none. + */ + public Collection findProbes(LineLocation line) { + final Collection probes = lineToProbesMap.get(line); + if (probes == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableCollection(probes); + } + + private class LineToProbesListener extends DefaultProbeListener { + + @Override + public void newProbeInserted(Probe probe) { + final SourceSection sourceSection = probe.getProbedSourceSection(); + if (sourceSection != null && !(sourceSection instanceof NullSourceSection)) { + final LineLocation lineLocation = sourceSection.getLineLocation(); + if (TRACE) { + trace("ADD " + lineLocation.getShortDescription() + " ==> " + probe.getShortDescription()); + } + Collection probes = lineToProbesMap.get(lineLocation); + if (probes == null) { + probes = new ArrayList<>(2); + lineToProbesMap.put(lineLocation, probes); + } else { + assert !probes.contains(probe); + } + probes.add(probe); + } + } + } +} diff -r 50b22daf6d53 -r ac114ad31cdd graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/NodeExecCounter.java Tue Jan 27 20:26:41 2015 -0800 @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.tools; + +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.*; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.instrument.impl.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.nodes.Node.Child; + +/** + * A {@link TruffleTool} that counts interpreter execution calls to AST nodes, tabulated by + * the type of called nodes; counting can be enabled all nodes or restricted to nodes with + * a specified {@linkplain SyntaxTag tag} that is presumed to be applied external to the tool. + *

+ * Tool Life Cycle + *

+ * See {@link TruffleTool} for the life cycle common to all such tools. + *

+ * Execution Counts + *

+ *

    + *
  • "Execution call" on a node is is defined as invocation of a node method that is instrumented + * to produce the event {@link TruffleEventReceiver#enter(Node, VirtualFrame)};
  • + *
  • Execution calls are tabulated only at instrumented nodes, i.e. those for which + * {@linkplain Node#isInstrumentable() isInstrumentable() == true};
  • + *
  • Execution calls are tabulated only at nodes present in the AST when originally created; + * dynamically added nodes will not be instrumented.
  • + *
+ *

+ * Failure Log + *

+ * For the benefit of language implementors, the tool maintains a log describing failed attempts to + * probe AST nodes. Most failures occur when the type of the wrapper created by + * {@link Node#createWrapperNode()} is not assignable to the relevant {@link Child} field in the + * node's parent. + *

+ *

+ * {@linkplain #reset() Resetting} the counts has no effect on the failure log. + *

+ * Results + *

+ * A modification-safe copy of the {@linkplain #getCounts() counts} can be retrieved at any time, + * without effect on the state of the tool. + *

+ *

+ * A "default" {@linkplain #print(PrintStream) print()} method can summarizes the current counts at + * any time in a simple textual format, without effect on the state of the tool. + *

+ * + * @see Instrument + * @see SyntaxTag + * @see ProbeFailure + */ +public final class NodeExecCounter extends TruffleTool { + + /** + * Execution count for AST nodes of a particular type. + */ + public interface NodeExecutionCount { + Class nodeClass(); + + long executionCount(); + } + + /** + * Receiver for events at instrumented nodes. Counts are maintained in a shared table, so the + * receiver is stateless and can be shared by every {@link Instrument}. + */ + private final TruffleEventReceiver eventReceiver = new DefaultEventReceiver() { + @Override + @TruffleBoundary + public void enter(Node node, VirtualFrame frame) { + if (isEnabled()) { + final Class nodeClass = node.getClass(); + AtomicLong nodeCounter = counters.get(nodeClass); + if (nodeCounter == null) { + nodeCounter = new AtomicLong(); + counters.put(nodeClass, nodeCounter); + } + nodeCounter.getAndIncrement(); + } + } + }; + + /** Counting data. */ + private final Map, AtomicLong> counters = new HashMap<>(); + + /** Failure log. */ + private final List failures = new ArrayList<>(); + + /** For disposal. */ + private final List instruments = new ArrayList<>(); + + /** + * If non-null, counting is restricted to nodes holding this tag. + */ + private final SyntaxTag countingTag; + + /** + * Prober used only when instrumenting every node. + */ + private ASTProber astProber; + + /** + * Listener used only when restricting counting to a specific tag. + */ + private ProbeListener probeListener; + + /** + * Create a per node-type execution counting tool for all nodes in subsequently created ASTs. + */ + public NodeExecCounter() { + this.countingTag = null; + } + + /** + * Creates a per-type execution counting for nodes tagged as specified in subsequently created + * ASTs. + */ + public NodeExecCounter(SyntaxTag tag) { + this.countingTag = tag; + } + + @Override + protected boolean internalInstall() { + if (countingTag == null) { + astProber = new ExecCounterASTProber(); + Probe.registerASTProber(astProber); + } else { + probeListener = new NodeExecCounterProbeListener(); + Probe.addProbeListener(probeListener); + } + return true; + } + + @Override + protected void internalReset() { + counters.clear(); + failures.clear(); + } + + @Override + protected void internalDispose() { + if (astProber != null) { + Probe.unregisterASTProber(astProber); + } + if (probeListener != null) { + Probe.removeProbeListener(probeListener); + } + for (Instrument instrument : instruments) { + instrument.dispose(); + } + } + + /** + * Gets a modification-safe summary of the current per-type node execution counts; does not + * affect the counts. + */ + public NodeExecutionCount[] getCounts() { + final Collection, AtomicLong>> entrySet = counters.entrySet(); + final NodeExecutionCount[] result = new NodeExecCountImpl[entrySet.size()]; + int i = 0; + for (Map.Entry, AtomicLong> entry : entrySet) { + result[i++] = new NodeExecCountImpl(entry.getKey(), entry.getValue().longValue()); + } + return result; + } + + /** + * Gets a log containing a report of every failed attempt to instrument a node. + */ + public ProbeFailure[] getFailures() { + return failures.toArray(new ProbeFailure[failures.size()]); + } + + /** + * A default printer for the current counts, producing lines of the form + * " : " in descending order of count. + */ + public void print(PrintStream out) { + print(out, false); + } + + /** + * A default printer for the current counts, producing lines of the form + * " : " in descending order of count. + * + * @param out + * @param verbose whether to describe nodes on which instrumentation failed + */ + public void print(PrintStream out, boolean verbose) { + + final long missedNodes = failures.size(); + out.println(); + if (countingTag == null) { + out.println("Execution counts by node type:"); + } else { + out.println("\"" + countingTag.name() + "\"-tagged execution counts by node type:"); + } + final StringBuilder disclaim = new StringBuilder("("); + if (missedNodes > 0) { + disclaim.append(Long.toString(missedNodes) + " original AST nodes not instrumented, "); + } + disclaim.append("dynamically added nodes not instrumented)"); + out.println(disclaim.toString()); + NodeExecutionCount[] execCounts = getCounts(); + // Sort in descending order + Arrays.sort(execCounts, new Comparator() { + + public int compare(NodeExecutionCount o1, NodeExecutionCount o2) { + return Long.compare(o2.executionCount(), o1.executionCount()); + } + + }); + for (NodeExecutionCount nodeCount : execCounts) { + out.format("%12d", nodeCount.executionCount()); + out.println(" : " + nodeCount.nodeClass().getName()); + } + + if (verbose && missedNodes > 0) { + out.println("Instrumentation failures for execution counts:"); + + for (ProbeFailure failure : failures) { + out.println("\t" + failure.getMessage()); + } + } + } + + /** + * A prober that attempts to probe and instrument every node. + */ + private class ExecCounterASTProber implements ASTProber, NodeVisitor { + + public boolean visit(Node node) { + + if (node.isInstrumentable()) { + try { + final Instrument instrument = Instrument.create(eventReceiver, "NodeExecCounter"); + instruments.add(instrument); + node.probe().attach(instrument); + } catch (ProbeException ex) { + failures.add(ex.getFailure()); + } + } + return true; + } + + public void probeAST(Node node) { + node.accept(this); + } + } + + /** + * A listener that assumes ASTs have been tagged external to this tool, and which instruments + * nodes holding a specified tag. + */ + private class NodeExecCounterProbeListener extends DefaultProbeListener { + + @Override + public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) { + if (countingTag == tag) { + final Instrument instrument = Instrument.create(eventReceiver, NodeExecCounter.class.getSimpleName()); + instruments.add(instrument); + probe.attach(instrument); + } + } + } + + private static class NodeExecCountImpl implements NodeExecutionCount { + + private final Class nodeClass; + private final long count; + + public NodeExecCountImpl(Class nodeClass, long count) { + this.nodeClass = nodeClass; + this.count = count; + } + + public Class nodeClass() { + return nodeClass; + } + + public long executionCount() { + return count; + } + } +}