comparison graal/com.oracle.truffle.api/src/com/oracle/truffle/api/tools/CoverageTracker.java @ 19248:a2ff253c458f

Truffle/Instrumentation: code cleanups in tools CoverageTracker and NodeExecCounter, especially for tutorial value
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Tue, 10 Feb 2015 16:44:19 -0800
parents c5b20395a8bf
children b5467bb34b24
comparison
equal deleted inserted replaced
19247:128586040207 19248:a2ff253c458f
27 import java.io.*; 27 import java.io.*;
28 import java.util.*; 28 import java.util.*;
29 import java.util.Map.Entry; 29 import java.util.Map.Entry;
30 import java.util.concurrent.atomic.*; 30 import java.util.concurrent.atomic.*;
31 31
32 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
33 import com.oracle.truffle.api.frame.*; 32 import com.oracle.truffle.api.frame.*;
34 import com.oracle.truffle.api.instrument.*; 33 import com.oracle.truffle.api.instrument.*;
35 import com.oracle.truffle.api.instrument.impl.*; 34 import com.oracle.truffle.api.instrument.impl.*;
36 import com.oracle.truffle.api.nodes.*; 35 import com.oracle.truffle.api.nodes.*;
37 import com.oracle.truffle.api.nodes.Node.Child; 36 import com.oracle.truffle.api.nodes.Node.Child;
50 * <p> 49 * <p>
51 * <b>Execution Counts</b> 50 * <b>Execution Counts</b>
52 * <p> 51 * <p>
53 * <ul> 52 * <ul>
54 * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented 53 * <li>"Execution call" on a node is is defined as invocation of a node method that is instrumented
55 * to produce the event {@link TruffleEventReceiver#enter(Node, VirtualFrame)};</li> 54 * to produce the event {@link TruffleEventListener#enter(Node, VirtualFrame)};</li>
56 * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which 55 * <li>Execution calls are tabulated only at <em>instrumented</em> nodes, i.e. those for which
57 * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li> 56 * {@linkplain Node#isInstrumentable() isInstrumentable() == true};</li>
58 * <li>Execution calls are tabulated only at nodes present in the AST when originally created; 57 * <li>Execution calls are tabulated only at nodes present in the AST when originally created;
59 * dynamically added nodes will not be instrumented.</li> 58 * dynamically added nodes will not be instrumented.</li>
60 * </ul> 59 * </ul>
73 * @see SyntaxTag 72 * @see SyntaxTag
74 */ 73 */
75 public final class CoverageTracker extends InstrumentationTool { 74 public final class CoverageTracker extends InstrumentationTool {
76 75
77 /** Counting data. */ 76 /** Counting data. */
78 private final Map<LineLocation, CoverageCounter> counters = new HashMap<>(); 77 private final Map<LineLocation, CoverageRecord> coverageMap = new HashMap<>();
79 78
80 /** For disposal. */ 79 /** Needed for disposal. */
81 private final List<Instrument> instruments = new ArrayList<>(); 80 private final List<Instrument> instruments = new ArrayList<>();
82 81
83 /** 82 /**
84 * Counting is restricted to nodes holding this tag. 83 * Coverage counting is restricted to nodes holding this tag.
85 */ 84 */
86 private final SyntaxTag countingTag; 85 private final SyntaxTag countingTag;
87 86
88 private final ProbeListener probeListener; 87 private final ProbeListener probeListener;
89 88
110 return true; 109 return true;
111 } 110 }
112 111
113 @Override 112 @Override
114 protected void internalReset() { 113 protected void internalReset() {
115 counters.clear(); 114 coverageMap.clear();
116 } 115 }
117 116
118 @Override 117 @Override
119 protected void internalDispose() { 118 protected void internalDispose() {
120 Probe.removeProbeListener(probeListener); 119 Probe.removeProbeListener(probeListener);
140 /** 139 /**
141 * Counters for every {Source, line number} for which a counter was installed, i.e. for 140 * Counters for every {Source, line number} for which a counter was installed, i.e. for
142 * every line associated with an appropriately tagged AST node; iterable in order of source 141 * every line associated with an appropriately tagged AST node; iterable in order of source
143 * name, then line number. 142 * name, then line number.
144 */ 143 */
145 final TreeSet<Entry<LineLocation, CoverageCounter>> entries = new TreeSet<>(new LineLocationEntryComparator()); 144 final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
146 145
147 final Map<Source, Long[]> results = new HashMap<>(); 146 for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
148
149 for (Entry<LineLocation, CoverageCounter> entry : counters.entrySet()) {
150 entries.add(entry); 147 entries.add(entry);
151 } 148 }
149 final Map<Source, Long[]> result = new HashMap<>();
152 Source curSource = null; 150 Source curSource = null;
153 Long[] curLineTable = null; 151 Long[] curLineTable = null;
154 for (Entry<LineLocation, CoverageCounter> entry : entries) { 152 for (Entry<LineLocation, CoverageRecord> entry : entries) {
155 final LineLocation key = entry.getKey(); 153 final LineLocation key = entry.getKey();
156 final Source source = key.getSource(); 154 final Source source = key.getSource();
157 final int lineNo = key.getLineNumber(); 155 final int lineNo = key.getLineNumber();
158 if (source != curSource) { 156 if (source != curSource) {
159 if (curSource != null) { 157 if (curSource != null) {
160 results.put(curSource, curLineTable); 158 result.put(curSource, curLineTable);
161 } 159 }
162 curSource = source; 160 curSource = source;
163 curLineTable = new Long[source.getLineCount()]; 161 curLineTable = new Long[source.getLineCount()];
164 } 162 }
165 curLineTable[lineNo - 1] = entry.getValue().count.longValue(); 163 curLineTable[lineNo - 1] = entry.getValue().count.longValue();
166 } 164 }
167 if (curSource != null) { 165 if (curSource != null) {
168 results.put(curSource, curLineTable); 166 result.put(curSource, curLineTable);
169 } 167 }
170 return results; 168 return result;
171 } 169 }
172 170
173 /** 171 /**
174 * A default printer for the current line counts, producing lines of the form " (<count>) <line 172 * A default printer for the current line counts, producing lines of the form " (<count>) <line
175 * number> : <text of line>", grouped by source. 173 * number> : <text of line>", grouped by source.
181 /** 179 /**
182 * Counters for every {Source, line number} for which a counter was installed, i.e. for 180 * Counters for every {Source, line number} for which a counter was installed, i.e. for
183 * every line associated with an appropriately tagged AST node; iterable in order of source 181 * every line associated with an appropriately tagged AST node; iterable in order of source
184 * name, then line number. 182 * name, then line number.
185 */ 183 */
186 final TreeSet<Entry<LineLocation, CoverageCounter>> entries = new TreeSet<>(new LineLocationEntryComparator()); 184 final TreeSet<Entry<LineLocation, CoverageRecord>> entries = new TreeSet<>(new LineLocationEntryComparator());
187 185
188 for (Entry<LineLocation, CoverageCounter> entry : counters.entrySet()) { 186 for (Entry<LineLocation, CoverageRecord> entry : coverageMap.entrySet()) {
189 entries.add(entry); 187 entries.add(entry);
190 } 188 }
191 Source curSource = null; 189 Source curSource = null;
192 int curLineNo = 1; 190 int curLineNo = 1;
193 for (Entry<LineLocation, CoverageCounter> entry : entries) { 191 for (Entry<LineLocation, CoverageRecord> entry : entries) {
194 final LineLocation key = entry.getKey(); 192 final LineLocation key = entry.getKey();
195 final Source source = key.getSource(); 193 final Source source = key.getSource();
196 final int lineNo = key.getLineNumber(); 194 final int lineNo = key.getLineNumber();
197 if (source != curSource) { 195 if (source != curSource) {
198 if (curSource != null) { 196 if (curSource != null) {
226 out.format(" %3d: ", lineNo); 224 out.format(" %3d: ", lineNo);
227 out.println(source.getCode(lineNo)); 225 out.println(source.getCode(lineNo));
228 } 226 }
229 227
230 /** 228 /**
231 * A receiver for events at each instrumented AST location. This receiver counts 229 * A listener for events at each instrumented AST location. This listener counts
232 * "execution calls" to the instrumented node and is <em>stateful</em>. State in receivers must 230 * "execution calls" to the instrumented node and is <em>stateful</em>. State in listeners must
233 * be considered carefully since ASTs, along with all instrumentation (including event receivers 231 * be considered carefully since ASTs, along with all instrumentation (including event listener
234 * such as this) are routinely cloned by the Truffle runtime. AST cloning is <em>shallow</em> 232 * such as this) are routinely cloned by the Truffle runtime. AST cloning is <em>shallow</em>
235 * (for non- {@link Child} nodes), so in this case the actual count <em>is shared</em> among all 233 * (for non- {@link Child} nodes), so in this case the actual count <em>is shared</em> among all
236 * the clones; the count is also held in a table indexed by source line. 234 * the clones; the count is also held in a table indexed by source line.
237 * <p> 235 * <p>
238 * In contrast, a primitive field would <em>not</em> be shared among clones and resulting counts 236 * In contrast, a primitive field would <em>not</em> be shared among clones and resulting counts
239 * would not be accurate. 237 * would not be accurate.
240 */ 238 */
241 private final class CoverageEventReceiver extends DefaultEventReceiver { 239 private final class CoverageEventListener extends DefaultEventListener {
242 240
243 /** 241 /**
244 * Shared by all clones of the associated instrument and by the table of counters for the 242 * Shared by all clones of the associated instrument and by the table of counters for the
245 * line. 243 * line.
246 */ 244 */
247 private final AtomicLong count; 245 private final AtomicLong count;
248 246
249 CoverageEventReceiver(AtomicLong count) { 247 CoverageEventListener(AtomicLong count) {
250 this.count = count; 248 this.count = count;
251 } 249 }
252 250
253 @Override 251 @Override
254 @TruffleBoundary
255 public void enter(Node node, VirtualFrame frame) { 252 public void enter(Node node, VirtualFrame frame) {
256 if (isEnabled()) { 253 if (isEnabled()) {
257 count.getAndIncrement(); 254 count.getAndIncrement();
258 } 255 }
259 } 256 }
260 } 257 }
261 258
262 private static final class LineLocationEntryComparator implements Comparator<Entry<LineLocation, CoverageCounter>> { 259 private static final class LineLocationEntryComparator implements Comparator<Entry<LineLocation, CoverageRecord>> {
263 260
264 public int compare(Entry<LineLocation, CoverageCounter> e1, Entry<LineLocation, CoverageCounter> e2) { 261 public int compare(Entry<LineLocation, CoverageRecord> e1, Entry<LineLocation, CoverageRecord> e2) {
265 return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey()); 262 return LineLocation.COMPARATOR.compare(e1.getKey(), e2.getKey());
266 } 263 }
267 } 264 }
268 265
269 /** 266 /**
277 274
278 final SourceSection srcSection = probe.getProbedSourceSection(); 275 final SourceSection srcSection = probe.getProbedSourceSection();
279 if (srcSection == null) { 276 if (srcSection == null) {
280 // TODO (mlvdv) report this? 277 // TODO (mlvdv) report this?
281 } else { 278 } else {
279 // Get the source line where the
282 final LineLocation lineLocation = srcSection.getLineLocation(); 280 final LineLocation lineLocation = srcSection.getLineLocation();
283 CoverageCounter counter = counters.get(lineLocation); 281 CoverageRecord record = coverageMap.get(lineLocation);
284 if (counter != null) { 282 if (record != null) {
285 // Another node starts on same line; count only the first (textually) 283 // Another node starts on same line; count only the first (textually)
286 if (srcSection.getCharIndex() > counter.srcSection.getCharIndex()) { 284 if (srcSection.getCharIndex() > record.srcSection.getCharIndex()) {
287 // Counter already in place, corresponds to code earlier on line 285 // Record already in place, corresponds to code earlier on line
288 return; 286 return;
289 } else { 287 } else {
290 // Counter already in place, corresponds to later code; replace it 288 // Record already in place, corresponds to later code; replace it
291 counter.instrument.dispose(); 289 record.instrument.dispose();
292 } 290 }
293 } 291 }
294 final AtomicLong count = new AtomicLong(); 292 final AtomicLong count = new AtomicLong();
295 final CoverageEventReceiver eventReceiver = new CoverageEventReceiver(count); 293 final CoverageEventListener eventListener = new CoverageEventListener(count);
296 final Instrument instrument = Instrument.create(eventReceiver, CoverageTracker.class.getSimpleName()); 294 final Instrument instrument = Instrument.create(eventListener, CoverageTracker.class.getSimpleName());
297 instruments.add(instrument); 295 instruments.add(instrument);
298 probe.attach(instrument); 296 probe.attach(instrument);
299 counters.put(lineLocation, new CoverageCounter(srcSection, instrument, count)); 297 coverageMap.put(lineLocation, new CoverageRecord(srcSection, instrument, count));
300 } 298 }
301 } 299 }
302 } 300 }
303 } 301 }
304 302
305 private class CoverageCounter { 303 private class CoverageRecord {
306 final SourceSection srcSection; 304
307 final Instrument instrument; 305 final SourceSection srcSection; // The text of the code being counted
306 final Instrument instrument; // The attached Instrument, in case need to remove.
308 final AtomicLong count; 307 final AtomicLong count;
309 308
310 CoverageCounter(SourceSection srcSection, Instrument instrument, AtomicLong count) { 309 CoverageRecord(SourceSection srcSection, Instrument instrument, AtomicLong count) {
311 this.srcSection = srcSection; 310 this.srcSection = srcSection;
312 this.instrument = instrument; 311 this.instrument = instrument;
313 this.count = count; 312 this.count = count;
314 } 313 }
315 } 314 }