Revision: 10159
Author:   [email protected]
Date:     Fri May  6 10:22:03 2011
Log: 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: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=10159

Added:
 /trunk/dev/core/src/com/google/gwt/dev/shell/DevModeSession.java
 /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard
/trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifier.java /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactory.java /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/NoOpDashboardNotifier.java
 /trunk/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTest.java
 /trunk/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTestUtil.java
 /trunk/dev/core/test/com/google/gwt/dev/util/log/dashboard
/trunk/dev/core/test/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactoryTest.java /trunk/dev/core/test/com/google/gwt/dev/util/log/dashboard/SpeedTracerLoggerTestMockNotifier.java
Modified:
 /trunk/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
 /trunk/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java
/trunk/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java
 /trunk/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java
/trunk/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java

=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/shell/DevModeSession.java Fri May 6 10:22:03 2011
@@ -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;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifier.java Fri May 6 10:22:03 2011
@@ -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
+
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactory.java Fri May 6 10:22:03 2011
@@ -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() {
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/util/log/dashboard/NoOpDashboardNotifier.java Fri May 6 10:22:03 2011
@@ -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
+  }
+
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTest.java Fri May 6 10:22:03 2011
@@ -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);
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/test/com/google/gwt/dev/shell/DevModeSessionTestUtil.java Fri May 6 10:22:03 2011
@@ -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 [email protected] (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;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/test/com/google/gwt/dev/util/log/dashboard/DashboardNotifierFactoryTest.java Fri May 6 10:22:03 2011
@@ -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"));
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/test/com/google/gwt/dev/util/log/dashboard/SpeedTracerLoggerTestMockNotifier.java Fri May 6 10:22:03 2011
@@ -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;
+  }
+}
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java Tue Aug 3 17:01:46 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java Fri May 6 10:22:03 2011
@@ -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;

@@ -162,6 +165,13 @@
       throw new HostedModeException("I/O error communicating with client");
     }
   }
+
+  /**
+ * 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.
@@ -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
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java Tue Apr 26 08:02:24 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/OophmSessionHandler.java Fri May 6 10:22:03 2011
@@ -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();
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java Mon Mar 7 09:15:21 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLogger.java Fri May 6 10:22:03 2011
@@ -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,27 +149,84 @@
         this.data = Lists.addAll(this.data, data);
       }
     }
-
+
     /**
      * Signals the end of the current event.
      */
     public void end(String... data) {
       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);
     }
@@ -232,20 +399,17 @@
     public long normalizedTimeNanos() {
       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();
@@ -457,18 +618,39 @@
   public static void markTimeline(String message) {
     SpeedTracerLogger.get().markTimelineImpl(message);
   }
+
+  /**
+   * 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
+ * @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(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
- * @data a set of key-value pairs (each key is followed by its value) that + * @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 closed by the caller
+   * @return an Event object to be ended by the caller
    */
-  public static Event start(EventType type, String... data) {
-    return SpeedTracerLogger.get().startImpl(type, data);
+ 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 BlockingQueue<Event> eventsToWrite;
-
+  private final DummyEvent dummyEvent = new DummyEvent();
+
+  private BlockingQueue<Event> eventsToWrite;
+
+  private final boolean fileLoggingEnabled;
+
   private CountDownLatch flushLatch;

-  private final Event flushSentinel;
-
-  private final Format outputFormat;
-
-  private final ThreadLocal<Stack<Event>> pendingEvents;
-
-  private final CountDownLatch shutDownLatch;
-
-  private final Event shutDownSentinel;
-
-  private final List<GarbageCollectorMXBean> gcMXBeans;
-
-  private final Map<String, Long> lastGcTimes;
-
-  private final NormalizedTimeKeeper timeKeeper;
+  private Event flushSentinel;
+
+  private Format outputFormat;
+
+  private ThreadLocal<Stack<Event>> pendingEvents;
+
+  private CountDownLatch shutDownLatch;
+
+  private Event shutDownSentinel;
+
+  private List<GarbageCollectorMXBean> gcMXBeans;
+
+  private Map<String, Long> lastGcTimes;
+
+ 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();
-      pendingEvents = initPendingEvents();
-      timeKeeper = initTimeKeeper();
-
+        outputFormat = format;
+        eventsToWrite = openDefaultLogWriter();
+
+        shutDownSentinel = new DummyEvent();
+        flushSentinel = new DummyEvent();
+        shutDownLatch = new CountDownLatch(1);
+      }
+
       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;
@@ -741,16 +924,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;
=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java Fri Feb 19 06:35:15 2010 +++ /trunk/dev/core/test/com/google/gwt/dev/shell/BrowserChannelServerTest.java Fri May 6 10:22:03 2011
@@ -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();
=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java Wed Sep 15 10:32:35 2010 +++ /trunk/dev/core/test/com/google/gwt/dev/util/log/speedtracer/SpeedTracerLoggerTest.java Fri May 6 10:22:03 2011
@@ -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);
+    }
+  }
+}

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to