Created
June 24, 2021 14:00
-
-
Save bmeck/7c57ed54b8ee17b7b8d20d0c13fae7ad to your computer and use it in GitHub Desktop.
in-progress in-thread heapsnapshot reflective API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ns from './index.js'; | |
import {promisify} from 'util'; | |
import {createReadStream, createWriteStream} from 'fs'; | |
import {Session} from 'inspector'; | |
import {PassThrough} from 'stream'; | |
import {createHash} from 'crypto'; | |
const session = new Session(); | |
const stream = new PassThrough(); | |
const post = promisify(session.post.bind(session)); | |
session.connect(); | |
session.on('HeapProfiler.addHeapSnapshotChunk', (m) => { | |
stream.write(m.params.chunk); | |
}); | |
session.on('Debugger.scriptParsed', (m) => { | |
if (m.params.url === import.meta.url) { | |
// console.log(m.params) | |
session.post('Debugger.getScriptSource', { | |
scriptId: m.params.scriptId | |
}, (e, src) => { | |
if (e) return; | |
if (src) { | |
let results = {}; | |
for (const algo of ['md5','sha1','sha256','sha384','sha512']) { | |
results[algo] = createHash(algo) | |
.update(src.scriptSource) | |
.digest('hex'); | |
} | |
// console.log(results); | |
} | |
}) | |
} | |
}) | |
await post('Debugger.enable', {}); | |
const filename = 'tmp.heapsnapshot'; | |
const file = createWriteStream(filename); | |
stream.pipe(file); | |
class EASY_TO_TEST {}; | |
async function main() { | |
let leak = new EASY_TO_TEST(); | |
await post('HeapProfiler.takeHeapSnapshot') | |
let gcd = new EASY_TO_TEST; | |
let heapId = (await getHeapId(gcd)).heapSnapshotObjectId; | |
console.log( | |
'gcd-pre', await getObjectFromHeapId(heapId) | |
); | |
// console.log(gcd.toString()); | |
gcd = null; | |
// leak = null; | |
console.log( | |
'gcd-nulled', await getObjectFromHeapId(heapId) | |
); | |
stream.end(); | |
await new Promise((f, r) => { | |
file.on('close', f); | |
file.on('error', r); | |
}); | |
// keep leak alive until here | |
setTimeout(() => console.log(leak.toString()), 10e3); | |
const snapshot = new ns.HeapSnapshot( | |
await ns.SplitSnapshotProvider.fromStream( | |
createReadStream(filename) | |
) | |
); | |
const iter = snapshot.walk({ | |
onNodeOpen(node) { | |
const {fields} = node; | |
if (fields.name === EASY_TO_TEST.name) { | |
if (fields.type === 'object') { | |
console.log(node.fields); | |
} | |
} | |
}, | |
}); | |
// perform the walk | |
for (const _ of iter) { | |
} | |
snapshot.provider.writeToDirectory( | |
'tmp' | |
); | |
} | |
main(); | |
async function getHeapId(value) { | |
await post('Debugger.enable'); | |
await post('HeapProfiler.enable'); | |
let result; | |
session.once('Debugger.paused', ({params: {callFrames}}) => { | |
result = post('Debugger.evaluateOnCallFrame', { | |
callFrameId: callFrames[0].callFrameId, | |
expression: 'value' | |
}); | |
}); | |
debugger; | |
const {result: {objectId}} = await result; | |
return post('HeapProfiler.getHeapObjectId', { | |
objectId | |
}); | |
} | |
async function getObjectFromHeapId(id) { | |
await post('HeapProfiler.enable'); | |
let result = await post('HeapProfiler.getObjectByHeapObjectId', { | |
objectId: id, | |
objectGroup: 'getObjectFromHeapId' | |
}); | |
let value; | |
session.once('Debugger.paused', ({params: {callFrames}}) => { | |
const objectId = result.result.objectId; | |
result = post('Debugger.setVariableValue', { | |
callFrameId: callFrames[0].callFrameId, | |
variableName: 'value', | |
scopeNumber: 0, | |
newValue: {objectId} | |
}); | |
}); | |
debugger; | |
return value; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment