UNPKG

@memlab/cli

Version:

command line interface for memlab

433 lines (432 loc) 18.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); const worker_threads_1 = require("worker_threads"); const core_1 = require("@memlab/core"); const e2e_1 = require("@memlab/e2e"); const HeapViewUtils_1 = require("./HeapViewUtils"); /** * HeapViewController managers all the data associated with each * UI components in CLI and coordinates the events/interaction * among all UI components. */ class HeapViewController { constructor(heap, objectCategory) { this.heap = heap; this.currentHeapObjectsInfo = this.getFlattenHeapObjectsInfo(objectCategory); this.currentClusteredObjectsInfo = this.getFlattenClusteredObjectsInfo(objectCategory); this.currentHeapObject = (0, HeapViewUtils_1.getHeapObjectAt)(this.currentHeapObjectsInfo, 0); this.componentIdToDataMap = new Map(); this.componentIdToComponentMap = new Map(); this.scriptManager = new e2e_1.ScriptManager(); this.scriptManager.loadFromFiles(); } getFocusedComponent() { return this.focusedComponent; } getFlattenHeapObjectsInfo(objectCategory) { let ret = []; for (const category of objectCategory.keys()) { const nodes = objectCategory.get(category); ret = [...ret, ...nodes]; } return ret; } getFlattenClusteredObjectsInfo(objectCategory) { let ret = []; for (const category of objectCategory.keys()) { let nodes = objectCategory.get(category); if (this.shouldClusterCategory(category)) { nodes = this.clusterComponentDataItems(nodes); } ret = [...ret, ...nodes]; } return ret; } shouldClusterCategory(category) { return category === 'detached'; } clusterComponentDataItems(nodes) { const ret = []; const nodeIds = new Set(nodes .filter(node => node.heapObject) .map(node => { var _a, _b; return (_b = (_a = node.heapObject) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : -1; })); const clusters = core_1.analysis.clusterHeapObjects(nodeIds, this.heap); clusters.forEach(cluster => { var _a; const id = (_a = cluster.id) !== null && _a !== void 0 ? _a : -1; let node = null; node = this.heap.getNodeById(id); const details = new Map(); if (cluster.count) { details.set('# of clusters', `${cluster.count}`); } if (cluster.retainedSize) { details.set('aggregated retained size', `${core_1.utils.getReadableBytes(cluster.retainedSize)}`); } ret.push({ tag: 'Cluster', heapObject: node, details }); }); return ret; } getComponentDataById(componentId) { var _a; return (_a = this.componentIdToDataMap.get(componentId)) !== null && _a !== void 0 ? _a : null; } getContent(componentId) { const ret = []; const data = this.componentIdToDataMap.get(componentId); if (data) { for (const item of data.items) { ret.push(HeapViewUtils_1.ComponentDataItem.getTextForDisplay(item)); } } return ret; } setClusteredBox(component) { this.componentIdToComponentMap.set(component.id, component); this.clusteredBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getClusteredBoxData() { const data = new HeapViewUtils_1.ComponentData(); data.selectedIdx = 0; data.items = this.currentClusteredObjectsInfo; return data; } setReferrerBox(component) { this.componentIdToComponentMap.set(component.id, component); this.referrerBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getReferrerBoxData(node = this.selectedHeapObject) { const data = new HeapViewUtils_1.ComponentData(); node.forEachReferrer(ref => { var _a, _b; const tag = ref.fromNode.id === ((_b = (_a = node.pathEdge) === null || _a === void 0 ? void 0 : _a.fromNode) === null || _b === void 0 ? void 0 : _b.id) ? { tag: '<-' } : {}; data.items.push(Object.assign({ heapObject: ref.fromNode, referenceEdge: ref }, tag)); return { stop: false }; }); data.selectedIdx = data.items.length > 0 ? 0 : -1; return data; } setObjectBox(component) { this.componentIdToComponentMap.set(component.id, component); this.objectBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getObjectBoxData() { const data = new HeapViewUtils_1.ComponentData(); const index = this.currentHeapObjectsInfo.findIndex(item => { var _a; return ((_a = item.heapObject) === null || _a === void 0 ? void 0 : _a.id) === this.currentHeapObject.id; }); if (index >= 0) { data.selectedIdx = index; } else { data.selectedIdx = 0; this.currentHeapObjectsInfo.unshift({ tag: 'Chosen', heapObject: this.currentHeapObject, }); } data.items = this.currentHeapObjectsInfo; return data; } setReferenceBox(component) { this.componentIdToComponentMap.set(component.id, component); this.referenceBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getReferenceBoxData() { const data = new HeapViewUtils_1.ComponentData(); this.selectedHeapObject.forEachReference(ref => { data.items.push({ referrerEdge: ref, heapObject: ref.toNode }); return { stop: false }; }); data.items.sort((i1, i2) => { var _a, _b, _c, _d; return ((_b = (_a = i2.heapObject) === null || _a === void 0 ? void 0 : _a.retainedSize) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = i1.heapObject) === null || _c === void 0 ? void 0 : _c.retainedSize) !== null && _d !== void 0 ? _d : 0); }); data.selectedIdx = data.items.length > 0 ? 0 : -1; return data; } setObjectPropertyBox(component) { this.componentIdToComponentMap.set(component.id, component); this.objectPropertyBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getObjectPropertyData(options = {}) { var _a, _b; const data = new HeapViewUtils_1.ComponentData(); const node = this.selectedHeapObject; data.items.push({ stringContent: this.getKeyValuePairString('id', `@${node.id}`), }); data.items.push({ stringContent: this.getKeyValuePairString('name', node.name), }); data.items.push({ stringContent: this.getKeyValuePairString('type', node.type), }); data.items.push({ stringContent: this.getKeyValuePairString('self size', core_1.utils.getReadableBytes(node.self_size)), }); data.items.push({ stringContent: this.getKeyValuePairString('retained size', core_1.utils.getReadableBytes(node.retainedSize)), }); data.items.push({ stringContent: this.getKeyValuePairString('# of references', node.edge_count), }); data.items.push({ stringContent: this.getKeyValuePairString('# of referrers', node.referrers.length), }); if (node.dominatorNode) { data.items.push({ stringContent: 'dominator node' + chalk_1.default.grey(': '), heapObject: node.dominatorNode, }); } // if the node has associated location info const location = node.location; if (location) { data.items.push({ stringContent: this.getKeyValuePairString('script id', location.script_id), }); data.items.push({ stringContent: this.getKeyValuePairString('line', location.line), }); data.items.push({ stringContent: this.getKeyValuePairString('column', location.column), }); } // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; (_a = options.details) === null || _a === void 0 ? void 0 : _a.forEach((value, key) => data.items.push({ stringContent: self.getKeyValuePairString(key, value), })); // inject additional node information if (node.isString) { const stringNode = node.toStringNode(); if (stringNode) { const value = this.getReadableString(stringNode.stringValue); data.items.push({ stringContent: this.getKeyValuePairString('string value', value), }); } } if (node.type === 'number') { data.items.push({ stringContent: this.getKeyValuePairString('numeric value', (_b = core_1.utils.getNumberNodeValue(node)) !== null && _b !== void 0 ? _b : '<error>'), }); } if (node.type === 'closure') { const url = core_1.utils.getClosureSourceUrl(node); if (url) { data.items.push({ stringContent: this.getKeyValuePairString('code link', url), }); } const closureVars = this.getClosureNodeScopeVarEdges(node); closureVars.forEach(edge => data.items.push({ tag: chalk_1.default.grey('Outer Scope Var'), referrerEdge: edge, heapObject: edge.toNode, })); } data.selectedIdx = data.items.length > 0 ? 0 : -1; return data; } getReadableString(value) { return value.length > 300 ? value.substring(0, 300) + chalk_1.default.grey('...') : value; } getKeyValuePairString(key, value) { return key + chalk_1.default.grey(': ') + chalk_1.default.green(value); } setRetainerTraceBox(component) { this.componentIdToComponentMap.set(component.id, component); this.retainerTracePropertyBox = component; this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData()); } getRetainerTraceData() { var _a; const data = new HeapViewUtils_1.ComponentData(); const node = this.selectedHeapObject; let curNode = node; while (curNode && !core_1.utils.isRootNode(curNode)) { if (!curNode.pathEdge) { curNode = null; break; } data.items.unshift({ referrerEdge: curNode.pathEdge, heapObject: curNode }); curNode = (_a = curNode.pathEdge) === null || _a === void 0 ? void 0 : _a.fromNode; } if (curNode) { data.items.unshift({ heapObject: curNode }); } data.selectedIdx = data.items.length > 0 ? 0 : -1; return data; } getHeapObject(componentId, itemIndex) { const data = this.componentIdToDataMap.get(componentId); if (!data) { return null; } const item = data.items[itemIndex]; if (!item) { return null; } const heapObject = item.heapObject; if (!heapObject) { return null; } return heapObject; } displaySourceCode(componentId, itemIndex) { const node = this.getHeapObject(componentId, itemIndex); if (node && node.type === 'closure') { this.displayClosureInfo(node); } } // locate and display source code of a closure node // in a worker thread displayClosureInfo(node) { const url = core_1.utils.getClosureSourceUrl(node); if (!url) { return; } const closureVars = this.getClosureNodeScopeVarEdges(node).map(edge => `${edge.name_or_index}`); const workerData = { url, closureVars, }; new worker_threads_1.Worker(path_1.default.join(__dirname, '..', 'worker', 'LocateClosureSourceWorker.js'), { workerData }); } getClosureNodeScopeVarEdges(node) { const internalReferences = new Set(['map', 'scope_info', 'previous']); const contextNode = node.getReferenceNode('context', 'internal'); const ret = []; if (contextNode) { contextNode.forEachReference(edge => { const name = `${edge.name_or_index}`; if (!internalReferences.has(name)) { ret.push(edge); } }); } return ret; } setCurrentHeapObjectFromComponent(componentId, itemIndex, options = {}) { const heapObject = this.getHeapObject(componentId, itemIndex); if (heapObject) { this.setCurrentHeapObject(heapObject, options); } } setCurrentHeapObject(node, options = {}) { this.currentHeapObject = node; // set clustered box's data and content const clusteredBoxData = this.getClusteredBoxData(); this.componentIdToDataMap.set(this.clusteredBox.id, clusteredBoxData); this.clusteredBox.setContent(this.getContent(this.clusteredBox.id)); this.clusteredBox.selectIndex(clusteredBoxData.selectedIdx); // must set label here again so the additional label info // can render with updated component data this.clusteredBox.setLabel('Clustered Objects'); // set object box's data and content const objectBoxData = this.getObjectBoxData(); this.componentIdToDataMap.set(this.objectBox.id, objectBoxData); this.objectBox.setContent(this.getContent(this.objectBox.id)); this.objectBox.selectIndex(objectBoxData.selectedIdx); // must set label here again so the additional label info // can render with updated component data this.objectBox.setLabel('Objects'); this.setSelectedHeapObject(node); if (!options.skipFocus) { this.focusOnComponent(this.clusteredBox.id); } } focusOnComponent(componentId) { var _a; for (const component of this.componentIdToComponentMap.values()) { if (component.id === componentId) { component.focus(); this.focusedComponent = component; const data = this.componentIdToDataMap.get(componentId); const selectIndex = (_a = (data && data.selectedIdx)) !== null && _a !== void 0 ? _a : -1; this.setSelectedHeapObjectFromComponent(componentId, selectIndex); } else { component.loseFocus(); } } } setSelectedHeapObjectFromComponent(componentId, itemIndex) { const data = this.componentIdToDataMap.get(componentId); if (!data) { return; } data.selectedIdx = itemIndex; const item = data.items[itemIndex]; if (!item) { return; } const heapObject = item.heapObject; if (!heapObject) { return; } // if selecting in a specific box, do not update content in that box const noChangeInReferenceBox = componentId === this.referenceBox.id; const noChangeInReferrerBox = componentId === this.referrerBox.id; const noChangeInRetainerTraceBox = componentId === this.retainerTracePropertyBox.id; const noChangeInObjectPropertyBox = componentId === this.objectPropertyBox.id; this.setSelectedHeapObject(heapObject, { noChangeInReferenceBox, noChangeInReferrerBox, noChangeInRetainerTraceBox, noChangeInObjectPropertyBox, componentDataItem: item, }); } setSelectedHeapObject(node, options = {}) { var _a; this.selectedHeapObject = node; // set referrer box's data and content if (!options.noChangeInReferrerBox) { const data = this.getReferrerBoxData(); this.componentIdToDataMap.set(this.referrerBox.id, data); this.referrerBox.setContent(this.getContent(this.referrerBox.id)); this.referrerBox.selectIndex(data.selectedIdx); this.referrerBox.setLabel(`Referrers of @${node.id}`); } // set reference box's data and content if (!options.noChangeInReferenceBox) { const data = this.getReferenceBoxData(); this.componentIdToDataMap.set(this.referenceBox.id, data); this.referenceBox.setContent(this.getContent(this.referenceBox.id)); this.referenceBox.selectIndex(data.selectedIdx); this.referenceBox.setLabel(`References of @${node.id}`); } // set object property box's data and content if (!options.noChangeInObjectPropertyBox) { const propertyOption = ((_a = options === null || options === void 0 ? void 0 : options.componentDataItem) === null || _a === void 0 ? void 0 : _a.details) ? { details: options.componentDataItem.details } : {}; const data = this.getObjectPropertyData(propertyOption); this.componentIdToDataMap.set(this.objectPropertyBox.id, data); this.objectPropertyBox.setContent(this.getContent(this.objectPropertyBox.id)); this.objectPropertyBox.selectIndex(data.selectedIdx); this.objectPropertyBox.setLabel(`Object: @${node.id}`); } // set retainer trace box's data and content if (!options.noChangeInRetainerTraceBox) { const data = this.getRetainerTraceData(); this.componentIdToDataMap.set(this.retainerTracePropertyBox.id, data); this.retainerTracePropertyBox.setContent(this.getContent(this.retainerTracePropertyBox.id)); this.retainerTracePropertyBox.selectIndex(data.selectedIdx); this.retainerTracePropertyBox.setLabel(`Retainer Trace of @${node.id}`); } } } exports.default = HeapViewController;