Phase 1 of GWT Dashboard. This includes an interface in gwt-dev for posting to a dashboard (default implementation is a no-op). Added calls to this interface in a couple of places. Created factory class that can provide the no-op implementation or a real implementation (based on system property). Updated unit test for SpeedTracerLogger to include a test that verifies that it is posting events to dashboard as expected. Review at http://gwt-code-reviews.appspot.com/1427807 Review by: tobyr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10159 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java index f81101b..a9a7324 100644 --- a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java +++ b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -19,6 +19,7 @@ import com.google.gwt.core.ext.TreeLogger.HelpInfo; import com.google.gwt.dev.shell.BrowserChannel.SessionHandler.ExceptionOrReturnValue; import com.google.gwt.dev.shell.JsValue.DispatchObject; +import com.google.gwt.dev.util.log.dashboard.DashboardNotifierFactory; import java.io.IOException; import java.io.InputStream; @@ -106,6 +107,8 @@ private static Map<String, byte[]> iconCache = new HashMap<String, byte[]>(); private static final Object cacheLock = new Object(); + + private DevModeSession devModeSession; private final SessionHandlerServer handler; @@ -164,6 +167,13 @@ } /** + * Returns the {@code DevModeSession} representing this browser connection. + */ + public DevModeSession getDevModeSession() { + return devModeSession; + } + + /** * @return the table of Java objects which have been sent to the browser. */ public ServerObjectsTable getJavaObjectsExposedInBrowser() { @@ -507,6 +517,9 @@ Thread.currentThread().setName( "Code server for " + moduleName + " from " + userAgent + " on " + url + " @ " + sessionKey); + + createDevModeSession(); + logger = handler.loadModule(this, moduleName, userAgent, url, tabKey, sessionKey, iconBytes); if (logger == null) { @@ -631,6 +644,17 @@ break; } } + + /** + * Creates the {@code DevModeSession} that represents the current browser + * connection, sets it as the "default" session for the current thread, and + * notifies a GWT Dashboard. + */ + private void createDevModeSession() { + devModeSession = new DevModeSession(moduleName, userAgent); + DevModeSession.setSessionForCurrentThread(devModeSession); + DashboardNotifierFactory.getNotifier().devModeSession(devModeSession); + } /** * Create the requested transport and return the appropriate information so
diff --git a/dev/core/src/com/google/gwt/dev/shell/DevModeSession.java b/dev/core/src/com/google/gwt/dev/shell/DevModeSession.java new file mode 100644 index 0000000..0f32d7a --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/shell/DevModeSession.java
@@ -0,0 +1,68 @@ +/* + * Copyright 2011 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.shell; + +/** + * Represents a session between devmode and a browser plugin. A session is + * essentially a socket connection, established by the plugin when a GWT module + * is started in devmode and used to communicate invocations of Java from + * JavaScript and vice-versa. + */ +public class DevModeSession { + + private static final ThreadLocal<DevModeSession> sessionForCurrentThread = + new ThreadLocal<DevModeSession>(); + + /** + * Gets the devmode session for the current thread. If a thread is not + * associated with a devmode session, via + * <code>setSessionForCurrentThread()</code>, then this will return null. + */ + public static DevModeSession getSessionForCurrentThread() { + return sessionForCurrentThread.get(); + } + + /** + * Sets the devmode session for the current thread. + */ + static void setSessionForCurrentThread(DevModeSession session) { + sessionForCurrentThread.set(session); + } + + private String moduleName; + private String userAgent; + + /** + * Creates a new instance. + * + * @param moduleName the name of the GWT module for this session + * @param userAgent the User agent field provided by the browser for this + * session + */ + DevModeSession(String moduleName, String userAgent) { + this.moduleName = moduleName; + this.userAgent = userAgent; + } + + public String getModuleName() { + return moduleName; + } + + public String getUserAgent() { + return userAgent; + } +}
diff --git a/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java b/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java index c529093..64b6963 100644 --- a/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java +++ b/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java
@@ -101,7 +101,8 @@ @Override public ExceptionOrReturnValue invoke(BrowserChannelServer channel, Value thisVal, int methodDispatchId, Value[] args) { - Event jsToJavaCallEvent = SpeedTracerLogger.start(DevModeEventType.JS_TO_JAVA_CALL); + Event jsToJavaCallEvent = + SpeedTracerLogger.start(channel.getDevModeSession(), DevModeEventType.JS_TO_JAVA_CALL); ServerObjectsTable localObjects = channel.getJavaObjectsExposedInBrowser(); ModuleSpace moduleSpace = moduleMap.get(channel); ModuleHandle moduleHandle = moduleHandleMap.get(channel); @@ -183,7 +184,9 @@ public synchronized TreeLogger loadModule(BrowserChannelServer channel, String moduleName, String userAgent, String url, String tabKey, String sessionKey, byte[] userAgentIcon) { - Event moduleInit = SpeedTracerLogger.start(DevModeEventType.MODULE_INIT, "Module Name", moduleName); + Event moduleInit = + SpeedTracerLogger.start(channel.getDevModeSession(), DevModeEventType.MODULE_INIT, + "Module Name", moduleName); ModuleHandle moduleHandle = host.createModuleLogger(moduleName, userAgent, url, tabKey, sessionKey, channel, userAgentIcon); TreeLogger logger = moduleHandle.getLogger();
diff --git a/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifier.java b/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifier.java new file mode 100644 index 0000000..8b9f128 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifier.java
@@ -0,0 +1,49 @@ +/* + * Copyright 2011 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.dashboard; + +import com.google.gwt.dev.shell.DevModeSession; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; + +/** + * Sends information to a dashboard service. The dashboard service collects + * information from GWT runtime and compiler instrumentation. + */ +public interface DashboardNotifier { + // First: Compiler related entry points + + // TODO(jhumphries) Add interface methods for collecting data from the + // compiler + + // Second: Dev-Mode related entry points + + /** + * Records a top-level event to the dashboard. + */ + void devModeEvent(DevModeSession session, Event event); + + /** + * Records a new module/session. + */ + void devModeSession(DevModeSession session); + + // Third: Test related entry points + + // TODO(jhumphries) Add interface methods for collecting data from automated + // tests + +}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactory.java b/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactory.java new file mode 100644 index 0000000..74b49d3 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactory.java
@@ -0,0 +1,88 @@ +/* + * Copyright 2011 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.dashboard; + +/** + * Gets an instance of {@link DashboardNotifier} for sending data to a GWT + * dashboard. + */ +public class DashboardNotifierFactory { + private static final NoOpDashboardNotifier defaultNotifier = new NoOpDashboardNotifier(); + private static DashboardNotifier theNotifier; + + static { + setNotifier(createNotifier(System.getProperty("gwt.dashboard.notifierClass"))); + } + + /** + * Determines whether notifications to a GWT Dashboard are enabled. Returns + * true if the current notifier is <strong>not</strong> the default "no-op" + * instance. + */ + public static boolean areNotificationsEnabled() { + return theNotifier != defaultNotifier; + } + + /** + * Returns an instance of {@code DashboardNotifier} for sending data to a GWT + * dashboard. + */ + public static DashboardNotifier getNotifier() { + return theNotifier; + } + + /** + * Creates a {@code DashboardNotifier} from a given class name. The object is + * instantiated via reflection. + * + * @return the new notifier instance if the creation was successful or null on + * failure + */ + static DashboardNotifier createNotifier(String className) { + // Create the instance! + DashboardNotifier notifier = null; + if (className != null) { + try { + Class<?> clazz = Class.forName(className); + notifier = (DashboardNotifier) clazz.newInstance(); + } catch (Exception e) { + // print error and skip dashboard notifications... + new Exception("Unexpected failure while trying to load dashboard class: " + className + + ". Notifications to the dashboard will be disabled.", e).printStackTrace(); + return null; + } + } + return notifier; + } + + /** + * Defines the {@code DashboardNotifier} returned by this factory. Exposed for + * unit testing purposes (to support mock notifier objects). If set to null, a + * default notifier (whose methods do nothing) will be returned by subsequent + * calls to {@code getNotifier}, not null. + */ + static void setNotifier(DashboardNotifier notifier) { + theNotifier = notifier == null ? defaultNotifier : notifier; + } + + /** + * Prevents this class from being instantiated. Instead use static method + * {@link #getNotifier()}. + */ + private DashboardNotifierFactory() { + } +}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/dashboard/NoOpDashboardNotifier.java b/dev/core/src/com/google/gwt/dev/util/log/dashboard/NoOpDashboardNotifier.java new file mode 100644 index 0000000..111660f --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/log/dashboard/NoOpDashboardNotifier.java
@@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.dashboard; + +import com.google.gwt.dev.shell.DevModeSession; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; + +/** + * Implements the <code>DashboardNotifier</code> interface but does not actually + * do anything. + */ +public class NoOpDashboardNotifier implements DashboardNotifier { + + @Override + public void devModeEvent(DevModeSession sesion, Event event) { + // do nothing + } + + @Override + public void devModeSession(DevModeSession session) { + // do nothing + } + +}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java b/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java index 7cdb2a4..f4cd953 100644 --- a/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java +++ b/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java
@@ -17,7 +17,9 @@ import com.google.gwt.dev.json.JsonArray; import com.google.gwt.dev.json.JsonObject; +import com.google.gwt.dev.shell.DevModeSession; import com.google.gwt.dev.util.collect.Lists; +import com.google.gwt.dev.util.log.dashboard.DashboardNotifierFactory; import java.io.BufferedWriter; import java.io.FileWriter; @@ -60,13 +62,14 @@ private static final String defaultFormatString = System.getProperty("gwt.speedtracerformat"); - // Use cummulative multi-threaded process cpu time instead of wall time + // Use cumulative multi-threaded process cpu time instead of wall time private static final boolean logProcessCpuTime = getBooleanProperty("gwt.speedtracer.logProcessCpuTime"); - // Use per thread cpu time instead of wall time - private static final boolean logThreadCpuTime = - getBooleanProperty("gwt.speedtracer.logThreadCpuTime"); + // Use per thread cpu time instead of wall time. If logProcessCpuTime is set, + // then this can remain false - we only need one or the other. + private static final boolean logThreadCpuTime = + getBooleanProperty("gwt.speedtracer.logThreadCpuTime"); // Turn on logging summarizing gc time during an event private static final boolean logGcTime = @@ -81,6 +84,15 @@ private static final boolean jsniCallLoggingEnabled = !getBooleanProperty("gwt.speedtracer.disableJsniLogging"); + static { + // verify configuration + if (logProcessCpuTime && logThreadCpuTime) { + throw new RuntimeException("System properties are misconfigured: " + + "Specify one or the other of 'gwt.speedtracer.logProcessCpuTime' " + + "or 'gwt.speedtracer.logThreadCpuTime', not both."); + } + } + /** * Represents a node in a tree of SpeedTracer events. */ @@ -88,38 +100,46 @@ protected final EventType type; List<Event> children; List<String> data; - long durationNanos; - final long startTimeNanos; + DevModeSession devModeSession; + + long elapsedDurationNanos; + long elapsedStartTimeNanos; + + long processCpuDurationNanos; + long processCpuStartTimeNanos; + + long threadCpuDurationNanos; + long threadCpuStartTimeNanos; Event() { if (enabled) { - timeKeeper.resetTimeBase(); - this.startTimeNanos = timeKeeper.normalizedTimeNanos(); + threadCpuTimeKeeper.resetTimeBase(); + recordStartTime(); this.data = Lists.create(); this.children = Lists.create(); } else { - this.startTimeNanos = 0L; + this.processCpuStartTimeNanos = 0L; + this.threadCpuStartTimeNanos = 0L; + this.elapsedStartTimeNanos = 0L; this.data = null; this.children = null; } this.type = null; } - - Event(Event parent, EventType type, String... data) { - this(timeKeeper.normalizedTimeNanos(), parent, type, data); - } - - Event(long startTimeNanos, Event parent, EventType type, String... data) { + + Event(DevModeSession session, Event parent, EventType type, String... data) { + if (parent != null) { parent.children = Lists.add(parent.children, this); } this.type = type; assert (data.length % 2 == 0); + recordStartTime(); this.data = Lists.create(data); this.children = Lists.create(); - this.startTimeNanos = startTimeNanos; + this.devModeSession = session; } - + /** * @param data key/value pairs to add to JSON object. */ @@ -129,7 +149,7 @@ this.data = Lists.addAll(this.data, data); } } - + /** * Signals the end of the current event. */ @@ -137,19 +157,76 @@ endImpl(this, data); } + public DevModeSession getDevModeSession() { + return devModeSession; + } + + /** + * Returns the event duration, in nanoseconds, for the log file. Depending + * on system properties, this will measured in elapsed time, process CPU + * time, or thread CPU time. + */ + public long getDurationNanos() { + return logProcessCpuTime ? processCpuDurationNanos : (logThreadCpuTime + ? threadCpuDurationNanos : elapsedDurationNanos); + } + + public long getElapsedDurationNanos() { + return this.elapsedDurationNanos; + } + + public long getElapsedStartTimeNanos() { + return this.elapsedStartTimeNanos; + } + + /** + * Returns the event start time, normalized in nanoseconds, for the log + * file. Depending on system properties, this will be normalized based on + * elapsed time, process CPU time, or thread CPU time. + */ + public long getStartTimeNanos() { + return logProcessCpuTime ? processCpuStartTimeNanos : (logThreadCpuTime + ? threadCpuStartTimeNanos : elapsedStartTimeNanos); + } + + public EventType getType() { + return type; + } + @Override public String toString() { return type.getName(); } + /** + * Extends the durations of the current event by the durations of the + * specified event. + */ + void extendDuration(Event refEvent) { + elapsedDurationNanos += refEvent.elapsedDurationNanos; + processCpuDurationNanos += refEvent.processCpuDurationNanos; + threadCpuDurationNanos += refEvent.threadCpuDurationNanos; + } + + /** + * Sets the start time of this event to start immediately after the + * specified event ends. + */ + void setStartsAfter(Event refEvent) { + elapsedStartTimeNanos = refEvent.elapsedStartTimeNanos + refEvent.elapsedDurationNanos; + processCpuStartTimeNanos = + refEvent.processCpuStartTimeNanos + refEvent.processCpuDurationNanos; + threadCpuStartTimeNanos = refEvent.threadCpuStartTimeNanos + refEvent.threadCpuDurationNanos; + } + JsonObject toJson() { JsonObject json = JsonObject.create(); json.put("type", -2); json.put("typeName", type.getName()); json.put("color", type.getColor()); - double startMs = convertToMilliseconds(startTimeNanos); + double startMs = convertToMilliseconds(getStartTimeNanos()); json.put("time", startMs); - double durationMs = convertToMilliseconds(durationNanos); + double durationMs = convertToMilliseconds(getDurationNanos()); json.put("duration", durationMs); JsonObject jsonData = JsonObject.create(); @@ -166,6 +243,49 @@ return json; } + + /** + * Records the duration of this event based on the current time and the + * event's recorded start time. + */ + void updateDuration() { + long elapsedEndTimeNanos = elapsedTimeKeeper.normalizedTimeNanos(); + assert (elapsedEndTimeNanos >= elapsedStartTimeNanos); + elapsedDurationNanos = elapsedEndTimeNanos - elapsedStartTimeNanos; + + // don't bother making expensive time keeping method calls unless + // necessary + if (logProcessCpuTime) { + long processCpuEndTimeNanos = processCpuTimeKeeper.normalizedTimeNanos(); + assert (processCpuEndTimeNanos >= processCpuStartTimeNanos); + processCpuDurationNanos = processCpuEndTimeNanos - processCpuStartTimeNanos; + } else if (logThreadCpuTime) { + long threadCpuEndTimeNanos = threadCpuTimeKeeper.normalizedTimeNanos(); + assert (threadCpuEndTimeNanos >= threadCpuStartTimeNanos); + threadCpuDurationNanos = threadCpuEndTimeNanos - threadCpuStartTimeNanos; + } + } + + /** + * Marks the start time for this event. Three different time measurements + * are used: + * <ol> + * <li>Elapsed (wall-clock) time</li> + * <li>Process CPU time</li> + * <li>Thread CPU time</li> + * </ol> + */ + private void recordStartTime() { + elapsedStartTimeNanos = elapsedTimeKeeper.normalizedTimeNanos(); + + // don't bother making expensive time keeping method calls unless + // necessary + if (logProcessCpuTime) { + processCpuStartTimeNanos = processCpuTimeKeeper.normalizedTimeNanos(); + } else if (logThreadCpuTime) { + threadCpuStartTimeNanos = threadCpuTimeKeeper.normalizedTimeNanos(); + } + } } /** @@ -210,21 +330,68 @@ } } - private interface NormalizedTimeKeeper { - long normalizedTimeNanos(); - void resetTimeBase(); - long zeroTimeMillis(); - } + /** + * Provides functionality specific to garbage collection events. + */ + private class GcEvent extends Event { + private Event refEvent; - /* + /** + * Constructs an event that represents garbage collection metrics. + * + * @param refEvent the event during which the garbage collections took place + * @param gcType the garbage collector type + * @param collectionCount the total number of collections for this garbage + * collector type + * @param durationNanos the total elapsed time spent in garbage collection + * during the span of {@code refEvent} + */ + GcEvent(Event refEvent, String gcType, long collectionCount, long durationNanos) { + super(null, null, SpeedTracerEventType.GC, "Collector Type", gcType, + "Cumulative Collection Count", Long.toString(collectionCount)); + + this.refEvent = refEvent; + // GarbageCollectorMXBean can only provide elapsed time, so that's all we + // record + this.elapsedDurationNanos = durationNanos; + } + + /** + * Returns elapsed duration since that is the only duration we can measure + * for garbage collection events. + */ + @Override + public long getDurationNanos() { + return getElapsedDurationNanos(); + } + + /** + * Returns a start time so that this event ends with its {@code refEvent}. + */ + @Override + public long getElapsedStartTimeNanos() { + return refEvent.getElapsedStartTimeNanos() + refEvent.getElapsedDurationNanos() + - getElapsedDurationNanos(); + } + + /** + * Returns a start time so that this event ends with its {@code refEvent}. + */ + @Override + public long getStartTimeNanos() { + return refEvent.getStartTimeNanos() + refEvent.getDurationNanos() - getDurationNanos(); + } + } + + /** * Time keeper which uses wall time. */ - private class DefaultNormalizedTimeKeeper implements NormalizedTimeKeeper { + private class ElapsedNormalizedTimeKeeper { private final long zeroTimeNanos; private final long zeroTimeMillis; - public DefaultNormalizedTimeKeeper() { + public ElapsedNormalizedTimeKeeper() { zeroTimeNanos = System.nanoTime(); zeroTimeMillis = (long) convertToMilliseconds(zeroTimeNanos); } @@ -233,19 +400,16 @@ return System.nanoTime() - zeroTimeNanos; } - public void resetTimeBase() { - } - public long zeroTimeMillis() { return zeroTimeMillis; } } - - /* + + /** * Time keeper which uses process cpu time. This can be greater than wall - * time, since it is cummulative over the multiple threads of a process. + * time, since it is cumulative over the multiple threads of a process. */ - private class ProcessNormalizedTimeKeeper implements NormalizedTimeKeeper { + private class ProcessNormalizedTimeKeeper { private final OperatingSystemMXBean osMXBean; private final Method getProcessCpuTimeMethod; private final long zeroTimeNanos; @@ -256,7 +420,7 @@ osMXBean = ManagementFactory.getOperatingSystemMXBean(); /* * Find this method by reflection, since it's part of the Sun - * implementation for OperatingSystemMXBean, and we can't alwayws assume + * implementation for OperatingSystemMXBean, and we can't always assume * that com.sun.management.OperatingSystemMXBean will be available. */ getProcessCpuTimeMethod = @@ -276,16 +440,13 @@ throw new RuntimeException(ex); } } - - public void resetTimeBase() { - } - + public long zeroTimeMillis() { return zeroTimeMillis; } } - /* + /** * Time keeper which uses per thread cpu time. It is assumed that individual * events logged will be single threaded, and that top-level events will call * {@link #resetTimeBase()} prior to logging time. The resettable time base @@ -295,7 +456,7 @@ * output, although the relation to wall time is actually compressed within * a logged event (thread cpu time does not include wait time, etc.). */ - private class ThreadNormalizedTimeKeeper implements NormalizedTimeKeeper { + private class ThreadNormalizedTimeKeeper { private final ThreadMXBean threadMXBean; private final ThreadLocal<Long> resettableTimeBase = new ThreadLocal<Long>(); @@ -409,7 +570,7 @@ JsonObject toJson() { JsonObject json = JsonObject.create(); json.put("type", 11); - double startMs = convertToMilliseconds(startTimeNanos); + double startMs = convertToMilliseconds(getStartTimeNanos()); json.put("time", startMs); json.put("duration", 0.0); JsonObject jsonData = JsonObject.create(); @@ -461,14 +622,35 @@ /** * Signals that a new event has started. You must end each event for each * corresponding call to {@code start}. You may nest timing calls. - * + * + * <p> + * Has the same effect as calling + * {@link #start(DevModeSession, EventType, String...) + * start(DevModeSession.getSessionForCurrentThread(), type, data)}. + * * @param type the type of event - * @data a set of key-value pairs (each key is followed by its value) that - * contain additional information about the event - * @return an Event object to be closed by the caller + * @param data a set of key-value pairs (each key is followed by its value) + * that contain additional information about the event + * @return an Event object to be ended by the caller */ public static Event start(EventType type, String... data) { - return SpeedTracerLogger.get().startImpl(type, data); + return SpeedTracerLogger.get().startImpl(DevModeSession.getSessionForCurrentThread(), type, + data); + } + + /** + * Signals that a new event has started. You must end each event for each + * corresponding call to {@code start}. You may nest timing calls. + * + * @param session the devmode session with which this event is associated or + * null if no devmode session is active + * @param type the type of event + * @param data a set of key-value pairs (each key is followed by its value) that + * contain additional information about the event + * @return an Event object to be ended by the caller + */ + public static Event start(DevModeSession session, EventType type, String... data) { + return SpeedTracerLogger.get().startImpl(session, type, data); } private static double convertToMilliseconds(long nanos) { @@ -496,91 +678,81 @@ private final boolean enabled; - private final DummyEvent dummyEvent; + private final DummyEvent dummyEvent = new DummyEvent(); - private final BlockingQueue<Event> eventsToWrite; + private BlockingQueue<Event> eventsToWrite; + private final boolean fileLoggingEnabled; + private CountDownLatch flushLatch; - private final Event flushSentinel; + private Event flushSentinel; - private final Format outputFormat; + private Format outputFormat; - private final ThreadLocal<Stack<Event>> pendingEvents; + private ThreadLocal<Stack<Event>> pendingEvents; - private final CountDownLatch shutDownLatch; + private CountDownLatch shutDownLatch; - private final Event shutDownSentinel; + private Event shutDownSentinel; - private final List<GarbageCollectorMXBean> gcMXBeans; + private List<GarbageCollectorMXBean> gcMXBeans; - private final Map<String, Long> lastGcTimes; + private Map<String, Long> lastGcTimes; - private final NormalizedTimeKeeper timeKeeper; + private final ElapsedNormalizedTimeKeeper elapsedTimeKeeper = new ElapsedNormalizedTimeKeeper(); + + private final ProcessNormalizedTimeKeeper processCpuTimeKeeper = + new ProcessNormalizedTimeKeeper(); + + private final ThreadNormalizedTimeKeeper threadCpuTimeKeeper = new ThreadNormalizedTimeKeeper(); /** * Constructor intended for unit testing. - * + * * @param writer alternative {@link Writer} to send speed tracer output. */ SpeedTracerLogger(Writer writer, Format format) { enabled = true; + fileLoggingEnabled = true; outputFormat = format; eventsToWrite = openLogWriter(writer, ""); pendingEvents = initPendingEvents(); - timeKeeper = initTimeKeeper(); - gcMXBeans = null; - lastGcTimes = null; shutDownSentinel = new DummyEvent(); flushSentinel = new DummyEvent(); shutDownLatch = new CountDownLatch(1); - dummyEvent = null; } private SpeedTracerLogger() { - // Enabled flag (will be true if logFile is non-null) - this.enabled = logFile != null; - + fileLoggingEnabled = logFile != null; + enabled = fileLoggingEnabled || DashboardNotifierFactory.areNotificationsEnabled(); + if (enabled) { - // Allow a system property to override the default output format - Format format = Format.HTML; - if (defaultFormatString != null) { - for (Format value : Format.values()) { - if (value.name().toLowerCase().equals(defaultFormatString.toLowerCase())) { - format = value; - break; + if (fileLoggingEnabled) { + // Allow a system property to override the default output format + Format format = Format.HTML; + if (defaultFormatString != null) { + for (Format value : Format.values()) { + if (value.name().toLowerCase().equals(defaultFormatString.toLowerCase())) { + format = value; + break; + } } } + outputFormat = format; + eventsToWrite = openDefaultLogWriter(); + + shutDownSentinel = new DummyEvent(); + flushSentinel = new DummyEvent(); + shutDownLatch = new CountDownLatch(1); } - - outputFormat = format; - eventsToWrite = openDefaultLogWriter(); - pendingEvents = initPendingEvents(); - timeKeeper = initTimeKeeper(); - + if (logGcTime) { gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); lastGcTimes = new ConcurrentHashMap<String, Long>(); - } else { - gcMXBeans = null; - lastGcTimes = null; } - - shutDownSentinel = new DummyEvent(); - flushSentinel = new DummyEvent(); - shutDownLatch = new CountDownLatch(1); - dummyEvent = null; - } else { - outputFormat = null; - eventsToWrite = null; - pendingEvents = null; - timeKeeper = null; - gcMXBeans = null; - lastGcTimes = null; - shutDownSentinel = null; - flushSentinel = null; - shutDownLatch = null; - dummyEvent = new DummyEvent(); + + pendingEvents = initPendingEvents(); } } @@ -607,7 +779,13 @@ } void addGcEvents(Event refEvent) { - for (java.lang.management.GarbageCollectorMXBean gcMXBean : gcMXBeans) { + // we're not sending GC events to the dashboard, so we only record them + // to file + if (!fileLoggingEnabled) { + return; + } + + for (GarbageCollectorMXBean gcMXBean : gcMXBeans) { String gcName = gcMXBean.getName(); Long lastGcTime = lastGcTimes.get(gcName); long currGcTime = gcMXBean.getCollectionTime(); @@ -617,26 +795,23 @@ if (currGcTime > lastGcTime) { // create a new event long gcDurationNanos = (currGcTime - lastGcTime) * 1000000L; - long gcStartTimeNanos = refEvent.startTimeNanos + refEvent.durationNanos - - gcDurationNanos; - Event gcEvent = new Event(gcStartTimeNanos, null, - SpeedTracerEventType.GC, "Collector Type", gcName, - "Cummulative Collection Count", Long.toString(gcMXBean.getCollectionCount())); - gcEvent.durationNanos = gcDurationNanos; + Event gcEvent = + new GcEvent(refEvent, gcName, gcMXBean.getCollectionCount(), gcDurationNanos); + eventsToWrite.add(gcEvent); - lastGcTimes.put(gcName, currGcTime); } } } void addOverheadEvent(Event refEvent) { - long overheadStartTime = refEvent.startTimeNanos + refEvent.durationNanos; - Event overheadEvent = - new Event(overheadStartTime, refEvent, SpeedTracerEventType.OVERHEAD); - overheadEvent.durationNanos = - timeKeeper.normalizedTimeNanos() - overheadStartTime; - refEvent.durationNanos += overheadEvent.durationNanos; + Event overheadEvent = + new Event(refEvent.devModeSession, refEvent, SpeedTracerEventType.OVERHEAD); + // measure the time between the end of refEvent and now + overheadEvent.setStartsAfter(refEvent); + overheadEvent.updateDuration(); + + refEvent.extendDuration(overheadEvent); } void endImpl(Event event, String... data) { @@ -644,8 +819,6 @@ return; } - long endTimeNanos = timeKeeper.normalizedTimeNanos(); - if (data.length % 2 == 1) { throw new IllegalArgumentException("Unmatched data argument"); } @@ -656,17 +829,14 @@ "Tried to end an event that never started!"); } Event currentEvent = threadPendingEvents.pop(); - - assert (endTimeNanos >= currentEvent.startTimeNanos); - currentEvent.durationNanos = endTimeNanos - currentEvent.startTimeNanos; - + currentEvent.updateDuration(); + while (currentEvent != event && !threadPendingEvents.isEmpty()) { // Missed a closing end for one or more frames! Try to sync back up. currentEvent.addData("Missed", "This event was closed without an explicit call to Event.end()"); currentEvent = threadPendingEvents.pop(); - assert (endTimeNanos >= currentEvent.startTimeNanos); - currentEvent.durationNanos = endTimeNanos - currentEvent.startTimeNanos; + currentEvent.updateDuration(); } if (threadPendingEvents.isEmpty() && currentEvent != event) { @@ -685,7 +855,12 @@ } if (threadPendingEvents.isEmpty()) { - eventsToWrite.add(currentEvent); + if (fileLoggingEnabled) { + eventsToWrite.add(currentEvent); + } + + DashboardNotifierFactory.getNotifier().devModeEvent(currentEvent.getDevModeSession(), + currentEvent); } } @@ -695,6 +870,10 @@ * thread. */ void flush() { + if (!fileLoggingEnabled) { + return; + } + try { // Wait for the other thread to drain the queue. flushLatch = new CountDownLatch(1); @@ -705,7 +884,7 @@ } } - Event startImpl(EventType type, String... data) { + Event startImpl(DevModeSession session, EventType type, String... data) { if (!enabled) { return dummyEvent; } @@ -719,15 +898,19 @@ if (!threadPendingEvents.isEmpty()) { parent = threadPendingEvents.peek(); } else { - // start new time base for top-level events - timeKeeper.resetTimeBase(); + // reset the thread CPU time base for top-level events (so events can be + // properly sequenced chronologically) + threadCpuTimeKeeper.resetTimeBase(); } - Event newEvent = new Event(parent, type, data); + Event newEvent = new Event(session, parent, type, data); // Add a field to the top level event in order to track the base time // so we can re-normalize the data if (threadPendingEvents.size() == 0) { - newEvent.addData("baseTime", "" + timeKeeper.zeroTimeMillis()); + long baseTime = logProcessCpuTime ? processCpuTimeKeeper.zeroTimeMillis() + : (logThreadCpuTime ? threadCpuTimeKeeper.zeroTimeMillis() + : elapsedTimeKeeper.zeroTimeMillis()); + newEvent.addData("baseTime", "" + baseTime); } threadPendingEvents.push(newEvent); return newEvent; @@ -742,16 +925,6 @@ }; } - private NormalizedTimeKeeper initTimeKeeper() { - if (logProcessCpuTime) { - return new ProcessNormalizedTimeKeeper(); - } else if (logThreadCpuTime) { - return new ThreadNormalizedTimeKeeper(); - } else { - return new DefaultNormalizedTimeKeeper(); - } - } - private BlockingQueue<Event> openDefaultLogWriter() { Writer writer = null; if (enabled) {
diff --git a/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java b/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java index eb90b23..2653712 100644 --- a/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java +++ b/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java
@@ -208,6 +208,10 @@ assertNull(handler.getSessionKey()); assertNull(handler.getUserAgentIcon()); assertEquals(MessageType.RETURN, type); + DevModeSession session = server.getDevModeSession(); + assertNotNull(session); + assertEquals("testModule", session.getModuleName()); + assertEquals("userAgent", session.getUserAgent()); ReturnMessage.receive(client); QuitMessage.send(client); server.waitForClose(); @@ -246,6 +250,10 @@ assertEquals("session", handler.getSessionKey()); assertNull(handler.getUserAgentIcon()); assertEquals(MessageType.RETURN, type); + DevModeSession session = server.getDevModeSession(); + assertNotNull(session); + assertEquals("testModule", session.getModuleName()); + assertEquals("userAgent", session.getUserAgent()); ReturnMessage.receive(client); QuitMessage.send(client); server.waitForClose(); @@ -296,6 +304,10 @@ assertEquals(iconBytes[i], receivedIcon[i]); } assertEquals(MessageType.RETURN, type); + DevModeSession session = server.getDevModeSession(); + assertNotNull(session); + assertEquals("testModule", session.getModuleName()); + assertEquals("userAgent", session.getUserAgent()); ReturnMessage.receive(client); QuitMessage.send(client); server.waitForClose();
diff --git a/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTest.java b/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTest.java new file mode 100644 index 0000000..d18f3ce --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTest.java
@@ -0,0 +1,41 @@ +/* + * Copyright 2011 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.shell; + +import junit.framework.TestCase; + +/** + * Tests the DevModeSession class. + */ +public class DevModeSessionTest extends TestCase { + public void testConstructor() { + String moduleName = "test module name"; + String userAgent = "test user agent"; + DevModeSession session = new DevModeSession(moduleName, userAgent); + assertEquals("Constructor failed to initialize moduleName", session.getModuleName(), moduleName); + assertEquals("Constructor failed to initialize userAgent", session.getUserAgent(), userAgent); + } + + public void testSetSessionForCurrentThread() { + DevModeSession session = new DevModeSession("test", "test"); + // call method + DevModeSession.setSessionForCurrentThread(session); + // verify + assertTrue(DevModeSession.getSessionForCurrentThread() == session); + // tear-down + DevModeSession.setSessionForCurrentThread(null); + } +}
diff --git a/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTestUtil.java b/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTestUtil.java new file mode 100644 index 0000000..8654565 --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTestUtil.java
@@ -0,0 +1,20 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.google.gwt.dev.shell; + +/** + * Provides public method for accessing package-private members of + * <code>DevModeSession</code> for testing. + * + * @author jhumphries@google.com (Joshua Humphries) + */ +public class DevModeSessionTestUtil { + public static DevModeSession createSession(String moduleName, String userAgent, + boolean setForCurrentThread) { + DevModeSession session = new DevModeSession(moduleName, userAgent); + if (setForCurrentThread) { + DevModeSession.setSessionForCurrentThread(session); + } + return session; + } +}
diff --git a/dev/core/test/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactoryTest.java b/dev/core/test/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactoryTest.java new file mode 100644 index 0000000..44df0fa --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactoryTest.java
@@ -0,0 +1,70 @@ +/* + * Copyright 2011 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.dashboard; + +import com.google.gwt.dev.shell.DevModeSession; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; + +import junit.framework.TestCase; + +/** + * Tests the DashboardNotifierFactory class. + */ +public class DashboardNotifierFactoryTest extends TestCase { + + public void testSetNotifier() { + // create test notifier instance + DashboardNotifier obj = new DashboardNotifier() { + @Override + public void devModeEvent(DevModeSession session, Event event) { + // no need to do anything + } + + @Override + public void devModeSession(DevModeSession session) { + // no need to do anything + } + }; + // call method + DashboardNotifierFactory.setNotifier(obj); + // verify it worked + assertTrue("Notifier is not set correctly!", DashboardNotifierFactory.getNotifier() == obj); + assertTrue("Setting notifier failed to enable notifications!", DashboardNotifierFactory + .areNotificationsEnabled()); + } + + public void testClearNotifier() { + // clearing the notifier should use a "no-op" instance and disable + // notifications + DashboardNotifierFactory.setNotifier(null); + // verify it worked + assertTrue("Notifier is of wrong type!", + DashboardNotifierFactory.getNotifier() instanceof NoOpDashboardNotifier); + assertFalse("Resetting notifier failed to disable notifications!", DashboardNotifierFactory + .areNotificationsEnabled()); + } + + public void testCreateNotifier() { + DashboardNotifier notifier = + DashboardNotifierFactory.createNotifier(SpeedTracerLoggerTestMockNotifier.class.getName()); + assertNotNull("Notifier could not be created!", notifier); + assertTrue(notifier instanceof SpeedTracerLoggerTestMockNotifier); + } + + public void testCreateNotifierBadClass() { + assertNull(DashboardNotifierFactory.createNotifier("this.is.not.a.valid.Notifier")); + } +}
diff --git a/dev/core/test/com/google/gwt/dev/util/log/dashboard/SpeedTracerLoggerTestMockNotifier.java b/dev/core/test/com/google/gwt/dev/util/log/dashboard/SpeedTracerLoggerTestMockNotifier.java new file mode 100644 index 0000000..889a357 --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/util/log/dashboard/SpeedTracerLoggerTestMockNotifier.java
@@ -0,0 +1,61 @@ +/* + * Copyright 2010 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.dashboard; + +import com.google.gwt.dev.shell.DevModeSession; +import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; + +import junit.framework.Assert; + +import java.util.LinkedList; + +/** + * Mock object for testing SpeedTracerLogger + */ +public class SpeedTracerLoggerTestMockNotifier implements DashboardNotifier { + /** + * Activates this mock object. After calling this, the notifier factory will + * be setup so that dashboard notifications are enabled and the notifier + * instance returned is an instance of this class + */ + public static SpeedTracerLoggerTestMockNotifier enable() { + SpeedTracerLoggerTestMockNotifier ret = new SpeedTracerLoggerTestMockNotifier(); + DashboardNotifierFactory.setNotifier(ret); + return ret; + } + + /** + * This keeps track of events + */ + private LinkedList<Event> eventSeq = new LinkedList<Event>(); + + @Override + public void devModeEvent(DevModeSession session, Event event) { + eventSeq.add(event); + } + + @Override + public void devModeSession(DevModeSession session) { + // always raise exception here - this method shouldn't be invoked from + // SpeedTracerLogger + Assert.assertTrue("SpeedTracerLogger should not be calling DashboardNotifier.devModeSession()", + false); + } + + public LinkedList<Event> getEventSequence() { + return eventSeq; + } +}
diff --git a/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java b/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java index 7e08850..7ed6395 100644 --- a/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java +++ b/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java
@@ -1,5 +1,5 @@ /* - * Copyright 2010 Google Inc. + * Copyright 2011 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 @@ -18,6 +18,9 @@ import com.google.gwt.dev.json.JsonArray; import com.google.gwt.dev.json.JsonException; import com.google.gwt.dev.json.JsonObject; +import com.google.gwt.dev.shell.DevModeSession; +import com.google.gwt.dev.shell.DevModeSessionTestUtil; +import com.google.gwt.dev.util.log.dashboard.SpeedTracerLoggerTestMockNotifier; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.EventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Format; @@ -30,6 +33,8 @@ import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; +import java.util.LinkedList; +import java.util.Properties; /** * Tests the SpeedTracerLogger class. @@ -44,10 +49,12 @@ this.color = color; } + @Override public String getColor() { return color; } + @Override public String getName() { return name; } @@ -66,7 +73,7 @@ @Override public void run() { for (int i = 0; i < MAX_EVENT_LOGS; i++) { - Event e = logger.startImpl(event); + Event e = logger.startImpl(null, event); logger.endImpl(e); } } @@ -83,10 +90,10 @@ public void testSpeedTracerLogger() throws IOException, JsonException { Writer writer = new StringWriter(); SpeedTracerLogger logger = new SpeedTracerLogger(writer, Format.HTML); - Event dummyOneEvent = logger.startImpl(dummyOne); - Event dummyTwoEvent = logger.startImpl(dummyTwo); + Event dummyOneEvent = logger.startImpl(null, dummyOne); + Event dummyTwoEvent = logger.startImpl(null, dummyTwo); logger.endImpl(dummyTwoEvent); - Event dummyThreeEvent = logger.startImpl(dummyThree); + Event dummyThreeEvent = logger.startImpl(null, dummyThree); logger.endImpl(dummyThreeEvent); logger.endImpl(dummyOneEvent); logger.flush(); @@ -113,7 +120,7 @@ JsonException { Writer writer = new StringWriter(); SpeedTracerLogger logger = new SpeedTracerLogger(writer, Format.HTML); - Event dummyOneEvent = logger.startImpl(dummyOne, "extraStart", "valueStart"); + Event dummyOneEvent = logger.startImpl(null, dummyOne, "extraStart", "valueStart"); logger.addDataImpl("extraMiddle", "valueMiddle"); logger.endImpl(dummyOneEvent, "extraEnd", "valueEnd"); logger.flush(); @@ -133,11 +140,11 @@ public void testSpeedTracerLoggerMultiple() throws IOException, JsonException { Writer writer = new StringWriter(); SpeedTracerLogger logger = new SpeedTracerLogger(writer, Format.HTML); - Event dummyOneEvent = logger.startImpl(dummyOne); + Event dummyOneEvent = logger.startImpl(null, dummyOne); logger.endImpl(dummyOneEvent); - Event dummyTwoEvent = logger.startImpl(dummyTwo); + Event dummyTwoEvent = logger.startImpl(null, dummyTwo); logger.endImpl(dummyTwoEvent); - Event dummyThreeEvent = logger.startImpl(dummyThree); + Event dummyThreeEvent = logger.startImpl(null, dummyThree); logger.endImpl(dummyThreeEvent); logger.flush(); @@ -199,7 +206,7 @@ public void testSpeedTracerLoggerMarkTimeline() throws IOException, JsonException { Writer writer = new StringWriter(); SpeedTracerLogger logger = new SpeedTracerLogger(writer, Format.RAW); - Event dummyOneEvent = logger.startImpl(dummyOne); + Event dummyOneEvent = logger.startImpl(null, dummyOne); logger.markTimelineImpl("Test Message"); dummyOneEvent.end(); logger.flush(); @@ -222,7 +229,7 @@ public void testSpeedTracerLoggerRaw() throws IOException, JsonException { Writer writer = new StringWriter(); SpeedTracerLogger logger = new SpeedTracerLogger(writer, Format.RAW); - Event dummyOneEvent = logger.startImpl(dummyOne); + Event dummyOneEvent = logger.startImpl(null, dummyOne); dummyOneEvent.end(); logger.flush(); @@ -261,4 +268,74 @@ } return jsonReader; } + + public void testSpeedTracerWhenOnlyDashboardEnabled() { + // backup system properties before making changes to them + Properties props = (Properties) System.getProperties().clone(); + + try { + // no logging to file! + System.clearProperty("gwt.speedtracerlog"); + // we don't capture GC events in dashboard, so setting this will allow us + // to confirm that they *don't* show up in dashboard notices + System.setProperty("gwt.speedtracer.logGcTime", "yes"); + + // now enable the mock dashboard notifier + SpeedTracerLoggerTestMockNotifier notifier = SpeedTracerLoggerTestMockNotifier.enable(); + + // create "sessions" + DevModeSession session1 = DevModeSessionTestUtil.createSession("test1", "test", true); + DevModeSession session2 = DevModeSessionTestUtil.createSession("test2", "test", false); + + // expected values (used in final assertions below) + LinkedList<Event> expectedEvents = new LinkedList<Event>(); + LinkedList<DevModeSession> expectedSessions = new LinkedList<DevModeSession>(); + + Event evt1, evt2; + + // test events with no session specified + evt1 = SpeedTracerLogger.start(DevModeEventType.MODULE_INIT, "k1", "v1", "k2", "v2"); + // also test that child events aren't posted (only top-level events) + evt2 = SpeedTracerLogger.start(DevModeEventType.CLASS_BYTES_REWRITE); + evt2.end(); + evt1.end(); + // expect only first event + expectedEvents.add(evt1); + expectedSessions.add(session1); // event should get "default" session + + // now with session specified + evt1 = SpeedTracerLogger.start(session2, DevModeEventType.JAVA_TO_JS_CALL, "k1", "v1"); + // also test that child events aren't posted (only top-level events) + evt2 = SpeedTracerLogger.start(DevModeEventType.CREATE_UI); + evt2.end(); + evt1.end(); + // expect only first event + expectedEvents.add(evt1); + expectedSessions.add(session2); + + evt1 = SpeedTracerLogger.start(session1, DevModeEventType.JS_TO_JAVA_CALL, "k1", "v1"); + evt1.end(); + expectedEvents.add(evt1); + expectedSessions.add(session1); + + // Finally, assert that the events and corresponding sessions sent to the + // notifier are exactly as expected + assertEquals("Events posted to dashboard do not match expected events!", expectedEvents, + notifier.getEventSequence()); + + // Collect sessions associated with each event + LinkedList<DevModeSession> actualSessions = new LinkedList<DevModeSession>(); + for (Event event : notifier.getEventSequence()) { + actualSessions.add(event.getDevModeSession()); + } + + // and confirm the sessions are correct + assertEquals("Events posted to dashboard are associated with incorrect sessions!", + expectedSessions, actualSessions); + + } finally { + // restore system properties + System.setProperties(props); + } + } }