From ff7200291af0b916028315ae6ab9f3e88fa665c1 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 10 Jun 2016 13:28:15 -0700 Subject: [PATCH] bug 1265723 - Decouple the MemoryFront from the MemoryActor; r=ejpbruel --- devtools/client/memory/models.js | 2 +- devtools/client/memory/panel.js | 2 +- devtools/server/actors/memory.js | 206 ++---------------- .../server/tests/mochitest/memory-helpers.js | 2 +- devtools/server/tests/unit/head_dbg.js | 2 +- devtools/shared/fronts/memory.js | 88 ++++++++ devtools/shared/fronts/moz.build | 1 + devtools/shared/specs/memory.js | 121 ++++++++++ devtools/shared/specs/moz.build | 1 + 9 files changed, 232 insertions(+), 193 deletions(-) create mode 100644 devtools/shared/fronts/memory.js create mode 100644 devtools/shared/specs/memory.js diff --git a/devtools/client/memory/models.js b/devtools/client/memory/models.js index 5f9279fbdc54..7624f7accc52 100644 --- a/devtools/client/memory/models.js +++ b/devtools/client/memory/models.js @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ const { assert } = require("devtools/shared/DevToolsUtils"); -const { MemoryFront } = require("devtools/server/actors/memory"); +const { MemoryFront } = require("devtools/shared/fronts/memory"); const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient"); const { PropTypes } = require("devtools/client/shared/vendor/react"); const { diff --git a/devtools/client/memory/panel.js b/devtools/client/memory/panel.js index 088ac4084455..cf867faff0b9 100644 --- a/devtools/client/memory/panel.js +++ b/devtools/client/memory/panel.js @@ -7,7 +7,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); const { Task } = require("devtools/shared/task"); const EventEmitter = require("devtools/shared/event-emitter"); -const { MemoryFront } = require("devtools/server/actors/memory"); +const { MemoryFront } = require("devtools/shared/fronts/memory"); const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient"); const promise = require("promise"); diff --git a/devtools/server/actors/memory.js b/devtools/server/actors/memory.js index 5730b8ba1a6a..e0636f759b22 100644 --- a/devtools/server/actors/memory.js +++ b/devtools/server/actors/memory.js @@ -4,33 +4,13 @@ "use strict"; -const { Cc, Ci, Cu, components } = require("chrome"); const protocol = require("devtools/shared/protocol"); -const { method, RetVal, Arg, types } = protocol; const { Memory } = require("devtools/server/performance/memory"); -const { actorBridge } = require("devtools/server/actors/common"); -const { Task } = require("devtools/shared/task"); +const { actorBridgeWithSpec } = require("devtools/server/actors/common"); +const { memorySpec } = require("devtools/shared/specs/memory"); loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "StackFrameCache", "devtools/server/actors/utils/stack", true); -loader.lazyRequireGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm", true); -loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true); -loader.lazyRequireGetter(this, "HeapSnapshotFileUtils", - "devtools/shared/heapsnapshot/HeapSnapshotFileUtils"); -loader.lazyRequireGetter(this, "ThreadSafeChromeUtils"); - -types.addDictType("AllocationsRecordingOptions", { - // The probability we sample any given allocation when recording - // allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling - // every allocation. - probability: "number", - - // The maximum number of of allocation events to keep in the allocations - // log. If new allocations arrive, when we are already at capacity, the oldest - // allocation event is lost. This number must fit in a 32 bit signed integer. - maxLogLength: "number" -}); /** * An actor that returns memory usage data for its parent actor's window. @@ -43,31 +23,7 @@ types.addDictType("AllocationsRecordingOptions", { * * @see devtools/server/performance/memory.js for documentation. */ -var MemoryActor = exports.MemoryActor = protocol.ActorClass({ - typeName: "memory", - - /** - * The set of unsolicited events the MemoryActor emits that will be sent over - * the RDP (by protocol.js). - */ - - events: { - // Same format as the data passed to the - // `Debugger.Memory.prototype.onGarbageCollection` hook. See - // `js/src/doc/Debugger/Debugger.Memory.md` for documentation. - "garbage-collection": { - type: "garbage-collection", - data: Arg(0, "json"), - }, - - // Same data as the data from `getAllocations` -- only fired if - // `autoDrain` set during `startRecordingAllocations`. - "allocations": { - type: "allocations", - data: Arg(0, "json"), - }, - }, - +exports.MemoryActor = protocol.ActorClassWithSpec(memorySpec, { initialize: function (conn, parent, frameCache = new StackFrameCache()) { protocol.Actor.prototype.initialize.call(this, conn); @@ -85,88 +41,33 @@ var MemoryActor = exports.MemoryActor = protocol.ActorClass({ protocol.Actor.prototype.destroy.call(this); }, - attach: actorBridge("attach", { - request: {}, - response: { - type: "attached" - } - }), + attach: actorBridgeWithSpec("attach"), - detach: actorBridge("detach", { - request: {}, - response: { - type: "detached" - } - }), + detach: actorBridgeWithSpec("detach"), - getState: actorBridge("getState", { - response: { - state: RetVal(0, "string") - } - }), + getState: actorBridgeWithSpec("getState"), - saveHeapSnapshot: method(function () { + saveHeapSnapshot: function () { return this.bridge.saveHeapSnapshot(); - }, { - response: { - snapshotId: RetVal("string") - } - }), + }, - takeCensus: actorBridge("takeCensus", { - request: {}, - response: RetVal("json") - }), + takeCensus: actorBridgeWithSpec("takeCensus"), - startRecordingAllocations: actorBridge("startRecordingAllocations", { - request: { - options: Arg(0, "nullable:AllocationsRecordingOptions") - }, - response: { - // Accept `nullable` in the case of server Gecko <= 37, handled on the front - value: RetVal(0, "nullable:number") - } - }), + startRecordingAllocations: actorBridgeWithSpec("startRecordingAllocations"), - stopRecordingAllocations: actorBridge("stopRecordingAllocations", { - request: {}, - response: { - // Accept `nullable` in the case of server Gecko <= 37, handled on the front - value: RetVal(0, "nullable:number") - } - }), + stopRecordingAllocations: actorBridgeWithSpec("stopRecordingAllocations"), - getAllocationsSettings: actorBridge("getAllocationsSettings", { - request: {}, - response: { - options: RetVal(0, "json") - } - }), + getAllocationsSettings: actorBridgeWithSpec("getAllocationsSettings"), - getAllocations: actorBridge("getAllocations", { - request: {}, - response: RetVal("json") - }), + getAllocations: actorBridgeWithSpec("getAllocations"), - forceGarbageCollection: actorBridge("forceGarbageCollection", { - request: {}, - response: {} - }), + forceGarbageCollection: actorBridgeWithSpec("forceGarbageCollection"), - forceCycleCollection: actorBridge("forceCycleCollection", { - request: {}, - response: {} - }), + forceCycleCollection: actorBridgeWithSpec("forceCycleCollection"), - measure: actorBridge("measure", { - request: {}, - response: RetVal("json"), - }), + measure: actorBridgeWithSpec("measure"), - residentUnique: actorBridge("residentUnique", { - request: {}, - response: { value: RetVal("number") } - }), + residentUnique: actorBridgeWithSpec("residentUnique"), _onGarbageCollection: function (data) { if (this.conn.transport) { @@ -180,76 +81,3 @@ var MemoryActor = exports.MemoryActor = protocol.ActorClass({ } }, }); - -exports.MemoryFront = protocol.FrontClass(MemoryActor, { - initialize: function (client, form, rootForm = null) { - protocol.Front.prototype.initialize.call(this, client, form); - this._client = client; - this.actorID = form.memoryActor; - this.heapSnapshotFileActorID = rootForm - ? rootForm.heapSnapshotFileActor - : null; - this.manage(this); - }, - - /** - * Save a heap snapshot, transfer it from the server to the client if the - * server and client do not share a file system, and return the local file - * path to the heap snapshot. - * - * Note that this is safe to call for actors inside sandoxed child processes, - * as we jump through the correct IPDL hoops. - * - * @params Boolean options.forceCopy - * Always force a bulk data copy of the saved heap snapshot, even when - * the server and client share a file system. - * - * @returns Promise - */ - saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) { - const snapshotId = yield this._saveHeapSnapshotImpl(); - - if (!options.forceCopy && - (yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) { - return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId); - } - - return yield this.transferHeapSnapshot(snapshotId); - }), { - impl: "_saveHeapSnapshotImpl" - }), - - /** - * Given that we have taken a heap snapshot with the given id, transfer the - * heap snapshot file to the client. The path to the client's local file is - * returned. - * - * @param {String} snapshotId - * - * @returns Promise - */ - transferHeapSnapshot: protocol.custom(function (snapshotId) { - if (!this.heapSnapshotFileActorID) { - throw new Error("MemoryFront initialized without a rootForm"); - } - - const request = this._client.request({ - to: this.heapSnapshotFileActorID, - type: "transferHeapSnapshot", - snapshotId - }); - - return new Promise((resolve, reject) => { - const outFilePath = - HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath(); - const outFile = new FileUtils.File(outFilePath); - - const outFileStream = FileUtils.openSafeFileOutputStream(outFile); - request.on("bulk-reply", Task.async(function* ({ copyTo }) { - yield copyTo(outFileStream); - FileUtils.closeSafeFileOutputStream(outFileStream); - resolve(outFilePath); - })); - }); - }) -}); diff --git a/devtools/server/tests/mochitest/memory-helpers.js b/devtools/server/tests/mochitest/memory-helpers.js index 3a828ad3f065..0e7007b5e457 100644 --- a/devtools/server/tests/mochitest/memory-helpers.js +++ b/devtools/server/tests/mochitest/memory-helpers.js @@ -8,7 +8,7 @@ var Services = require("Services"); var { DebuggerClient } = require("devtools/shared/client/main"); var { DebuggerServer } = require("devtools/server/main"); -var { MemoryFront } = require("devtools/server/actors/memory"); +var { MemoryFront } = require("devtools/shared/fronts/memory"); // Always log packets when running tests. Services.prefs.setBoolPref("devtools.debugger.log", true); diff --git a/devtools/server/tests/unit/head_dbg.js b/devtools/server/tests/unit/head_dbg.js index ea3cbd4c8828..c550c7b6ffa7 100644 --- a/devtools/server/tests/unit/head_dbg.js +++ b/devtools/server/tests/unit/head_dbg.js @@ -38,7 +38,7 @@ const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { DebuggerServer } = require("devtools/server/main"); const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main"); const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); -const { MemoryFront } = require("devtools/server/actors/memory"); +const { MemoryFront } = require("devtools/shared/fronts/memory"); const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {}); diff --git a/devtools/shared/fronts/memory.js b/devtools/shared/fronts/memory.js new file mode 100644 index 000000000000..ac162b16b478 --- /dev/null +++ b/devtools/shared/fronts/memory.js @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { memorySpec } = require("devtools/shared/specs/memory"); +const { Task } = require("devtools/shared/task"); +const protocol = require("devtools/shared/protocol"); + +loader.lazyRequireGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm", true); +loader.lazyRequireGetter(this, "HeapSnapshotFileUtils", + "devtools/shared/heapsnapshot/HeapSnapshotFileUtils"); + +const MemoryFront = protocol.FrontClassWithSpec(memorySpec, { + initialize: function (client, form, rootForm = null) { + protocol.Front.prototype.initialize.call(this, client, form); + this._client = client; + this.actorID = form.memoryActor; + this.heapSnapshotFileActorID = rootForm + ? rootForm.heapSnapshotFileActor + : null; + this.manage(this); + }, + + /** + * Save a heap snapshot, transfer it from the server to the client if the + * server and client do not share a file system, and return the local file + * path to the heap snapshot. + * + * Note that this is safe to call for actors inside sandoxed child processes, + * as we jump through the correct IPDL hoops. + * + * @params Boolean options.forceCopy + * Always force a bulk data copy of the saved heap snapshot, even when + * the server and client share a file system. + * + * @returns Promise + */ + saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) { + const snapshotId = yield this._saveHeapSnapshotImpl(); + + if (!options.forceCopy && + (yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) { + return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId); + } + + return yield this.transferHeapSnapshot(snapshotId); + }), { + impl: "_saveHeapSnapshotImpl" + }), + + /** + * Given that we have taken a heap snapshot with the given id, transfer the + * heap snapshot file to the client. The path to the client's local file is + * returned. + * + * @param {String} snapshotId + * + * @returns Promise + */ + transferHeapSnapshot: protocol.custom(function (snapshotId) { + if (!this.heapSnapshotFileActorID) { + throw new Error("MemoryFront initialized without a rootForm"); + } + + const request = this._client.request({ + to: this.heapSnapshotFileActorID, + type: "transferHeapSnapshot", + snapshotId + }); + + return new Promise((resolve, reject) => { + const outFilePath = + HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath(); + const outFile = new FileUtils.File(outFilePath); + + const outFileStream = FileUtils.openSafeFileOutputStream(outFile); + request.on("bulk-reply", Task.async(function* ({ copyTo }) { + yield copyTo(outFileStream); + FileUtils.closeSafeFileOutputStream(outFileStream); + resolve(outFilePath); + })); + }); + }) +}); + +exports.MemoryFront = MemoryFront; diff --git a/devtools/shared/fronts/moz.build b/devtools/shared/fronts/moz.build index 6e05243f1c50..3537b464c881 100644 --- a/devtools/shared/fronts/moz.build +++ b/devtools/shared/fronts/moz.build @@ -19,6 +19,7 @@ DevToolsModules( 'highlighters.js', 'inspector.js', 'layout.js', + 'memory.js', 'preference.js', 'promises.js', 'settings.js', diff --git a/devtools/shared/specs/memory.js b/devtools/shared/specs/memory.js new file mode 100644 index 000000000000..b8dea3962c5e --- /dev/null +++ b/devtools/shared/specs/memory.js @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { + Arg, + RetVal, + types, + generateActorSpec, +} = require("devtools/shared/protocol"); + +types.addDictType("AllocationsRecordingOptions", { + // The probability we sample any given allocation when recording + // allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling + // every allocation. + probability: "number", + + // The maximum number of of allocation events to keep in the allocations + // log. If new allocations arrive, when we are already at capacity, the oldest + // allocation event is lost. This number must fit in a 32 bit signed integer. + maxLogLength: "number" +}); + +const memorySpec = generateActorSpec({ + typeName: "memory", + + /** + * The set of unsolicited events the MemoryActor emits that will be sent over + * the RDP (by protocol.js). + */ + events: { + // Same format as the data passed to the + // `Debugger.Memory.prototype.onGarbageCollection` hook. See + // `js/src/doc/Debugger/Debugger.Memory.md` for documentation. + "garbage-collection": { + type: "garbage-collection", + data: Arg(0, "json"), + }, + + // Same data as the data from `getAllocations` -- only fired if + // `autoDrain` set during `startRecordingAllocations`. + "allocations": { + type: "allocations", + data: Arg(0, "json"), + }, + }, + + methods: { + attach: { + request: {}, + response: { + type: "attached" + } + }, + detach: { + request: {}, + response: { + type: "detached" + } + }, + getState: { + response: { + state: RetVal(0, "string") + } + }, + takeCensus: { + request: {}, + response: RetVal("json") + }, + startRecordingAllocations: { + request: { + options: Arg(0, "nullable:AllocationsRecordingOptions") + }, + response: { + // Accept `nullable` in the case of server Gecko <= 37, handled on the front + value: RetVal(0, "nullable:number") + } + }, + stopRecordingAllocations: { + request: {}, + response: { + // Accept `nullable` in the case of server Gecko <= 37, handled on the front + value: RetVal(0, "nullable:number") + } + }, + getAllocationsSettings: { + request: {}, + response: { + options: RetVal(0, "json") + } + }, + getAllocations: { + request: {}, + response: RetVal("json") + }, + forceGarbageCollection: { + request: {}, + response: {} + }, + forceCycleCollection: { + request: {}, + response: {} + }, + measure: { + request: {}, + response: RetVal("json"), + }, + residentUnique: { + request: {}, + response: { value: RetVal("number") } + }, + saveHeapSnapshot: { + response: { + snapshotId: RetVal("string") + } + }, + }, +}); + +exports.memorySpec = memorySpec; diff --git a/devtools/shared/specs/moz.build b/devtools/shared/specs/moz.build index 9b4464990df3..dfe132096cc0 100644 --- a/devtools/shared/specs/moz.build +++ b/devtools/shared/specs/moz.build @@ -23,6 +23,7 @@ DevToolsModules( 'highlighters.js', 'inspector.js', 'layout.js', + 'memory.js', 'node.js', 'preference.js', 'promises.js',