Bug 1392411 Part 2 - Report stacks for websocket construction in net monitor, r=ochameau.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 19 May 2019 12:44:55 -1000
changeset 533287 3e0d5c61f31e3fd31726f5b4769e734a8e933008
parent 533286 541a6a19a385608655f5a8ae6a1b3a11494021ba
child 533288 c4af6879f495591ca82b946a4fc1717e0322847d
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1392411
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1392411 Part 2 - Report stacks for websocket construction in net monitor, r=ochameau.
devtools/server/actors/network-event.js
devtools/server/actors/network-monitor.js
devtools/server/actors/network-monitor/stack-trace-collector.js
--- a/devtools/server/actors/network-event.js
+++ b/devtools/server/actors/network-event.js
@@ -257,28 +257,36 @@ const NetworkEventActor = protocol.Actor
    *         The response packet - stack trace.
    */
   async getStackTrace() {
     let stacktrace = this._stackTrace;
     // If _stackTrace was "true", it means we are in parent process
     // and the stack is available from the content process.
     // Fetch it lazily from here via the message manager.
     if (stacktrace && typeof stacktrace == "boolean") {
+      let id;
+      if (this._cause.type == "websocket") {
+        // Convert to a websocket URL, as in onNetworkEvent.
+        id = this._request.url.replace(/^http/, "ws");
+      } else {
+        id = this._channelId;
+      }
+
       const messageManager = this.netMonitorActor.messageManager;
       stacktrace = await new Promise(resolve => {
         const onMessage = ({ data }) => {
           const { channelId, stack } = data;
-          if (channelId == this._channelId) {
+          if (channelId == id) {
             messageManager.removeMessageListener("debug:request-stack:response",
               onMessage);
             resolve(stack);
           }
         };
         messageManager.addMessageListener("debug:request-stack:response", onMessage);
-        messageManager.sendAsyncMessage("debug:request-stack:request", this._channelId);
+        messageManager.sendAsyncMessage("debug:request-stack:request", id);
       });
       this._stackTrace = stacktrace;
     }
 
     return {
       stacktrace,
     };
   },
--- a/devtools/server/actors/network-monitor.js
+++ b/devtools/server/actors/network-monitor.js
@@ -211,24 +211,35 @@ const NetworkMonitorActor = ActorClassWi
 
     // map channel to actor so we can associate future events with it
     this._netEvents.set(channelId, actor);
     return actor;
   },
 
   // This method is called by NetworkMonitor instance when a new request is fired
   onNetworkEvent(event) {
-    const { channelId } = event;
+    const { channelId, cause, url } = event;
 
     const actor = this.getNetworkEventActor(channelId);
     this._netEvents.set(channelId, actor);
 
-    event.cause.stacktrace = this.stackTraces.has(channelId);
+    // Find the ID which the stack trace collector will use to save this
+    // channel's stack trace.
+    let id;
+    if (cause.type == "websocket") {
+      // Use the URL, but convert from the http URL which this channel uses to
+      // the original websocket URL which triggered this channel's construction.
+      id = url.replace(/^http/, "ws");
+    } else {
+      id = channelId;
+    }
+
+    event.cause.stacktrace = this.stackTraces.has(id);
     if (event.cause.stacktrace) {
-      this.stackTraces.delete(channelId);
+      this.stackTraces.delete(id);
     }
     actor.init(event);
 
     this._networkEventActorsByURL.set(actor._request.url, actor);
 
     this.conn.sendActorEvent(this.parentID, "networkEvent", { eventActor: actor.form() });
     return actor;
   },
--- a/devtools/server/actors/network-monitor/stack-trace-collector.js
+++ b/devtools/server/actors/network-monitor/stack-trace-collector.js
@@ -38,34 +38,46 @@ StackTraceCollector.prototype = {
     Services.obs.removeObserver(this, "network-monitor-alternate-stack");
     ChannelEventSinkFactory.getService().unregisterCollector(this);
     for (const { messageManager } of this.netmonitors) {
       messageManager.removeMessageListener("debug:request-stack:request",
         this.onGetStack);
     }
   },
 
-  _saveStackTrace(channel, stacktrace) {
-    if (this.stacktracesById.has(channel.channelId)) {
+  _saveStackTrace(id, stacktrace) {
+    if (this.stacktracesById.has(id)) {
       // We can get up to two stack traces for the same channel: one each from
       // the two observer topics we are listening to. Use the first stack trace
       // which is specified, and ignore any later one.
       return;
     }
     for (const { messageManager } of this.netmonitors) {
       messageManager.sendAsyncMessage("debug:request-stack-available", {
-        channelId: channel.channelId,
+        channelId: id,
         stacktrace: stacktrace && stacktrace.length > 0,
       });
     }
-    this.stacktracesById.set(channel.channelId, stacktrace);
+    this.stacktracesById.set(id, stacktrace);
   },
 
   observe(subject, topic, data) {
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    let channel, id;
+    try {
+      channel = subject.QueryInterface(Ci.nsIHttpChannel);
+      id = channel.channelId;
+    } catch (e) {
+      // WebSocketChannels do not have IDs, so use the URL. When a WebSocket is
+      // opened in a content process, a channel is created locally but the HTTP
+      // channel for the connection lives entirely in the parent process. When
+      // the server code running in the parent sees that HTTP channel, it will
+      // look for the creation stack using the websocket's URL.
+      channel = subject.QueryInterface(Ci.nsIWebSocketChannel);
+      id = channel.URI.spec;
+    }
 
     if (!matchRequest(channel, this.filters)) {
       return;
     }
 
     const stacktrace = [];
     switch (topic) {
       case "http-on-opening-request": {
@@ -116,33 +128,33 @@ StackTraceCollector.prototype = {
           frame = frame.parent || frame.asyncParent;
         }
         break;
       }
       default:
         throw new Error("Unexpected observe() topic");
     }
 
-    this._saveStackTrace(channel, stacktrace);
+    this._saveStackTrace(id, stacktrace);
   },
 
   // eslint-disable-next-line no-shadow
   onChannelRedirect(oldChannel, newChannel, flags) {
     // We can be called with any nsIChannel, but are interested only in HTTP channels
     try {
       oldChannel.QueryInterface(Ci.nsIHttpChannel);
       newChannel.QueryInterface(Ci.nsIHttpChannel);
     } catch (ex) {
       return;
     }
 
     const oldId = oldChannel.channelId;
     const stacktrace = this.stacktracesById.get(oldId);
     if (stacktrace) {
-      this._saveStackTrace(newChannel, stacktrace);
+      this._saveStackTrace(newChannel.channelId, stacktrace);
     }
   },
 
   getStackTrace(channelId) {
     const trace = this.stacktracesById.get(channelId);
     this.stacktracesById.delete(channelId);
     return trace;
   },