foxdriver icon indicating copy to clipboard operation
foxdriver copied to clipboard

Taking a heap snapshot

Open sunnz opened this issue 5 years ago • 1 comments

Hi,

I am trying to take a heap snapshot with foxdriver like so:

const Foxdriver = require('foxdriver')

;(async function() {
    const { browser, tab } = await Foxdriver.attach('localhost', 6000)
    await browser.tabs[0].memory.attach()
    const id = await browser.tabs[0].memory.saveHeapSnapshot()
    console.log(id)
    const snapshot = await browser.heapSnapshotFile.getHeapSnapshot(id)
    // console.log(snapshot)
    browser.tabs[0].memory.detach()
    browser.disconnect()
})()

However this seem to hang at line browser.heapSnapshotFile.getHeapSnapshot

What am I doing wrong?

sunnz avatar Oct 04 '19 06:10 sunnz

I had taken a deeper look and found a workaround by adding a few properties and methods into the Client actor:

const Foxdriver = require('foxdriver')
const fs = require('fs')

;(async function() {
    const { browser, tab } = await Foxdriver.attach('127.0.0.1', 6000)
    // monkeypatch Client Actor to handle bulk data packets from firefox remote debugging protocol
    function (client) {
        client._bulk = new Map()
        client.readMessage = function () {
            var sep = this.incoming.toString().indexOf(':')

            if (sep < 0) {
                return false
            }

            /**
             * beginning of a message is preceded by byteLength(message) + ":",
             * or "bulk" + " " + actorId + " " + dataName + " " + byteLength(message) + ":"
             */
            const messageHeaderString = this.incoming.slice(0, sep).toString()
            const messageHeaders = messageHeaderString.split(' ')
            const messageIsBulk = (messageHeaders[0] === 'bulk')
            const messageActorId = messageHeaders[1]
            const messageDataName = messageHeaders[2]
            const byteLength = messageIsBulk ? parseInt(messageHeaders[3]) : parseInt(messageHeaderString)

            /**
             * check if response is complete
             */
            if (this.incoming.length - (sep + 1) < byteLength) {
                return false
            }

            this.incoming = this.incoming.slice(sep + 1)
            const packet = this.incoming.slice(0, byteLength)
            this.incoming = this.incoming.slice(byteLength)
            if (messageIsBulk) {
                // console.log(packet)
                const bulkId = `bulk ${messageActorId} ${messageDataName}`
                this.handleBulkPacket(packet, bulkId)
                // this.handleMessage(packet)
            } else {
                this.handleMessage(packet)
            }
            return true
        }

        /**
         * handle unsolicited bulk packet from server
         */
        client.handleBulkPacket = function (packet, bulkId) {
            this.log.info(`bulk packet: ${packet}`)
            this.pushToBulkStack(bulkId, packet)
            return this.emit('message', packet)
        }

        client.pushToBulkStack = function (bulkId, data) {
            if (!this._bulk.has(bulkId)) {
                // create array for the stack if it is does not already exists
                this._bulk.set(bulkId, [])
            }
            this._bulk.get(bulkId).push(data)
        }

        client.popBulkStack = function (bulkId) {
            return this._bulk.get(bulkId).pop()
        }
    } (browser.client)

    // get snapshot
    await browser.tabs[0].memory.attach()
    const id = await browser.tabs[0].memory.saveHeapSnapshot()
    let snapshotResponse = await browser.heapSnapshotFile.getHeapSnapshot(id)
    // the actual snapshot data are available from the bulk stack after this point
    let bulkId = `bulk ${snapshotResponse.from} heap-snapshot`
    let snapshotData = browser.client.popBulkStack(bulkId)
    // save snapshot to disk
    fs.writeFile(id + '.fxsnapshot', snapshotData)
})()

I have found that getHeapSnapshot() uses Client Actor to send a transferSnapshot request to firefox, but when the response comes back, it is delivered as a bulk data packet followed by a JSON packet (see stream transport, packets here: https://docs.firefox-dev.tools/backend/protocol.html) but Client.readMessage() reads all incoming packets as JSON packets and stucks in an infinite loop when it gets a bulk data packet

The code above changes readMessage to both read the incoming packet and stores it into a local _bulk stack if it is a bulk data packet, which can be retrieved later from the stack with the actor ID

sunnz avatar Oct 21 '19 01:10 sunnz