Introduce MetricMap as a way of exporting compiler metrics
Any method that has a TreeLogger can export a metric by calling
setAmount(). These metrics are exported by Super Dev Mode inside
each JobEvent.
Started with one metric for DeclaredTypesInModule as proof of
concept.
Also, fixed error-handling bugs in Super Dev Mode:
- JobEvent VALID_TAG regular expression was incorrect.
- If a Job failed with a RuntimeException that happened outside the
compiler, the stack trace was not being logged and the job would
never complete.
Clean up:
- JobEvent was exposing the rebased ImmutableList class
- JobRunner had an unused dependency on OutboxTable
Change-Id: Icb6f7e457c6d9b3b7de341a21b18b2ea0d505177
Review-Link: https://gwt-review.googlesource.com/#/c/9862/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
index aeb7ebb..945296f 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -118,7 +118,7 @@
OutboxTable outboxes = makeOutboxes(options, startupLogger);
JobEventTable eventTable = new JobEventTable();
- JobRunner runner = new JobRunner(eventTable, outboxes);
+ JobRunner runner = new JobRunner(eventTable);
JsonExporter exporter = new JsonExporter(options, outboxes);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
index 2b883d3..8272d35 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
@@ -20,8 +20,10 @@
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.codeserver.JobEvent.CompileStrategy;
import com.google.gwt.dev.codeserver.JobEvent.Status;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap;
import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures;
import com.google.gwt.thirdparty.guava.common.util.concurrent.ListenableFuture;
@@ -291,9 +293,18 @@
out.setCompileStrategy(compileStrategy);
out.setArguments(args);
out.setTags(tags);
+ out.setMetricMap(getMetricMapSnapshot());
return out.build();
}
+ private Map<String, Long> getMetricMapSnapshot() {
+ TreeLogger logger = getLogger();
+ if (logger instanceof AbstractTreeLogger) {
+ return ((AbstractTreeLogger)logger).getMetricMap().getSnapshot();
+ }
+ return ImmutableMap.of(); // not found
+ }
+
/**
* Makes an event visible externally.
*/
@@ -331,6 +342,9 @@
synchronized TreeLogger get() {
if (child == null) {
child = parent.branch(Type.INFO, "Job " + jobId);
+ if (child instanceof AbstractTreeLogger) {
+ ((AbstractTreeLogger)child).resetMetricMap();
+ }
}
return child;
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java
index bae068a..56f6e01 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java
@@ -17,6 +17,7 @@
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefSchema;
+import com.google.gwt.dev.util.log.MetricName;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
@@ -33,7 +34,7 @@
* <p>JobEvent objects are deeply immutable, though they describe a Job that changes.
*/
public final class JobEvent {
- private static final Pattern VALID_TAG = Pattern.compile("\\S{1,100}");
+ private static final Pattern VALID_TAG = Pattern.compile("^\\S{1,100}$");
private final String jobId;
@@ -47,6 +48,7 @@
private final CompileStrategy compileStrategy;
private final ImmutableList<String> arguments;
private final ImmutableList<String> tags;
+ private final ImmutableSortedMap<String, Long> metricMap;
private JobEvent(Builder builder) {
this.jobId = Preconditions.checkNotNull(builder.jobId);
@@ -55,12 +57,14 @@
this.status = Preconditions.checkNotNull(builder.status);
this.message = builder.message == null ? status.defaultMessage : builder.message;
+ this.arguments = ImmutableList.copyOf(builder.args);
+ this.tags = ImmutableList.copyOf(builder.tags);
+ this.metricMap = ImmutableSortedMap.copyOf(builder.metricMap);
+
// The following fields may be null.
this.outputModuleName = builder.outputModuleName;
this.compileDir = builder.compileDir;
this.compileStrategy = builder.compileStrategy;
- this.arguments = ImmutableList.copyOf(builder.args);
- this.tags = ImmutableList.copyOf(builder.tags);
// Any new fields added should allow nulls for backward compatibility.
}
@@ -131,14 +135,23 @@
/**
* The arguments passed to Super Dev Mode at startup, or null if not available.
*/
- public ImmutableList<String> getArguments() {
+ public List<String> getArguments() {
return arguments;
}
/**
* User-defined tags associated with this job. (Not null but may be empty.)
*/
- public ImmutableList<String> getTags() { return tags; }
+ public List<String> getTags() { return tags; }
+
+ /**
+ * Returns the amounts of performance-related metrics. (Not null but may be empty.)
+ * The keys are subject to change.
+ */
+ public SortedMap<String, Long> getMetricMap() {
+ // can't return ImmutableSortedMap because it's repackaged
+ return metricMap;
+ }
/**
* If all the given tags are valid, returns a list containing the tags.
@@ -216,11 +229,14 @@
private Map<String, String> bindings = ImmutableMap.of();
private Status status;
private String message;
- private CompileDir compileDir;
- private CompileStrategy compileStrategy;
+
private List<String> args = ImmutableList.of();
private List<String> tags = ImmutableList.of();
+ private Map<String, Long> metricMap = ImmutableMap.of();
+
private String outputModuleName;
+ private CompileDir compileDir;
+ private CompileStrategy compileStrategy;
/**
* A unique id for this job. Required.
@@ -311,6 +327,21 @@
this.tags = checkTags(tags);
}
+ /**
+ * Sets a map containing metrics used for understanding compiler performance.
+ * Optional but may not be null. If not set, defaults to the empty map.
+ * Each key must be a valid identifier beginning with a capital letter.
+ * (This constraint may be relaxed later.)
+ */
+ public void setMetricMap(Map<String, Long> nameToMetric) {
+ for (String key : nameToMetric.keySet()) {
+ // TODO: allow '.' in the key after making an API for that.
+ Preconditions.checkArgument(MetricName.isValidKey(key),
+ "invalid counter key: %s", key);
+ }
+ this.metricMap = nameToMetric;
+ }
+
public JobEvent build() {
return new JobEvent(this);
}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
index 404d16c..d07f360 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
@@ -33,12 +33,10 @@
*/
public class JobRunner {
private final JobEventTable table;
- private final OutboxTable outboxes;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
- JobRunner(JobEventTable table, OutboxTable outboxes) {
+ JobRunner(JobEventTable table) {
this.table = table;
- this.outboxes = outboxes;
}
/**
@@ -70,13 +68,29 @@
executor.submit(new Runnable() {
@Override
public void run() {
- recompile(job, outboxes);
+ try {
+ recompile(job);
+ } catch (Throwable t) {
+ // Try to release the job so the HTTP request will return an error.
+ // (But this might not work if the same exception is thrown while
+ // sending the finished event.)
+ if (!job.isDone()) {
+ try {
+ job.onFinished(new Job.Result(null, null, t));
+ return;
+ } catch (Throwable t2) {
+ // fall through and log original exception
+ }
+ }
+ // Assume everything is broken. Last-ditch attempt to report the error.
+ t.printStackTrace();
+ }
}
});
job.getLogger().log(Type.TRACE, "added job to queue");
}
- private static void recompile(Job job, OutboxTable outboxes) {
+ private static void recompile(Job job) {
job.getLogger().log(Type.INFO, "starting job: " + job.getId());
job.getOutbox().recompile(job);
}
diff --git a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
index 9cc7656..023551b 100644
--- a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
+++ b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
@@ -119,7 +119,7 @@
Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
OutboxTable outboxes = new OutboxTable();
outboxes.addOutbox(outbox);
- JobRunner runner = new JobRunner(new JobEventTable(), outboxes);
+ JobRunner runner = new JobRunner(new JobEventTable());
// Perform a first compile. This should pass since all resources are valid.
Result result =
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index f68ca3e..6295000 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -83,6 +83,7 @@
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.dev.util.log.MetricName;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
@@ -862,9 +863,13 @@
mainLoop();
if (incrementalCompile) {
+ int declaredTypesInModule = program.getModuleDeclaredTypes().size();
+
+ MetricName.DECLARED_TYPES_IN_MODULE.setAmount(logger, declaredTypesInModule);
+
logger.log(TreeLogger.INFO, "Unification traversed " + liveFieldsAndMethods.size()
+ " fields and methods and " + program.getDeclaredTypes().size() + " types. "
- + program.getModuleDeclaredTypes().size()
+ + declaredTypesInModule
+ " are considered part of the current module and " + fullFlowTypes.size()
+ " had all of their fields and methods traversed.");
diff --git a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
index 5ea39c0..edc86b8c 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
@@ -23,7 +23,7 @@
/**
* Abstract base class for TreeLoggers.
*/
-public abstract class AbstractTreeLogger extends TreeLogger {
+public abstract class AbstractTreeLogger extends TreeLogger implements CanUpdateMetrics {
private static class UncommittedBranchData {
@@ -101,6 +101,12 @@
private UncommittedBranchData uncommitted;
/**
+ * Descendant tree loggers share the same MetricMap by default,
+ * but this can be overridden with {@link #resetMetricMap()}.
+ */
+ private volatile MetricMap metricMap = new MetricMap();
+
+ /**
* The constructor used when creating a top-level logger.
*/
protected AbstractTreeLogger() {
@@ -146,6 +152,9 @@
childLogger.uncommitted = new UncommittedBranchData(type, msg, caught,
helpInfo);
+ // Inherit the parent's compiler counters by default.
+ childLogger.metricMap = metricMap;
+
// This logic is intertwined with log(). If a log message is associated
// with a special error condition, then we turn it into a branch,
// so this method can be called directly from log(). It is of course
@@ -170,10 +179,26 @@
return childLogger;
}
+ /**
+ * Clears this logger's MetricMap. If the map was shared with the
+ * parent logger, the parent's map will be unchanged and the new map will
+ * be independent of its parent.
+ */
+ public void resetMetricMap() {
+ metricMap = new MetricMap();
+ }
+
public final int getBranchedIndex() {
return indexWithinMyParent;
}
+ /**
+ * Returns the MetricMap currently associated with this TreeLogger.
+ */
+ public MetricMap getMetricMap() {
+ return metricMap;
+ }
+
public final synchronized TreeLogger.Type getMaxDetail() {
return logLevel;
}
@@ -314,6 +339,10 @@
protected abstract void doLog(int indexOfLogEntryWithinParentLogger,
TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo);
+ public void setAmount(MetricName name, long amount) {
+ metricMap.setAmount(name, amount);
+ }
+
/**
* Scans <code>t</code> and its causes for {@link OutOfMemoryError} or
* {@link StackOverflowError}.
diff --git a/dev/core/src/com/google/gwt/dev/util/log/CanUpdateMetrics.java b/dev/core/src/com/google/gwt/dev/util/log/CanUpdateMetrics.java
new file mode 100644
index 0000000..e1205b2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/log/CanUpdateMetrics.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.log;
+
+/**
+ * Something that accepts updates to metrics.
+ */
+interface CanUpdateMetrics {
+
+ /**
+ * A metric's value should normally be set using {@link MetricName#setAmount}.
+ */
+ void setAmount(MetricName name, long amount);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java
index 8f06b23..e12b343 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java
@@ -21,7 +21,7 @@
* Forks logging over two child loggers. This provides the graphics + file
* logging of DevModeBase's -logfile option.
*/
-public class CompositeTreeLogger extends TreeLogger {
+public class CompositeTreeLogger extends TreeLogger implements CanUpdateMetrics {
private TreeLogger[] loggers;
@@ -55,4 +55,11 @@
logger.log(type, msg, caught, helpInfo);
}
}
+
+ @Override
+ public void setAmount(MetricName name, long amount) {
+ for (TreeLogger logger : loggers) {
+ name.setAmount(logger, amount);
+ }
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/MetricMap.java b/dev/core/src/com/google/gwt/dev/util/log/MetricMap.java
new file mode 100644
index 0000000..a5c4118
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/log/MetricMap.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.log;
+
+import com.google.gwt.dev.util.collect.HashMap;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap;
+
+import java.util.Map;
+
+/**
+ * Holds mappings from metrics to their values. Thread-safe.
+ */
+public class MetricMap implements CanUpdateMetrics {
+ private final Map<String, Long> map = new HashMap<String, Long>();
+
+ /**
+ * Each MetricMap lives in one or more TreeLoggers.
+ * See {@link AbstractTreeLogger#resetMetricMap()}.
+ */
+ MetricMap() {
+ }
+
+ public synchronized void setAmount(MetricName name, long amount) {
+ map.put(name.key, amount);
+ }
+
+ public synchronized ImmutableSortedMap<String, Long> getSnapshot() {
+ return ImmutableSortedMap.copyOf(map);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/MetricName.java b/dev/core/src/com/google/gwt/dev/util/log/MetricName.java
new file mode 100644
index 0000000..0808afe
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/log/MetricName.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.log;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+
+import java.util.regex.Pattern;
+
+/**
+ * Defines a name for each performance-related metric.
+ */
+public enum MetricName {
+
+ /**
+ * The number of types that UnifyAst considers to be part of the module being compiled.
+ */
+ DECLARED_TYPES_IN_MODULE("DeclaredTypesInModule");
+
+ // Note: this constraint is used in JobEvent which is a public API (for Super Dev Mode).
+ // Allowing more characters in keys may break data collection.
+ private static final Pattern VALID_KEY = Pattern.compile("^[A-Z][A-Za-z0-9]*$");
+
+ static {
+ // Cannot check this in the enum constructor.
+ for (MetricName counter : values()) {
+ Preconditions.checkState(isValidKey(counter.key), "invalid key: %s", counter.key);
+ }
+ }
+
+ final String key;
+
+ /**
+ * @param key the string key to appear in output. Must be an identifier beginning
+ * with a capital letter and not containing underscores.
+ */
+ MetricName(String key) {
+ this.key = key;
+ }
+
+ /**
+ * Adds the given amount to the counter.
+ * @param logger the destination where the count will be logged.
+ */
+ public void setAmount(TreeLogger logger, long amount) {
+ Preconditions.checkNotNull(logger);
+ Preconditions.checkArgument(amount >= 0, "attempted to set a negative amount");
+ if (logger instanceof CanUpdateMetrics) {
+ ((CanUpdateMetrics) logger).setAmount(this, amount);
+ } else {
+ // Just log it.
+ logger.log(Type.DEBUG, "Metric: " + key + " = " + amount);
+ }
+ }
+
+ /**
+ * Returns true if the given name can be used as a key for a compiler counter.
+ */
+ public static boolean isValidKey(String name) {
+ return VALID_KEY.matcher(name).matches();
+ }
+}