Skip to content

Commit 5665cf9

Browse files
author
Esben Sparre Andreasen
committed
JS: record metrics during extraction
1 parent 7fcde4c commit 5665cf9

File tree

13 files changed

+283
-21
lines changed

13 files changed

+283
-21
lines changed

javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import com.semmle.js.ast.jsx.JSXNamespacedName;
9999
import com.semmle.js.ast.jsx.JSXOpeningElement;
100100
import com.semmle.js.ast.jsx.JSXSpreadAttribute;
101+
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
101102
import com.semmle.js.extractor.ExtractorConfig.Platform;
102103
import com.semmle.js.extractor.ExtractorConfig.SourceType;
103104
import com.semmle.js.extractor.ScopeManager.DeclKind;
@@ -192,6 +193,10 @@ public ScopeManager getScopeManager() {
192193
return scopeManager;
193194
}
194195

196+
public ExtractionMetrics getMetrics() {
197+
return lexicalExtractor.getMetrics();
198+
}
199+
195200
/**
196201
* The binding semantics for an identifier.
197202
*
@@ -1945,9 +1950,11 @@ public Label visit(XMLDotDotExpression nd, Context c) {
19451950
}
19461951

19471952
public void extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) {
1953+
lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract);
19481954
trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind);
19491955
locationManager.emitNodeLocation(root, toplevelLabel);
19501956

19511957
root.accept(new V(platform, sourceType), null);
1958+
lexicalExtractor.getMetrics().stopPhase(ExtractionPhase.ASTExtractor_extract);
19521959
}
19531960
}

javascript/extractor/src/com/semmle/js/extractor/CFGExtractor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import com.semmle.js.ast.jsx.JSXNamespacedName;
9494
import com.semmle.js.ast.jsx.JSXOpeningElement;
9595
import com.semmle.js.ast.jsx.JSXSpreadAttribute;
96+
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
9697
import com.semmle.ts.ast.DecoratorList;
9798
import com.semmle.ts.ast.EnumDeclaration;
9899
import com.semmle.ts.ast.EnumMember;
@@ -171,11 +172,13 @@ public class CFGExtractor {
171172
private final TrapWriter trapwriter;
172173
private final Label toplevelLabel;
173174
private final LocationManager locationManager;
175+
private final ExtractionMetrics metrics;
174176

175177
public CFGExtractor(ASTExtractor astExtractor) {
176178
this.trapwriter = astExtractor.getTrapwriter();
177179
this.toplevelLabel = astExtractor.getToplevelLabel();
178180
this.locationManager = astExtractor.getLocationManager();
181+
this.metrics = astExtractor.getMetrics();
179182
}
180183

181184
@SuppressWarnings("unchecked")
@@ -1955,6 +1958,8 @@ public Void visit(XMLDotDotExpression nd, SuccessorInfo c) {
19551958
}
19561959

19571960
public void extract(Node nd) {
1961+
metrics.startPhase(ExtractionPhase.CFGExtractor_extract);
19581962
nd.accept(new V(), new SimpleSuccessorInfo(null));
1963+
metrics.stopPhase(ExtractionPhase.CFGExtractor_extract);
19591964
}
19601965
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.semmle.js.extractor;
2+
3+
import com.semmle.util.exception.Exceptions;
4+
import com.semmle.util.files.FileUtil;
5+
import com.semmle.util.trap.TrapWriter;
6+
import com.semmle.util.trap.TrapWriter.Label;
7+
import com.semmle.util.trap.pathtransformers.PathTransformer;
8+
import java.io.BufferedWriter;
9+
import java.io.File;
10+
import java.io.FileOutputStream;
11+
import java.io.OutputStreamWriter;
12+
import java.lang.management.ManagementFactory;
13+
import java.lang.management.ThreadMXBean;
14+
import java.nio.charset.Charset;
15+
import java.util.Stack;
16+
import java.util.zip.GZIPOutputStream;
17+
18+
/** Metrics for the (single-threaded) extraction of a single file. */
19+
public class ExtractionMetrics {
20+
/**
21+
* The phase of the extraction that should be measured time for.
22+
*
23+
* <p>Convention: the enum names have the format <code>{ClassName}_{MethodName}</code>, and should
24+
* identify the methods they correspond to.
25+
*/
26+
public enum ExtractionPhase {
27+
ASTExtractor_extract(0),
28+
CFGExtractor_extract(1),
29+
FileExtractor_extractContents(2),
30+
JSExtractor_extract(3),
31+
JSParser_parse(4),
32+
LexicalExtractor_extractLines(5),
33+
LexicalExtractor_extractTokens(6),
34+
TypeScriptASTConverter_convertAST(7),
35+
TypeScriptParser_talkToParserWrapper(8);
36+
37+
/** The id used in the database for the time spent performing this phase of the extraction. */
38+
final int dbschemeId;
39+
40+
ExtractionPhase(int dbschemeId) {
41+
this.dbschemeId = dbschemeId;
42+
}
43+
}
44+
45+
/** The cache file, if any. */
46+
private File cacheFile;
47+
48+
/** True iff the extraction of this file reuses an existing trap cache file. */
49+
private boolean canReuseCacheFile;
50+
51+
/** The cumulative CPU-time spent in each extraction phase so far. */
52+
private final long[] cpuTimes = new long[ExtractionPhase.values().length];
53+
54+
/** The label for the file that is being extracted. */
55+
private Label fileLabel;
56+
57+
/** The number of characters in the file that is being extracted. */
58+
private int length;
59+
60+
/** The previous time a CPU-time measure was performed. */
61+
private long previousCpuTime;
62+
63+
/** The previous time a wallclock-time measure was performed. */
64+
private long previousWallclockTime;
65+
66+
/** The extraction phase stack. */
67+
private final Stack<ExtractionPhase> stack = new Stack<>();
68+
69+
/** The current thread, used for measuring CPU-time. */
70+
private final ThreadMXBean thread = ManagementFactory.getThreadMXBean();
71+
72+
/** The cumulative wallclock-time spent in each extraction phase so far. */
73+
private final long[] wallclockTimes = new long[ExtractionPhase.values().length];
74+
75+
/**
76+
* True iff extraction metrics could not be obtained for this file (due to an unforeseen error
77+
* that should not prevent the ordinary extraction from succeeding).
78+
*/
79+
private boolean timingsFailed;
80+
81+
/**
82+
* Appends these metrics to a trap file. Note that this makes the resulting trap file content
83+
* non-deterministic.
84+
*/
85+
public void appendToTrapFile(File trapFileToAppendTo) {
86+
if (trapFileToAppendTo == null) {
87+
return;
88+
}
89+
90+
BufferedWriter out = null;
91+
FileOutputStream fos = null;
92+
GZIPOutputStream gzip = null;
93+
TrapWriter trapwriter = null;
94+
try {
95+
fos = new FileOutputStream(trapFileToAppendTo, true);
96+
gzip = new GZIPOutputStream(fos);
97+
out = new BufferedWriter(new OutputStreamWriter(gzip, Charset.forName("UTF-8")));
98+
99+
trapwriter = new TrapWriter(out, PathTransformer.std());
100+
trapwriter.addTuple(
101+
"extraction_data",
102+
fileLabel,
103+
cacheFile != null ? cacheFile.getAbsolutePath() : "",
104+
canReuseCacheFile,
105+
length);
106+
107+
if (!stack.isEmpty()) {
108+
failTimings(
109+
String.format(
110+
"Could not properly record extraction times for %s. (stack = %s)%n",
111+
fileLabel, stack.toString()));
112+
}
113+
if (!timingsFailed) {
114+
for (int i = 0; i < ExtractionPhase.values().length; i++) {
115+
trapwriter.addTuple("extraction_time", fileLabel, i, 0, (float) cpuTimes[i]);
116+
trapwriter.addTuple("extraction_time", fileLabel, i, 1, (float) wallclockTimes[i]);
117+
}
118+
}
119+
FileUtil.close(trapwriter);
120+
} catch (Exception e) {
121+
FileUtil.close(fos);
122+
FileUtil.close(gzip);
123+
FileUtil.close(out);
124+
FileUtil.close(trapwriter);
125+
Exceptions.ignore(e, "Ignoring exception for extraction metrics writing");
126+
}
127+
}
128+
129+
private void failTimings(String msg) {
130+
System.err.printf(msg);
131+
System.err.flush();
132+
this.timingsFailed = true;
133+
}
134+
135+
private void incrementCurrentTimer() {
136+
long nowWallclock = System.nanoTime();
137+
long nowCpu = thread.getCurrentThreadCpuTime();
138+
139+
if (!stack.isEmpty()) {
140+
// increment by the time elapsed
141+
wallclockTimes[stack.peek().dbschemeId] += nowWallclock - previousWallclockTime;
142+
cpuTimes[stack.peek().dbschemeId] += nowCpu - previousCpuTime;
143+
}
144+
145+
// update the running clock
146+
previousWallclockTime = nowWallclock;
147+
previousCpuTime = nowCpu;
148+
}
149+
150+
public void setCacheFile(File cacheFile) {
151+
this.cacheFile = cacheFile;
152+
}
153+
154+
public void setCanReuseCacheFile(boolean canReuseCacheFile) {
155+
this.canReuseCacheFile = canReuseCacheFile;
156+
}
157+
158+
public void setFileLabel(Label fileLabel) {
159+
this.fileLabel = fileLabel;
160+
}
161+
162+
public void setLength(int length) {
163+
this.length = length;
164+
}
165+
166+
public void startPhase(ExtractionPhase event) {
167+
incrementCurrentTimer();
168+
stack.push(event);
169+
}
170+
171+
public void stopPhase(
172+
ExtractionPhase
173+
event /* technically not needed, but useful for documentation and sanity checking */) {
174+
if (stack.isEmpty()) {
175+
failTimings(
176+
String.format(
177+
"Inconsistent extraction time recording: trying to stop timer %s, but no timer is running",
178+
event));
179+
return;
180+
}
181+
if (stack.peek() != event) {
182+
failTimings(
183+
String.format(
184+
"Inconsistent extraction time recording: trying to stop timer %s, but current timer is: %s",
185+
event, stack.peek()));
186+
return;
187+
}
188+
incrementCurrentTimer();
189+
stack.pop();
190+
}
191+
}

