diff --git a/bin/cleanheap b/bin/cleanheap deleted file mode 100644 index 5f08400..0000000 --- a/bin/cleanheap +++ /dev/null @@ -1,535 +0,0 @@ -// node_modules/chalk/source/vendor/ansi-styles/index.js -var assembleStyles = function() { - const codes = new Map; - for (const [groupName, group] of Object.entries(styles)) { - for (const [styleName, style] of Object.entries(group)) { - styles[styleName] = { - open: `\x1B[${style[0]}m`, - close: `\x1B[${style[1]}m` - }; - group[styleName] = styles[styleName]; - codes.set(style[0], style[1]); - } - Object.defineProperty(styles, groupName, { - value: group, - enumerable: false - }); - } - Object.defineProperty(styles, "codes", { - value: codes, - enumerable: false - }); - styles.color.close = "\x1B[39m"; - styles.bgColor.close = "\x1B[49m"; - styles.color.ansi = wrapAnsi16(); - styles.color.ansi256 = wrapAnsi256(); - styles.color.ansi16m = wrapAnsi16m(); - styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET); - styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET); - styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET); - Object.defineProperties(styles, { - rgbToAnsi256: { - value(red, green, blue) { - if (red === green && green === blue) { - if (red < 8) { - return 16; - } - if (red > 248) { - return 231; - } - return Math.round((red - 8) / 247 * 24) + 232; - } - return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5); - }, - enumerable: false - }, - hexToRgb: { - value(hex) { - const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16)); - if (!matches) { - return [0, 0, 0]; - } - let [colorString] = matches; - if (colorString.length === 3) { - colorString = [...colorString].map((character) => character + character).join(""); - } - const integer = Number.parseInt(colorString, 16); - return [ - integer >> 16 & 255, - integer >> 8 & 255, - integer & 255 - ]; - }, - enumerable: false - }, - hexToAnsi256: { - value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)), - enumerable: false - }, - ansi256ToAnsi: { - value(code) { - if (code < 8) { - return 30 + code; - } - if (code < 16) { - return 90 + (code - 8); - } - let red; - let green; - let blue; - if (code >= 232) { - red = ((code - 232) * 10 + 8) / 255; - green = red; - blue = red; - } else { - code -= 16; - const remainder = code % 36; - red = Math.floor(code / 36) / 5; - green = Math.floor(remainder / 6) / 5; - blue = remainder % 6 / 5; - } - const value = Math.max(red, green, blue) * 2; - if (value === 0) { - return 30; - } - let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red)); - if (value === 2) { - result += 60; - } - return result; - }, - enumerable: false - }, - rgbToAnsi: { - value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)), - enumerable: false - }, - hexToAnsi: { - value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)), - enumerable: false - } - }); - return styles; -}; -var ANSI_BACKGROUND_OFFSET = 10; -var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`; -var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`; -var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`; -var styles = { - modifier: { - reset: [0, 0], - bold: [1, 22], - dim: [2, 22], - italic: [3, 23], - underline: [4, 24], - overline: [53, 55], - inverse: [7, 27], - hidden: [8, 28], - strikethrough: [9, 29] - }, - color: { - black: [30, 39], - red: [31, 39], - green: [32, 39], - yellow: [33, 39], - blue: [34, 39], - magenta: [35, 39], - cyan: [36, 39], - white: [37, 39], - blackBright: [90, 39], - gray: [90, 39], - grey: [90, 39], - redBright: [91, 39], - greenBright: [92, 39], - yellowBright: [93, 39], - blueBright: [94, 39], - magentaBright: [95, 39], - cyanBright: [96, 39], - whiteBright: [97, 39] - }, - bgColor: { - bgBlack: [40, 49], - bgRed: [41, 49], - bgGreen: [42, 49], - bgYellow: [43, 49], - bgBlue: [44, 49], - bgMagenta: [45, 49], - bgCyan: [46, 49], - bgWhite: [47, 49], - bgBlackBright: [100, 49], - bgGray: [100, 49], - bgGrey: [100, 49], - bgRedBright: [101, 49], - bgGreenBright: [102, 49], - bgYellowBright: [103, 49], - bgBlueBright: [104, 49], - bgMagentaBright: [105, 49], - bgCyanBright: [106, 49], - bgWhiteBright: [107, 49] - } -}; -var modifierNames = Object.keys(styles.modifier); -var foregroundColorNames = Object.keys(styles.color); -var backgroundColorNames = Object.keys(styles.bgColor); -var colorNames = [...foregroundColorNames, ...backgroundColorNames]; -var ansiStyles = assembleStyles(); -var ansi_styles_default = ansiStyles; - -// node_modules/chalk/source/vendor/supports-color/browser.js -var level = (() => { - if (navigator.userAgentData) { - const brand = navigator.userAgentData.brands.find(({ brand: brand2 }) => brand2 === "Chromium"); - if (brand && brand.version > 93) { - return 3; - } - } - if (/\b(Chrome|Chromium)\//.test(navigator.userAgent)) { - return 1; - } - return 0; -})(); -var colorSupport = level !== 0 && { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 -}; -var supportsColor = { - stdout: colorSupport, - stderr: colorSupport -}; -var browser_default = supportsColor; - -// node_modules/chalk/source/utilities.js -function stringReplaceAll(string, substring, replacer) { - let index = string.indexOf(substring); - if (index === -1) { - return string; - } - const substringLength = substring.length; - let endIndex = 0; - let returnValue = ""; - do { - returnValue += string.slice(endIndex, index) + substring + replacer; - endIndex = index + substringLength; - index = string.indexOf(substring, endIndex); - } while (index !== -1); - returnValue += string.slice(endIndex); - return returnValue; -} -function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) { - let endIndex = 0; - let returnValue = ""; - do { - const gotCR = string[index - 1] === "\r"; - returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix; - endIndex = index + 1; - index = string.indexOf("\n", endIndex); - } while (index !== -1); - returnValue += string.slice(endIndex); - return returnValue; -} - -// node_modules/chalk/source/index.js -var createChalk = function(options) { - return chalkFactory(options); -}; -var { stdout: stdoutColor, stderr: stderrColor } = browser_default; -var GENERATOR = Symbol("GENERATOR"); -var STYLER = Symbol("STYLER"); -var IS_EMPTY = Symbol("IS_EMPTY"); -var levelMapping = [ - "ansi", - "ansi", - "ansi256", - "ansi16m" -]; -var styles2 = Object.create(null); -var applyOptions = (object, options = {}) => { - if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { - throw new Error("The `level` option should be an integer from 0 to 3"); - } - const colorLevel = stdoutColor ? stdoutColor.level : 0; - object.level = options.level === undefined ? colorLevel : options.level; -}; -var chalkFactory = (options) => { - const chalk = (...strings) => strings.join(" "); - applyOptions(chalk, options); - Object.setPrototypeOf(chalk, createChalk.prototype); - return chalk; -}; -Object.setPrototypeOf(createChalk.prototype, Function.prototype); -for (const [styleName, style] of Object.entries(ansi_styles_default)) { - styles2[styleName] = { - get() { - const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]); - Object.defineProperty(this, styleName, { value: builder }); - return builder; - } - }; -} -styles2.visible = { - get() { - const builder = createBuilder(this, this[STYLER], true); - Object.defineProperty(this, "visible", { value: builder }); - return builder; - } -}; -var getModelAnsi = (model, level2, type, ...arguments_) => { - if (model === "rgb") { - if (level2 === "ansi16m") { - return ansi_styles_default[type].ansi16m(...arguments_); - } - if (level2 === "ansi256") { - return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_)); - } - return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_)); - } - if (model === "hex") { - return getModelAnsi("rgb", level2, type, ...ansi_styles_default.hexToRgb(...arguments_)); - } - return ansi_styles_default[type][model](...arguments_); -}; -var usedModels = ["rgb", "hex", "ansi256"]; -for (const model of usedModels) { - styles2[model] = { - get() { - const { level: level2 } = this; - return function(...arguments_) { - const styler = createStyler(getModelAnsi(model, levelMapping[level2], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]); - return createBuilder(this, styler, this[IS_EMPTY]); - }; - } - }; - const bgModel = "bg" + model[0].toUpperCase() + model.slice(1); - styles2[bgModel] = { - get() { - const { level: level2 } = this; - return function(...arguments_) { - const styler = createStyler(getModelAnsi(model, levelMapping[level2], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]); - return createBuilder(this, styler, this[IS_EMPTY]); - }; - } - }; -} -var proto = Object.defineProperties(() => { -}, { - ...styles2, - level: { - enumerable: true, - get() { - return this[GENERATOR].level; - }, - set(level2) { - this[GENERATOR].level = level2; - } - } -}); -var createStyler = (open, close, parent) => { - let openAll; - let closeAll; - if (parent === undefined) { - openAll = open; - closeAll = close; - } else { - openAll = parent.openAll + open; - closeAll = close + parent.closeAll; - } - return { - open, - close, - openAll, - closeAll, - parent - }; -}; -var createBuilder = (self, _styler, _isEmpty) => { - const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" ")); - Object.setPrototypeOf(builder, proto); - builder[GENERATOR] = self; - builder[STYLER] = _styler; - builder[IS_EMPTY] = _isEmpty; - return builder; -}; -var applyStyle = (self, string) => { - if (self.level <= 0 || !string) { - return self[IS_EMPTY] ? "" : string; - } - let styler = self[STYLER]; - if (styler === undefined) { - return string; - } - const { openAll, closeAll } = styler; - if (string.includes("\x1B")) { - while (styler !== undefined) { - string = stringReplaceAll(string, styler.close, styler.open); - styler = styler.parent; - } - } - const lfIndex = string.indexOf("\n"); - if (lfIndex !== -1) { - string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); - } - return openAll + string + closeAll; -}; -Object.defineProperties(createChalk.prototype, styles2); -var chalk = createChalk(); -var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 }); -var source_default = chalk; -// package.json -var package_default = { - name: "cleanheap", - version: "0.0.0", - description: "A tool for scrubbing Weak retainer paths from a heap snapshot", - main: "src/index.ts", - scripts: { - test: "echo \"Error: no test specified\" && exit 1" - }, - keywords: ["v8", "chrome", "heapdump", "heapsnapshot", "memory", "memory-leak"], - author: "Chris Thoborn (@runspired) ", - license: "MIT", - volta: { - node: "20.5.0", - pnpm: "8.6.10" - }, - devDependencies: { - "bun-types": "latest" - }, - peerDependencies: { - typescript: "^5.0.0" - }, - dependencies: { - chalk: "^5.3.0" - } -}; - -// src/index.ts -var logCompletion = function(str) { - process.stdout.clearLine(0); - process.stdout.cursorTo(0); - console.log(str); -}; -async function getInput(filePath) { - const fileHandle = Bun.file(filePath, { type: "application/json" }); - const exists = await fileHandle.exists(); - if (!exists) { - console.log(source_default.red(`\tThe file ${source_default.white(filePath)} does not exist!\n`)); - process.exit(1); - } - process.stdout.write(source_default.grey(`\t${source_default.white("\xB7")} \uD83D\uDD38 ...loading Snapshot`)); - const data = await fileHandle.json(); - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t${source_default.green("\u25B6")} Snapshot loaded`)); - return data; -} -async function getOutputFile(filePath) { - const fileHandle = Bun.file(filePath, { type: "application/json" }); - const exists = await fileHandle.exists(); - if (exists) { - console.log(source_default.yellow(` \u26A0\uFE0F Overwritting existing file ${source_default.white(filePath)}!\n`)); - } - return fileHandle; -} -var cleanupRetainers = function(data, edgeCount, startOffset, start, nextEdgeIndex, edgeFieldsCount) { - const { edges, nodes } = data; - iterateBySlice(start, nextEdgeIndex, edgeFieldsCount, (edgeStart, edgeEnd) => { - for (let i = edgeStart;i < edgeEnd; i++) { - edges[i] = null; - } - data.snapshot.edge_count--; - nodes[startOffset + edgeCount]--; - }); -}; -var fieldOffsets = function(fields) { - return Object.fromEntries(fields.map((field, offset) => [field, offset])); -}; -var getNodeName = function(wrapper, nodeIndex) { - const { nodes, strings } = wrapper.data; - const type = wrapper.nodeTypeEnum[nodes[nodeIndex + wrapper.nodeOffsets.type]]; - if (type !== "object") { - return null; - } - return strings[nodes[nodeIndex + wrapper.nodeOffsets.name]]; -}; -var getField = function(nodes, offset, index) { - return nodes[offset + index]; -}; -var iterateBySlice = function(start, end, sliceSize, callback) { - for (let i = start;i < end; i += sliceSize) { - callback(i, i + sliceSize); - } -}; -async function main() { - const inputFilePath = Bun.argv[2]; - const outputFilePath = Bun.argv[3] || inputFilePath.replace(/\.heapsnapshot$/, ".clean.heapsnapshot"); - console.log(source_default.grey(` - - \u2728 CleanHeap ${source_default.green("v" + package_default.version)}\n\t====================\n\n`) + source_default.grey(`\tReading ${source_default.green("\u25B6")} ${source_default.yellow(inputFilePath)}\n`) + source_default.grey(`\tWriting ${source_default.green("\u25B6")} ${source_default.yellow(outputFilePath)}\n\n`) + source_default.magenta(` \uD83E\uDDF9 Cleaning HeapSnapshot edges of Weak Retainers - -`)); - const writeHandler = await getOutputFile(outputFilePath); - const data = await getInput(inputFilePath); - process.stdout.write(source_default.grey(`\t${source_default.white("\xB7")} \uD83D\uDD38 ...parsing Snapshot`)); - const snapshot = new Snapshot(data); - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t${source_default.green("\u25B6")} Snapshot parsed`)); - process.stdout.write(source_default.grey(`\t${source_default.white("\xB7")} \uD83D\uDD38 ...cleaning Snapshot`)); - const isDirty = snapshot.clean(); - if (!isDirty) { - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t${source_default.green("\u25B6")} Snapshot Was Already Clean`)); - } else { - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t${source_default.green("\u25B6")} Snapshot cleaned`)); - process.stdout.write(source_default.grey(`\t${source_default.white("\xB7")} \uD83D\uDD38 ...writing Snapshot`)); - await Bun.write(writeHandler, JSON.stringify(snapshot.data)); - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t${source_default.green("\u25B6")} Snapshot written`)); - } - console.log(source_default.magenta(` - \u2728 Sparkling Clean - -`)); -} -var WeakRetainerNames = new Set([ - "WeakMap", - "WeakSet", - "WeakRef", - "DebugWeakCache", - "DebugWeakMap" -]); - -class Snapshot { - constructor(data) { - const meta = data.snapshot.meta; - this.data = data; - this.nodeOffsets = fieldOffsets(meta.node_fields); - this.edgeOffsets = fieldOffsets(meta.edge_fields); - this.nodeTypeEnum = meta.node_types[this.nodeOffsets.type]; - this.edgeTypeEnum = meta.edge_types[this.edgeOffsets.type]; - } - clean() { - let nextEdgeIndex = 0; - let totalObjects = 0; - let weakRetainers = 0; - const nodes = this.data.nodes; - const nodeFields = this.data.snapshot.meta.node_fields; - const fieldSliceSize = nodeFields.length; - const edgeCount = this.data.snapshot.edge_count; - const edgeFieldsCount = this.data.snapshot.meta.edge_fields.length; - const data = this.data; - iterateBySlice(0, nodes.length, fieldSliceSize, (startOffset, endOffset) => { - const start = nextEdgeIndex; - const ownedEdgeCount = getField(nodes, startOffset, edgeCount); - nextEdgeIndex = nextEdgeIndex + ownedEdgeCount + edgeFieldsCount; - totalObjects++; - const nodeName = getNodeName(this, startOffset); - if (!nodeName || !WeakRetainerNames.has(nodeName)) { - return; - } - weakRetainers++; - cleanupRetainers(data, edgeCount, startOffset, start, nextEdgeIndex, edgeFieldsCount); - }); - this.data.edges = this.data.edges.filter((v) => v !== null); - if (weakRetainers > 0) { - logCompletion(source_default.grey(`\t${source_default.white("\xB7")}\t\t${source_default.green("\u25B6")} Removed ${source_default.magenta(weakRetainers)} of ${source_default.magenta(totalObjects)} total traversed.`)); - } - return Boolean(weakRetainers > 0); - } -} -await main(); diff --git a/cleanheap b/cleanheap new file mode 100755 index 0000000..e8d86e3 Binary files /dev/null and b/cleanheap differ diff --git a/package.json b/package.json index 626e61d..ed3570e 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "cleanheap", - "version": "0.0.1", + "version": "0.0.2", "description": "A tool for scrubbing Weak retainer paths from a heap snapshot", "main": "src/index.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { - "cleanheap": "./bin/cleanheap" + "cleanheap": "./cleanheap" }, "keywords": ["v8", "chrome", "heapdump", "heapsnapshot", "memory", "memory-leak"], "author": "Chris Thoborn (@runspired) ",