javascript/extractor/src/com/semmle/js/extractor/FileExtractor.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.semmle.js.extractor;
22

3+
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
34
import com.semmle.js.extractor.trapcache.CachingTrapWriter;
45
import com.semmle.js.extractor.trapcache.ITrapCache;
56
import com.semmle.util.data.StringUtil;
@@ -386,13 +387,15 @@ public boolean supports(File f) {
386387

387388
/** @return the number of lines of code extracted, or {@code null} if the file was cached */
388389
public Integer extract(File f, ExtractorState state) throws IOException {
390+
389391
// populate source archive
390392
String source = new WholeIO(config.getDefaultEncoding()).strictread(f);
391393
outputConfig.getSourceArchive().add(f, source);
392394

393395
// extract language-independent bits
394396
TrapWriter trapwriter = outputConfig.getTrapWriterFactory().mkTrapWriter(f);
395397
Label fileLabel = trapwriter.populateFile(f);
398+
396399
LocationManager locationManager = new LocationManager(f, trapwriter, fileLabel);
397400
locationManager.emitFileLocation(fileLabel, 0, 0, 0, 0);
398401

@@ -424,23 +427,37 @@ public Integer extract(File f, ExtractorState state) throws IOException {
424427
private Integer extractContents(
425428
File f, Label fileLabel, String source, LocationManager locationManager, ExtractorState state)
426429
throws IOException {
430+
ExtractionMetrics metrics = new ExtractionMetrics();
431+
metrics.startPhase(ExtractionPhase.FileExtractor_extractContents);
432+
metrics.setLength(source.length());
433+
metrics.setFileLabel(fileLabel);
427434
TrapWriter trapwriter = locationManager.getTrapWriter();
428435
FileType fileType = getFileType(f);
429436

430437
File cacheFile = null, // the cache file for this extraction
431438
resultFile = null; // the final result TRAP file for this extraction
432439

433-
// check whether we can perform caching
434-
if (bumpIdCounter(trapwriter) && fileType.isTrapCachingAllowed()) {
440+
if (bumpIdCounter(trapwriter)) {
435441
resultFile = outputConfig.getTrapWriterFactory().getTrapFileFor(f);
436-
if (resultFile != null) cacheFile = trapCache.lookup(source, config, fileType);
437442
}
443+
// check whether we can perform caching
444+
if (resultFile != null && fileType.isTrapCachingAllowed()) {
445+
cacheFile = trapCache.lookup(source, config, fileType);
446+
}
447+
448+
boolean canUseCacheFile = cacheFile != null;
449+
boolean canReuseCacheFile = canUseCacheFile && cacheFile.exists();
438450

439-
if (cacheFile != null) {
451+
metrics.setCacheFile(cacheFile);
452+
metrics.setCanReuseCacheFile(canReuseCacheFile);
453+
454+
if (canUseCacheFile) {
440455
FileUtil.close(trapwriter);
441456

442-
if (cacheFile.exists()) {
457+
if (canReuseCacheFile) {
443458
FileUtil.append(cacheFile, resultFile);
459+
metrics.stopPhase(ExtractionPhase.FileExtractor_extractContents);
460+
metrics.appendToTrapFile(resultFile);
444461
return null;
445462
}
446463

@@ -457,7 +474,8 @@ private Integer extractContents(
457474
try {
458475
IExtractor extractor = fileType.mkExtractor(config, state);
459476
TextualExtractor textualExtractor =
460-
new TextualExtractor(trapwriter, locationManager, source, config.getExtractLines());
477+
new TextualExtractor(
478+
trapwriter, locationManager, source, config.getExtractLines(), metrics);
461479
LoCInfo loc = extractor.extract(textualExtractor);
462480
int numLines = textualExtractor.getNumLines();
463481
int linesOfCode = loc.getLinesOfCode(), linesOfComments = loc.getLinesOfComments();
@@ -468,8 +486,9 @@ private Integer extractContents(
468486
} finally {
469487
if (!successful && trapwriter instanceof CachingTrapWriter)
470488
((CachingTrapWriter) trapwriter).discard();
471-
472489
FileUtil.close(trapwriter);
490+
metrics.stopPhase(ExtractionPhase.FileExtractor_extractContents);
491+
metrics.appendToTrapFile(resultFile);
473492
}
474493
}
475494

javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,12 @@ private LoCInfo extractSnippet(
191191
JSExtractor extractor = new JSExtractor(config);
192192
try {
193193
TextualExtractor tx =
194-
new TextualExtractor(trapwriter, scriptLocationManager, source, config.getExtractLines());
194+
new TextualExtractor(
195+
trapwriter,
196+
scriptLocationManager,
197+
source,
198+
config.getExtractLines(),
199+
textualExtractor.getMetrics());
195200
return extractor.extract(tx, source, toplevelKind, scopeManager).snd();
196201
} catch (ParseError e) {
197202
e.setPosition(scriptLocationManager.translatePosition(e.getPosition()));

0 commit comments

Comments
 (0)