├── worker ├── public │ ├── css │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ └── index.css │ ├── favicon.ico │ └── js │ │ ├── dashboard │ │ ├── component │ │ │ ├── tooltip.js │ │ │ ├── tree_dominators.js │ │ │ ├── tree_edges.js │ │ │ └── tree_retainers.js │ │ └── index.js │ │ └── library │ │ └── axios.min.js ├── snapshot.js └── view │ └── index.html ├── assets ├── devtoolx.gif └── devtoolx.png ├── .clang-format ├── devtoolx.js ├── .gitignore ├── src ├── devtoolx.cc └── memory │ ├── heapsnapshot.h │ ├── heapsnapshot.cc │ ├── edge.h │ ├── node.h │ ├── tarjan.h │ ├── parser.h │ ├── edge.cc │ ├── node.cc │ ├── snapshot_parser.h │ ├── tarjan.cc │ ├── parser.cc │ └── snapshot_parser.cc ├── test └── test.js ├── package.json ├── binding.gyp ├── README.md ├── util.js ├── bin └── devtoolx └── LICENSE /worker/public/css/fonts/element-icons.ttf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/devtoolx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noslate-project/devtoolx/HEAD/assets/devtoolx.gif -------------------------------------------------------------------------------- /assets/devtoolx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noslate-project/devtoolx/HEAD/assets/devtoolx.png -------------------------------------------------------------------------------- /worker/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noslate-project/devtoolx/HEAD/worker/public/favicon.ico -------------------------------------------------------------------------------- /worker/public/css/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noslate-project/devtoolx/HEAD/worker/public/css/fonts/element-icons.woff -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | BasedOnStyle: Google 3 | DerivePointerAlignment: false 4 | MaxEmptyLinesToKeep: 1 5 | -------------------------------------------------------------------------------- /devtoolx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const devtoolx = require('bindings')('devtoolx'); 3 | 4 | exports = module.exports = devtoolx; 5 | 6 | exports.snapshot = require('./worker/snapshot'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | logs 3 | run 4 | .DS_Store 5 | node_modules 6 | .idea 7 | build 8 | npm-debug.log 9 | package-lock.json 10 | index_prod.html 11 | coverage 12 | tmp 13 | dispatch.js 14 | -------------------------------------------------------------------------------- /src/devtoolx.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./memory/heapsnapshot.h" 3 | 4 | void Init(v8::Local exports) { 5 | HeapSnapshot::Init(exports); 6 | } 7 | 8 | NODE_MODULE(devtoolx, Init) -------------------------------------------------------------------------------- /src/memory/heapsnapshot.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #ifndef _HEAPSNAPSHOT_H_ 6 | #define _HEAPSNAPSHOT_H_ 7 | 8 | class HeapSnapshot { 9 | public: 10 | static void Init(v8::Local exports); 11 | }; 12 | 13 | #endif -------------------------------------------------------------------------------- /src/memory/heapsnapshot.cc: -------------------------------------------------------------------------------- 1 | #include "parser.h" 2 | #include "heapsnapshot.h" 3 | 4 | using v8::Local; 5 | using v8::Object; 6 | void GetName(const Nan::FunctionCallbackInfo &info) 7 | { 8 | Local name = Nan::New("HeapTools").ToLocalChecked(); 9 | info.GetReturnValue().Set(name); 10 | } 11 | 12 | void HeapSnapshot::Init(Local exports) 13 | { 14 | Local heapTools = Nan::New(); 15 | Nan::Set(heapTools, Nan::New("getName").ToLocalChecked(), Nan::GetFunction(Nan::New(GetName)).ToLocalChecked()); 16 | parser::Parser::Init(heapTools); 17 | Nan::Set(exports, Nan::New("heapTools").ToLocalChecked(), heapTools); 18 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const devtoolx = require('..'); 4 | const heapTools = devtoolx.heapTools; 5 | const V8Parser = heapTools.V8Parser; 6 | const snapshot = path.join(__dirname, './resource/test.heapsnapshot'); 7 | 8 | function getNode() { 9 | let parser = new V8Parser(snapshot); 10 | parser.parse({ mode: 'search' }); 11 | 12 | // let node = parser.getNodeByOrdinalId([1, 2, 3], 0, 2) 13 | let node = parser.getNodeByOrdinalId([1, 2, 3], 0, 2, { type: 'retainers' }) 14 | // let node = parser.getNodeByOrdinalId([1, 2, 3], 0, 2) 15 | 16 | console.log(node); 17 | } 18 | 19 | 20 | // getNode(); 21 | 22 | console.time('cost'); 23 | require('..').snapshot(snapshot).listen(3001, () => console.timeEnd('cost')); 24 | -------------------------------------------------------------------------------- /src/memory/edge.h: -------------------------------------------------------------------------------- 1 | #include "../library/json.hpp" 2 | 3 | #ifndef _SNAPSHOT_EDGE_H_ 4 | #define _SNAPSHOT_EDGE_H_ 5 | 6 | namespace snapshot_parser { 7 | class SnapshotParser; 8 | } 9 | 10 | namespace snapshot_edge { 11 | using nlohmann::json; 12 | 13 | enum EdgeTypes { 14 | KCONTEXTVARIABLE, 15 | KELEMENT, 16 | KPROPERTY, 17 | KERNAL, 18 | KHIDDEN, 19 | KSHORTCUT, 20 | KWEAK 21 | }; 22 | 23 | class Edge { 24 | public: 25 | explicit Edge(snapshot_parser::SnapshotParser* parser); 26 | ~Edge(); 27 | std::string GetType(int id, bool source); 28 | int GetTypeForInt(int id, bool source); 29 | std::string GetNameOrIndex(int id, bool source); 30 | int GetNameOrIndexForInt(int id, bool source); 31 | int GetTargetNode(int id, bool source); 32 | 33 | private: 34 | snapshot_parser::SnapshotParser* parser_; 35 | }; 36 | } // namespace snapshot_edge 37 | 38 | #endif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtoolx", 3 | "version": "1.0.0", 4 | "description": "devtool box", 5 | "main": "devtoolx.js", 6 | "bin": { 7 | "devtoolx": "bin/devtoolx" 8 | }, 9 | "files": [ 10 | "bin", 11 | "src", 12 | "worker", 13 | "binding.gyp", 14 | "devtoolx.js", 15 | "LICENSE", 16 | "README.md", 17 | "util.js" 18 | ], 19 | "scripts": { 20 | "build": "node-gyp rebuild", 21 | "lint": "clang-format --version && clang-format -i --glob=\"src/**/!(json)[.hpp]\"" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/hyj1991/devtoolx.git" 26 | }, 27 | "keywords": [ 28 | "devtools" 29 | ], 30 | "author": "https://github.com/hyj1991", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/hyj1991/devtoolx/issues" 34 | }, 35 | "homepage": "https://github.com/hyj1991/devtoolx#readme", 36 | "dependencies": { 37 | "bindings": "^1.5.0", 38 | "body-parser": "^1.20.1", 39 | "compression": "^1.7.4", 40 | "ejs": "^3.1.8", 41 | "express": "^4.18.2", 42 | "nan": "^2.17.0", 43 | "serve-favicon": "^2.5.0" 44 | } 45 | } -------------------------------------------------------------------------------- /src/memory/node.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../library/json.hpp" 4 | 5 | #ifndef _SNAPSHOT_NODE_H_ 6 | #define _SNAPSHOT_NODE_H_ 7 | 8 | namespace snapshot_parser { 9 | class SnapshotParser; 10 | } 11 | 12 | namespace snapshot_node { 13 | using nlohmann::json; 14 | 15 | enum NodeTypes { 16 | KHIDDEN, 17 | KARRAY, 18 | KSTRING, 19 | KOBJECT, 20 | KCODE, 21 | KCLOSURE, 22 | KREGEXP, 23 | KNUMBER, 24 | KNATIVE, 25 | KSYNTHETIC, 26 | KCONCATENATED_STRING, 27 | KSLICED_STRING 28 | }; 29 | 30 | typedef std::unordered_map LazyStringMap; 31 | 32 | class Node { 33 | public: 34 | explicit Node(snapshot_parser::SnapshotParser* parser); 35 | ~Node(); 36 | bool CheckOrdinalId(int id); 37 | int GetNodeId(int source); 38 | long GetAddress(int id); 39 | std::string GetType(int id); 40 | int GetTypeForInt(int id); 41 | std::string GetName(int id); 42 | int GetNameForInt(int id); 43 | int* GetEdges(int id); 44 | int GetEdgeCount(int id); 45 | int GetSelfSize(int id); 46 | std::string GetConsStringName(int id); 47 | 48 | private: 49 | snapshot_parser::SnapshotParser* parser_; 50 | LazyStringMap lazy_string_map_; 51 | int first_int_ = -1; 52 | int second_int_ = -1; 53 | }; 54 | } // namespace snapshot_node 55 | 56 | #endif -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "devtoolx", 5 | "sources": [ 6 | "src/devtoolx.cc", 7 | "src/library/json.hpp", 8 | "src/memory/heapsnapshot.cc", 9 | "src/memory/node.cc", 10 | "src/memory/edge.cc", 11 | "src/memory/snapshot_parser.cc", 12 | "src/memory/parser.cc", 13 | "src/memory/tarjan.cc" 14 | ], 15 | "include_dirs": [ 16 | "src", 17 | " 2 | #include 3 | 4 | #ifndef __TARJAN_H_ 5 | #define __TARJAN_H_ 6 | 7 | namespace tarjan { 8 | 9 | const int ERROR_VALUE = -999; 10 | 11 | typedef std::unordered_map> BoundListMap; 12 | 13 | typedef struct TarjanBoundList { 14 | BoundListMap inbounds; 15 | BoundListMap outbounds; 16 | int root = 0; 17 | int count; 18 | ~TarjanBoundList() { 19 | this->inbounds.clear(); 20 | this->outbounds.clear(); 21 | this->root = 0; 22 | this->count = 0; 23 | } 24 | } tarjan_bound_list_t; 25 | 26 | class TarJan { 27 | public: 28 | explicit TarJan(tarjan_bound_list_t* data); 29 | ~TarJan(); 30 | int length; 31 | int root; 32 | // middle variables 33 | int* dfs; 34 | int* vertex; 35 | int* semi; 36 | int* parent; 37 | BoundListMap bucket; 38 | int* dom; 39 | int* ancestor; 40 | int* label; 41 | int* size; 42 | int* child; 43 | BoundListMap inbounds; 44 | BoundListMap outbounds; 45 | // calculate 46 | void Compute(); 47 | void ClearMiddleVariables(); 48 | // results 49 | BoundListMap dominators; 50 | int* idominator; 51 | 52 | private: 53 | void Enumerate_(); 54 | void Build_(); 55 | int Evaluate_(int v); 56 | void Compress_(int v); 57 | void Link_(int v, int w); 58 | }; 59 | } // namespace tarjan 60 | 61 | #endif -------------------------------------------------------------------------------- /worker/public/css/index.css: -------------------------------------------------------------------------------- 1 | .content { 2 | font-family: Menlo; 3 | } 4 | 5 | .content strong { 6 | font-size: 12px; 7 | font-weight: 400; 8 | } 9 | 10 | .content small { 11 | font-size: 10px; 12 | font-weight: 300; 13 | } 14 | 15 | .disabled { 16 | opacity: 0.5 17 | } 18 | 19 | .property { 20 | color: rgb(136, 19, 145) 21 | } 22 | 23 | .context { 24 | color: rgb(28, 0, 207) 25 | } 26 | 27 | .node-name { 28 | color: rgb(48, 57, 66); 29 | } 30 | 31 | .string { 32 | color: #4caf50; 33 | } 34 | 35 | .closure { 36 | font-style: italic; 37 | } 38 | 39 | .node-gcroot { 40 | background-color: #c0eafd; 41 | } 42 | 43 | .node-js-reference { 44 | background-color: rgb(255, 255, 200); 45 | } 46 | 47 | .address { 48 | color: grey 49 | } 50 | 51 | .additional { 52 | color: grey 53 | } 54 | 55 | .more-button { 56 | font-family: Menlo; 57 | padding: 3px 8px; 58 | color: #673ab7; 59 | margin-top:5px; 60 | margin-left: -15px; 61 | margin-bottom: 1px; 62 | } 63 | 64 | .statistics{ 65 | position: relative; 66 | font-family: Menlo; 67 | margin-left: 20px; 68 | margin-bottom: 0px; 69 | margin-top: 23px; 70 | font-size: 15px; 71 | z-index:999; 72 | } 73 | 74 | .statistics-value{ 75 | color: #d20d0d; 76 | } 77 | 78 | .tooltip-menu { 79 | font-size: 10px; 80 | margin: 5px 10px 5px 10px; 81 | } 82 | 83 | .customize-start { 84 | height:21px; 85 | outline:none; 86 | font-family: Menlo; 87 | padding: 3px 8px; 88 | margin-right: 15px; 89 | margin-left: 10px; 90 | width: 100px; 91 | border-radius: 3px; 92 | border: 1px solid #9a70e4; 93 | color: #673ab7; 94 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevTools X 2 | 3 | [![npm version](https://badge.fury.io/js/devtoolx.svg)](https://badge.fury.io/js/devtoolx) 4 | [![Package Quality](http://npm.packagequality.com/shield/devtoolx.svg)](http://packagequality.com/#?package=devtoolx) 5 | [![npm](https://img.shields.io/npm/dt/devtoolx.svg)](https://www.npmjs.com/package/devtoolx) 6 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE) 7 | 8 | JavaScript Developer Toolbox 9 | 10 | * Aid in the analysis of memory 11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm install devtoolx -g 16 | ``` 17 | 18 | ## Usage 19 | 20 | ### Search node by address or ordinal id 21 | 22 | ```bash 23 | devtoolx -s [-p ] 24 | ``` 25 | 26 | example: 27 | 28 | ![devtoox.gif](https://raw.githubusercontent.com/hyj1991/devtoolx/master/assets/devtoolx.gif) 29 | 30 | ### Color 31 | 32 | * light bule: means gc root 33 | * dark red: means this object's retainedSize / totalSize > 0.2 34 | 35 | ### Help 36 | 37 | ```bash 38 | devtoolx -h 39 | devtoolx --help 40 | ``` 41 | 42 | ## License 43 | 44 | [MIT License](LICENSE) 45 | 46 | Copyright (c) 2023 noslate project 47 | -------------------------------------------------------------------------------- /src/memory/parser.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "../library/json.hpp" 9 | #include "snapshot_parser.h" 10 | 11 | #ifndef _PARSER_H 12 | #define _PARSER_H 13 | 14 | namespace parser { 15 | using nlohmann::json; 16 | 17 | enum GetNodeTypes { KALL, KEDGES, KRETAINERS }; 18 | 19 | class Parser : public node::ObjectWrap { 20 | public: 21 | static void Init(v8::Local exports); 22 | static void NewInstance(const Nan::FunctionCallbackInfo& info); 23 | 24 | private: 25 | explicit Parser(char* filename, int filelength); 26 | ~Parser(); 27 | v8::Local GetNodeById_(int id, int current, int limit, 28 | GetNodeTypes type); 29 | v8::Local SetNormalInfo_(int id); 30 | static void New(const Nan::FunctionCallbackInfo& info); 31 | static void GetFileName(const Nan::FunctionCallbackInfo& info); 32 | static void Parse(const Nan::FunctionCallbackInfo& info); 33 | static void GetNodeId(const Nan::FunctionCallbackInfo& info); 34 | static void GetNodeByOrdinalId( 35 | const Nan::FunctionCallbackInfo& info); 36 | static void GetNodeByAddress( 37 | const Nan::FunctionCallbackInfo& info); 38 | static void GetNodeIdByAddress( 39 | const Nan::FunctionCallbackInfo& info); 40 | static void GetStatistics(const Nan::FunctionCallbackInfo& info); 41 | static void GetDominatorByIDom( 42 | const Nan::FunctionCallbackInfo& info); 43 | static void GetChildRepeat(const Nan::FunctionCallbackInfo& info); 44 | static void GetConsStringName( 45 | const Nan::FunctionCallbackInfo& info); 46 | static Nan::Persistent constructor; 47 | // snapshot info 48 | int filelength_; 49 | char* filename_; 50 | snapshot_parser::SnapshotParser* snapshot_parser; 51 | }; 52 | } // namespace parser 53 | 54 | #endif -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const logger = exports.logger = { 3 | infoConsole(str) { 4 | return `\x1b[35;1m${str}\x1b[0m`; 5 | }, 6 | 7 | infoConsole2(str) { 8 | return `\x1b[32;1m${str}\x1b[0m`; 9 | }, 10 | 11 | debugConsole(str) { 12 | return `\x1b[36;1m${str}\x1b[0m`; 13 | }, 14 | 15 | errorConsole(str) { 16 | return `\x1b[31;1m${str}\x1b[0m`; 17 | }, 18 | 19 | warnConsole(str) { 20 | return `\x1b[33;1m${str}\x1b[0m`; 21 | }, 22 | 23 | lineConsole(str) { 24 | return `\x1b[4;1m${str}\x1b[0m`; 25 | } 26 | }; 27 | 28 | exports.helpText = ` 29 | Usage: devtoolx [CMD]... [ARGS] 30 | 31 | ${logger.infoConsole(`-v --version`)} show devtoolx version 32 | ${logger.infoConsole(`version`)} 33 | 34 | ${logger.infoConsole(`-h --help`)} show devtoolx usage 35 | ${logger.infoConsole(`help`)} 36 | 37 | ${logger.infoConsole(`-s [-p ]`)} analysis js heapsnapshot, start web sever 38 | with default port 3000, change port by "-p " 39 | 40 | ${logger.infoConsole(`--no-open`)} don't auto open browser when analysis completed 41 | `; 42 | 43 | exports.formatTime = function (ts) { 44 | ts = !isNaN(ts) && ts || 0; 45 | let str = ''; 46 | if (ts < 1e3) { 47 | str = `${ts.toFixed(2)} ms`; 48 | } else if (ts < 1e3 * 60) { 49 | str = `${(ts / 1e3).toFixed(2)} s`; 50 | } else if (ts < 1e3 * 60 * 60) { 51 | str = `${(ts / (1e3 * 60)).toFixed(2)} min`; 52 | } else if (ts < 1e3 * 60 * 60 * 60) { 53 | str = `${(ts / (1e3 * 60 * 60)).toFixed(2)} h`; 54 | } else { 55 | str = `${ts.toFixed(2)} ms`; 56 | } 57 | 58 | return str; 59 | } 60 | 61 | exports.formatSize = function (size) { 62 | let str = ''; 63 | if (size / 1024 < 1) { 64 | str = `${(size).toFixed(2)} Bytes`; 65 | } else if (size / 1024 / 1024 < 1) { 66 | str = `${(size / 1024).toFixed(2)} KB`; 67 | } else if (size / 1024 / 1024 / 1024 < 1) { 68 | str = `${(size / 1024 / 1024).toFixed(2)} MB`; 69 | } else { 70 | str = `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`; 71 | } 72 | return str; 73 | } -------------------------------------------------------------------------------- /worker/public/js/dashboard/component/tooltip.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // var showRepeatSource = 'show repeat'; 3 | var showRepeatSource = ''; 4 | var ToolTip = { 5 | template: '#tooltip-template', 6 | props: ['tooltipStyle', 'tooltipData', 'getNode', 'formatSize', 'type'], 7 | data() { 8 | return { repeatLoading: false, repeatMsg: showRepeatSource } 9 | }, 10 | methods: { 11 | formatTooltip(name, count, sizes, index) { 12 | return `${name} ` 13 | + `repeat: ${count}, ` 14 | + `total sizes: ${this.formatSize(sizes)}` 15 | // + ((index || Number(index) === 0) && `, index: ${index}` || ''); 16 | }, 17 | getRepeat() { 18 | var vm = this; 19 | // if (!vm.repeatMsg.includes(showRepeatSource)) return; 20 | var tooltipData = vm.tooltipData; 21 | if (tooltipData.parentOrdinalId !== -1 && tooltipData.childOrdinalId !== -1) { 22 | vm.repeatLoading = true; 23 | vm.getNode(`/repeat/parend_id/${tooltipData.parentOrdinalId}/child_id/${tooltipData.childOrdinalId}?type=${tooltipData.type}`) 24 | .then(data => { 25 | if (data.count === 0) { 26 | data.count = 1; 27 | data.total_retained_size = tooltipData.childSize; 28 | } 29 | vm.repeatMsg = vm.formatTooltip(tooltipData.childName, data.count, data.total_retained_size, tooltipData.index); 30 | }) 31 | .catch(err => vm.$message.error(err.message || 'Server Inner Error')) 32 | .then(() => vm.repeatLoading = false) 33 | } else 34 | vm.repeatMsg = vm.formatTooltip(tooltipData.childName, 1, tooltipData.childSize); 35 | }, 36 | noop() { 37 | return false; 38 | } 39 | }, 40 | computed: { 41 | showRepeat() { 42 | return this.repeatMsg; 43 | } 44 | }, 45 | watch: { 46 | tooltipStyle() { 47 | this.getRepeat(); 48 | // this.repeatMsg = showRepeatSource; 49 | } 50 | } 51 | } 52 | if (typeof Devtoolx === 'undefined') window.Devtoolx = {}; 53 | Devtoolx.ToolTip = ToolTip; 54 | })(); -------------------------------------------------------------------------------- /bin/devtoolx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const util = require('../util'); 6 | const exec = require('child_process').exec; 7 | const argv = process.argv.slice(2); 8 | 9 | switch (argv[0]) { 10 | case '-v': 11 | case '--version': 12 | case 'version': 13 | console.log(require('../package.json').version); 14 | process.exit(0); 15 | break; 16 | case '-h': 17 | case '--help': 18 | case 'help': 19 | console.log(util.helpText); 20 | process.exit(0); 21 | break; 22 | case '-s': 23 | case '--snapshot': 24 | let file = ''; 25 | if (!argv[1]) { 26 | console.log(util.logger.errorConsole(`Error: need heapsnapshot filename`)); 27 | process.exit(0); 28 | break; 29 | } 30 | file = path.resolve(argv[1]); 31 | if (!fs.existsSync(file)) { 32 | console.log(util.logger.errorConsole(`Error: no such file or directory, fs.existsSync('${file}')`)); 33 | process.exit(0); 34 | break; 35 | } 36 | let stat = fs.statSync(file); 37 | let port = 3000; 38 | if (argv[2] === '-p') { 39 | isNaN(argv[3]) && console.log(util.logger.warnConsole(`Warn: port is not a number, using default port 3000`)); 40 | !isNaN(argv[3]) && (port = argv[3]); 41 | } 42 | let method = argv.reduce((pre, arg) => { 43 | if (~arg.indexOf('--method=')) { 44 | pre = arg.split('=')[1]; 45 | } 46 | return pre; 47 | }, ''); 48 | let start = Date.now(); 49 | let startText = `File ${path.basename(file)} (${util.formatSize(stat.size)}), waiting`; 50 | process.stdout.write(util.logger.infoConsole2(`${startText}...\n`)); 51 | require('..').snapshot(file, { method }).listen(port, () => { 52 | process.stdout.write(util.logger.infoConsole2(`\nCompleted, cost ` 53 | + `${util.logger.warnConsole(`${util.formatTime(Date.now() - start)}`)}` 54 | + `${util.logger.infoConsole2(`, open `)}` 55 | + `${util.logger.infoConsole(util.logger.lineConsole(`http://localhost:${port}`))} ` + 56 | `${util.logger.infoConsole2(`for more details.`)}\n`)); 57 | if (argv.every(arg => arg !== '--no-open')) { 58 | exec(`"open" http://localhost:${port}`); 59 | } 60 | }); 61 | break; 62 | default: 63 | console.log(util.helpText); 64 | process.exit(0); 65 | break; 66 | 67 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 noslate-project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | The project was externally maintained and licensed as follows: 24 | """ 25 | The MIT License (MIT) 26 | 27 | Copyright (c) 2018-2023 hyj1991 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | """ 47 | -------------------------------------------------------------------------------- /src/memory/edge.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "node.h" 3 | #include "snapshot_parser.h" 4 | 5 | namespace snapshot_edge { 6 | Edge::Edge(snapshot_parser::SnapshotParser* parser): parser_(parser) {} 7 | 8 | std::string Edge::GetType(int id, bool source) { 9 | int edge_field_length = parser_->edge_field_length; 10 | if(source && id % edge_field_length != 0) { 11 | Nan::ThrowTypeError(Nan::New("edge source id is wrong!").ToLocalChecked()); 12 | return "error"; 13 | } 14 | int edge_source_index = source ? id : id * edge_field_length; 15 | if(edge_source_index / edge_field_length >= parser_->edge_count) { 16 | Nan::ThrowTypeError(Nan::New("edge id larger than edges.length!").ToLocalChecked()); 17 | return "error"; 18 | } 19 | int type = static_cast(parser_->edges[edge_source_index + parser_->edge_type_offset]); 20 | json types = parser_->edge_types; 21 | return types[type]; 22 | } 23 | 24 | int Edge::GetTypeForInt(int id, bool source) { 25 | int edge_field_length = parser_->edge_field_length; 26 | if(source && id % edge_field_length != 0) { 27 | Nan::ThrowTypeError(Nan::New("edge source id is wrong!").ToLocalChecked()); 28 | return -1; 29 | } 30 | int edge_source_index = source ? id : id * edge_field_length; 31 | if(edge_source_index / edge_field_length >= parser_->edge_count) { 32 | Nan::ThrowTypeError(Nan::New("edge id larger than edges.length!").ToLocalChecked()); 33 | return -1; 34 | } 35 | return static_cast(parser_->edges[edge_source_index + parser_->edge_type_offset]); 36 | } 37 | 38 | std::string Edge::GetNameOrIndex(int id, bool source) { 39 | int edge_field_length = parser_->edge_field_length; 40 | if(source && id % edge_field_length != 0) { 41 | Nan::ThrowTypeError(Nan::New("edge source id is wrong!").ToLocalChecked()); 42 | return "error"; 43 | } 44 | int edge_source_index = source ? id : id * edge_field_length; 45 | if(edge_source_index / edge_field_length >= parser_->edge_count) { 46 | Nan::ThrowTypeError(Nan::New("edge id larger than edges.length!").ToLocalChecked()); 47 | return "error"; 48 | } 49 | int type = static_cast(parser_->edges[edge_source_index + parser_->edge_type_offset]); 50 | int name_or_index = static_cast(parser_->edges[edge_source_index + parser_->edge_name_or_index_offset]); 51 | if(type == KELEMENT) { 52 | return "[" + std::to_string(name_or_index) + "]"; 53 | } else if(type == KHIDDEN) { 54 | return std::to_string(name_or_index); 55 | } else { 56 | return parser_->strings[name_or_index]; 57 | }; 58 | } 59 | 60 | int Edge::GetNameOrIndexForInt(int id, bool source) { 61 | int edge_field_length = parser_->edge_field_length; 62 | if(source && id % edge_field_length != 0) { 63 | Nan::ThrowTypeError(Nan::New("edge source id is wrong!").ToLocalChecked()); 64 | return -1; 65 | } 66 | int edge_source_index = source ? id : id * edge_field_length; 67 | if(edge_source_index / edge_field_length >= parser_->edge_count) { 68 | Nan::ThrowTypeError(Nan::New("edge id larger than edges.length!").ToLocalChecked()); 69 | return -1; 70 | } 71 | return static_cast(parser_->edges[edge_source_index + parser_->edge_name_or_index_offset]); 72 | } 73 | 74 | int Edge::GetTargetNode(int id, bool source) { 75 | int edge_field_length = parser_->edge_field_length; 76 | if(source && id % edge_field_length != 0) { 77 | Nan::ThrowTypeError(Nan::New("edge source id is wrong!").ToLocalChecked()); 78 | return -1; 79 | } 80 | int edge_source_index = source ? id : id * edge_field_length; 81 | if(edge_source_index / edge_field_length >= parser_->edge_count) { 82 | Nan::ThrowTypeError(Nan::New("edge id larger than edges.length!").ToLocalChecked()); 83 | return -1; 84 | } 85 | int target_node_source_id = static_cast(parser_->edges[edge_source_index + parser_->edge_to_node_offset]); 86 | int node_field_length = parser_->node_field_length; 87 | if(target_node_source_id % node_field_length != 0) { 88 | Nan::ThrowTypeError(Nan::New("target node source id is wrong!").ToLocalChecked()); 89 | return -1; 90 | } 91 | return static_cast(target_node_source_id / node_field_length); 92 | } 93 | } -------------------------------------------------------------------------------- /worker/snapshot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const express = require('express'); 3 | const path = require('path'); 4 | const bodyParser = require('body-parser'); 5 | const serverFavicon = require('serve-favicon'); 6 | const compression = require('compression'); 7 | const devtoolx = require('..'); 8 | const logger = require('../util').logger; 9 | const formatTime = require('../util').formatTime; 10 | const heapTools = devtoolx.heapTools; 11 | const V8Parser = heapTools.V8Parser; 12 | 13 | function checkOptions(options) { 14 | let method = options.method; 15 | if (method && (method !== 'tarjan') && method !== 'data_iteration') { 16 | throw new Error(`not support method ${method}!`); 17 | } 18 | } 19 | 20 | function getMethod(method) { 21 | let name = ''; 22 | switch (method) { 23 | case 'tarjan': 24 | name = 'Lengauer Tarjan'; 25 | break; 26 | case 'data_iteration': 27 | name = 'Data Itreation'; 28 | break; 29 | default: 30 | name = 'Data Itreation'; 31 | break; 32 | } 33 | return name; 34 | } 35 | 36 | function createServer(snapshot, options) { 37 | checkOptions(options); 38 | let parser = new V8Parser(snapshot); 39 | process.stdout.write(logger.debugConsole(` \n`)); 40 | parser.parse(Object.assign({ mode: 'search' }, options), function (progress) { 41 | let message = ''; 42 | if (progress.status === 'start') { 43 | message = ` - ${progress.type}...`; 44 | } 45 | if (progress.status === 'end') { 46 | message = `\x1b[3D\x1b[K ${logger.warnConsole(`${formatTime(progress.cost)}`)}\n`; 47 | } 48 | process.stdout.write(logger.debugConsole(`${message}`)); 49 | }); 50 | let app = express(); 51 | app.set('views', path.join(__dirname, './view')); 52 | app.engine('.html', require('ejs').renderFile); 53 | app.set('view engine', 'html'); 54 | app.use(compression()); 55 | app.use(express.static(path.join(__dirname, './public'))); 56 | app.use(serverFavicon(path.join(__dirname, './public/favicon.ico'))); 57 | app.use(bodyParser.json({ limit: '50mb' })); 58 | app.use(bodyParser.urlencoded({ extended: false, limit: '10mb' })); 59 | 60 | app.get('/', (req, res) => { 61 | res.render('index', { 62 | msg: 'hello' 63 | }); 64 | }); 65 | 66 | app.get('/address/:address', (req, res) => { 67 | try { 68 | let current = !isNaN(req.query.current) && parseInt(req.query.current); 69 | let limit = !isNaN(req.query.limit) && parseInt(req.query.limit); 70 | let node = parser.getNodeByAddress(req.params.address, current, limit); 71 | res.json({ ok: true, data: node }); 72 | } catch (e) { 73 | res.json({ ok: false, message: e.message }); 74 | } 75 | }); 76 | 77 | app.get('/ordinal/:ordinal', (req, res) => { 78 | try { 79 | let ids = req.params.ordinal.split(',').map(id => parseInt(id)); 80 | let current = !isNaN(req.query.current) && parseInt(req.query.current); 81 | let limit = !isNaN(req.query.limit) && parseInt(req.query.limit); 82 | let type = req.query.type; 83 | let node = parser.getNodeByOrdinalId(ids, current, limit, { type }); 84 | res.json({ ok: true, data: node }); 85 | } catch (e) { 86 | res.json({ ok: false, message: e.message }); 87 | } 88 | }); 89 | 90 | app.get('/statistics', (req, res) => { 91 | try { 92 | let statistics = parser.getStatistics(); 93 | res.json({ ok: true, data: statistics }); 94 | } catch (e) { 95 | res.json({ ok: false, message: e.message }); 96 | } 97 | }); 98 | 99 | app.get('/dominates/:id', (req, res) => { 100 | try { 101 | let id = parseInt(req.params.id); 102 | let current = !isNaN(req.query.current) && parseInt(req.query.current); 103 | let limit = !isNaN(req.query.limit) && parseInt(req.query.limit); 104 | let dominates = parser.getDominatorByIDom(id, current, limit); 105 | res.json({ ok: true, data: dominates }); 106 | } catch (e) { 107 | res.json({ ok: false, message: e.message }); 108 | } 109 | }); 110 | 111 | app.get('/repeat/parend_id/:parend_id/child_id/:child_id', (req, res) => { 112 | try { 113 | let parendId = !isNaN(req.params.parend_id) && parseInt(req.params.parend_id); 114 | let childId = !isNaN(req.params.child_id) && parseInt(req.params.child_id); 115 | let repeat = parser.getChildRepeat(parendId, childId, req.query.type); 116 | res.json({ ok: true, data: repeat }); 117 | } catch (e) { 118 | res.json({ ok: false, message: e.message }); 119 | } 120 | }); 121 | 122 | return app; 123 | } 124 | 125 | module.exports = createServer; -------------------------------------------------------------------------------- /src/memory/node.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "node.h" 3 | #include "snapshot_parser.h" 4 | 5 | namespace snapshot_node { 6 | Node::Node(snapshot_parser::SnapshotParser* parser): parser_(parser) {} 7 | 8 | std::string Node::GetConsStringName(int id) { 9 | json& strings = parser_->strings; 10 | if(lazy_string_map_.count(id) != 0) 11 | return lazy_string_map_.at(id); 12 | // max length 13 | int* node_stack = new int[128](); 14 | int length = 1; 15 | node_stack[0] = id; 16 | std::string name = ""; 17 | while(length > 0 && name.length() < 256) { 18 | int index = node_stack[--length]; 19 | if(GetTypeForInt(index) != KCONCATENATED_STRING) { 20 | std::string cons = strings[static_cast(parser_->nodes[index * parser_->node_field_length + parser_->node_name_offset])]; 21 | name += cons; 22 | continue; 23 | } 24 | int* edges = GetEdges(index); 25 | int edge_count = GetEdgeCount(index); 26 | int first_node_index = -1; 27 | int second_node_index = -1; 28 | for(int i = 0; i < edge_count; i++) { 29 | int edge = edges[i]; 30 | int edge_type = parser_->edge_util->GetTypeForInt(edge, true); 31 | if(edge_type == snapshot_edge::KERNAL) { 32 | if(first_int_ != -1 && second_int_ != -1) { 33 | int edge_name = parser_->edge_util->GetNameOrIndexForInt(edge, true); 34 | if(edge_name == first_int_) 35 | first_node_index = parser_->edge_util->GetTargetNode(edge, true); 36 | if(edge_name == second_int_) 37 | second_node_index = parser_->edge_util->GetTargetNode(edge, true); 38 | } else { 39 | std::string edge_name = parser_->edge_util->GetNameOrIndex(edge, true); 40 | if (edge_name == "first") { 41 | first_node_index = parser_->edge_util->GetTargetNode(edge, true); 42 | first_int_ = parser_->edge_util->GetNameOrIndexForInt(edge, true); 43 | } 44 | if (edge_name == "second") { 45 | second_node_index = parser_->edge_util->GetTargetNode(edge, true); 46 | second_int_ = parser_->edge_util->GetNameOrIndexForInt(edge, true); 47 | } 48 | } 49 | } 50 | } 51 | delete[] edges; 52 | if(second_node_index != -1) 53 | node_stack[length++] = second_node_index; 54 | if(first_node_index != -1) 55 | node_stack[length++] = first_node_index; 56 | } 57 | delete[] node_stack; 58 | lazy_string_map_.insert(LazyStringMap::value_type(id, name)); 59 | return name; 60 | } 61 | 62 | bool Node::CheckOrdinalId(int oridnal) { 63 | return oridnal < parser_->node_count; 64 | } 65 | 66 | int Node::GetNodeId(int source) { 67 | int node_field_length = parser_->node_field_length; 68 | if(source % node_field_length != 0) { 69 | Nan::ThrowTypeError(Nan::New("node source id is wrong!").ToLocalChecked()); 70 | return -1; 71 | } 72 | return static_cast(source / node_field_length); 73 | } 74 | 75 | long Node::GetAddress(int id) { 76 | return static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_address_offset]); 77 | } 78 | 79 | std::string Node::GetType(int id) { 80 | int type = static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_type_offset]); 81 | json types = parser_->node_types; 82 | // with type "undefined", total 13 83 | if (type > static_cast(types.size() - 1)) { 84 | return "undefined"; 85 | } 86 | return types[type]; 87 | } 88 | 89 | int Node::GetTypeForInt(int id) { 90 | return static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_type_offset]); 91 | } 92 | 93 | std::string Node::GetName(int id) { 94 | return parser_->strings[static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_name_offset])]; 95 | } 96 | 97 | int Node::GetNameForInt(int id) { 98 | return static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_name_offset]); 99 | } 100 | 101 | int* Node::GetEdges(int id) { 102 | int first_edge_index = parser_->first_edge_indexes[id]; 103 | int next_first_edge_index = 0; 104 | if(id + 1 >= parser_->node_count) { 105 | next_first_edge_index = static_cast(parser_->edges.size()); 106 | } else { 107 | next_first_edge_index = parser_->first_edge_indexes[id + 1]; 108 | } 109 | int* edges = new int[(next_first_edge_index - first_edge_index) / parser_->edge_field_length]; 110 | for (int i = first_edge_index; i < next_first_edge_index; i += parser_->edge_field_length) { 111 | edges[(i - first_edge_index) / parser_->edge_field_length] = i; 112 | } 113 | return edges; 114 | } 115 | 116 | int Node::GetEdgeCount(int id) { 117 | // edge count may not be larger than 2^31 118 | return static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_edge_count_offset]); 119 | } 120 | 121 | int Node::GetSelfSize(int id) { 122 | return static_cast(parser_->nodes[id * parser_->node_field_length + parser_->node_self_size_offset]); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/memory/snapshot_parser.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "../library/json.hpp" 7 | #include "edge.h" 8 | #include "node.h" 9 | #include "tarjan.h" 10 | 11 | #ifndef __SNAPSHOT_PARSER_H_ 12 | #define __SNAPSHOT_PARSER_H_ 13 | 14 | namespace snapshot_parser { 15 | using nlohmann::json; 16 | 17 | enum DOMINATOR_ALGORITHM { TARJAN, DATA_ITERATION }; 18 | 19 | typedef struct SnapshotDistance { 20 | int distance; 21 | int ordinal; 22 | int* node_to_visit; 23 | int* node_to_visit_length; 24 | int* node_distances_; 25 | } snapshot_distance_t; 26 | 27 | typedef struct SnapshotRetainer { 28 | int ordinal; 29 | int edge; 30 | } snapshot_retainer_t; 31 | 32 | typedef struct SnapshotPostOrder { 33 | int* post_order_index_to_ordinal; 34 | int* ordinal_to_post_order_index; 35 | } snapshot_post_order_t; 36 | 37 | typedef struct SnapshotDominate { 38 | int dominate; 39 | int edge = -1; 40 | } snapshot_dominate_t; 41 | 42 | typedef struct SnapshotDominates { 43 | snapshot_dominate_t** dominates = nullptr; 44 | int length = 0; 45 | } snapshot_dominates_t; 46 | 47 | typedef std::unordered_map AddressMap; 48 | typedef std::unordered_map GCRootsMap; 49 | typedef std::unordered_map EdgeSearchingMap; 50 | typedef std::unordered_map OrderedRetainersMap; 51 | typedef std::unordered_map OrderedEdgesMap; 52 | typedef std::unordered_map OrderedDominatesMap; 53 | 54 | const int NO_DISTANCE = -5; 55 | const int BASE_SYSTEMDISTANCE = 100000000; 56 | 57 | class SnapshotParser { 58 | public: 59 | explicit SnapshotParser(json profile); 60 | ~SnapshotParser(); 61 | void CreateAddressMap(); 62 | void ClearAddressMap(); 63 | int SearchOrdinalByAddress(long address); 64 | void BuildTotalRetainer(); 65 | int GetRetainersCount(int id); 66 | snapshot_retainer_t** GetRetainers(int id); 67 | void BuildDistances(); 68 | int GetDistance(int id); 69 | int IsGCRoot(int id); 70 | void BuildDominatorTree(); 71 | int GetRetainedSize(int id); 72 | int* GetSortedEdges(int id); 73 | snapshot_dominates_t* GetSortedDominates(int id); 74 | int GetImmediateDominator(int id); 75 | void SetAlgorithm(DOMINATOR_ALGORITHM algo); 76 | json nodes; 77 | json edges; 78 | json strings; 79 | json snapshot; 80 | int root_index = 0; 81 | int node_field_length; 82 | int edge_field_length; 83 | int node_count; 84 | int edge_count; 85 | json node_types; 86 | json edge_types; 87 | int node_type_offset; 88 | int node_name_offset; 89 | int node_address_offset; 90 | int node_self_size_offset; 91 | int node_edge_count_offset; 92 | int node_trace_nodeid_offset; 93 | int edge_type_offset; 94 | int edge_name_or_index_offset; 95 | int edge_to_node_offset; 96 | int* edge_from_node; 97 | int* first_edge_indexes; 98 | snapshot_node::Node* node_util; 99 | snapshot_edge::Edge* edge_util; 100 | int gcroots; 101 | 102 | private: 103 | int* GetFirstEdgeIndexes_(); 104 | static void EnqueueNode_(snapshot_distance_t* t); 105 | void ForEachRoot_(void (*action)(snapshot_distance_t* t), 106 | snapshot_distance_t* user_root, bool user_root_only); 107 | void BFS_(int* node_to_visit, int node_to_visit_length); 108 | bool Filter_(int ordinal, int edge); 109 | static void FillArray_(int* array, int length, int fill); 110 | void CalculateFlags_(); 111 | static int IndexOf_(json array, std::string target); 112 | snapshot_post_order_t* BuildPostOrderIndex_(); 113 | void BuildDominatorTree_(); 114 | void BuildDominatorTree_(snapshot_post_order_t* ptr); 115 | void CalculateRetainedSizes_(snapshot_post_order_t* ptr); 116 | bool IsEssentialEdge_(int ordinal, int type); 117 | bool HasOnlyWeakRetainers_(int ordinal); 118 | int GetEdgeByParentAndChild_(int parent, int child); 119 | void MarkEdge_(int ordinal); 120 | // address -> node ordinal id 121 | AddressMap address_map_; 122 | // ordinal id -> bool 123 | GCRootsMap gcroots_map_; 124 | // (std::to_string(parent)+std::toString(child)) -> source edge index 125 | EdgeSearchingMap edge_searching_map_; 126 | // ordinal id -> ordered retainers 127 | OrderedRetainersMap ordered_retainers_map_; 128 | // ordinal id -> ordered edges 129 | OrderedEdgesMap ordered_edges_map_; 130 | // ordinal id -> ordered dominates map 131 | OrderedDominatesMap ordered_dominates_map_; 132 | // total retainers 133 | int* retaining_nodes_; 134 | int* retaining_edges_; 135 | int* first_retainer_index_; 136 | // distances 137 | int* node_distances_; 138 | // paged object flags 139 | int* flags_; 140 | // detached dom flag & queried object flag are temporarily ignored 141 | int page_object_flag_ = 4; 142 | // dominator tree 143 | int* dominators_tree_; 144 | tarjan::BoundListMap dominators_; 145 | // retained sizes 146 | int* retained_sizes_; 147 | // algorithm 148 | DOMINATOR_ALGORITHM algorithm_ = DOMINATOR_ALGORITHM::DATA_ITERATION; 149 | }; 150 | } // namespace snapshot_parser 151 | 152 | #endif -------------------------------------------------------------------------------- /src/memory/tarjan.cc: -------------------------------------------------------------------------------- 1 | #include "tarjan.h" 2 | 3 | namespace tarjan 4 | { 5 | TarJan::TarJan(tarjan_bound_list_t *data) 6 | { 7 | int count = data->count; 8 | inbounds = data->inbounds; 9 | outbounds = data->outbounds; 10 | length = count + 1; 11 | dom = new int[length]; 12 | root = data->root; 13 | dfs = new int[length]; 14 | vertex = new int[length]; 15 | semi = new int[length]; 16 | parent = new int[length]; 17 | // bucket = new int[count + 1]; 18 | ancestor = new int[length]; 19 | label = new int[length]; 20 | size = new int[length]; 21 | child = new int[length]; 22 | // init 23 | for (int i = 0; i < length; ++i) 24 | { 25 | dfs[i] = 0; 26 | vertex[i] = ERROR_VALUE; 27 | semi[i] = i; 28 | parent[i] = 0; 29 | // bucket[i] = []; 30 | ancestor[i] = 0; 31 | label[i] = i; 32 | size[i] = 1; 33 | child[i] = 0; 34 | dom[i] = 0; 35 | } 36 | 37 | size[0] = 0; 38 | } 39 | 40 | TarJan::~TarJan() 41 | { 42 | ClearMiddleVariables(); 43 | dominators.clear(); 44 | delete idominator; 45 | } 46 | 47 | void TarJan::Compute() 48 | { 49 | Enumerate_(); 50 | Build_(); 51 | idominator = new int[length]; 52 | for (int i = 0; i < length; ++i) 53 | { 54 | idominator[i] = ERROR_VALUE; 55 | } 56 | for (int i = 1; i < length; ++i) 57 | { 58 | if (dom[i] == 0) 59 | continue; 60 | int dominator = vertex[dom[i]]; 61 | int block = vertex[i]; 62 | dominators[dominator].emplace_back(block); 63 | idominator[block] = dominator; 64 | } 65 | } 66 | 67 | void TarJan::Enumerate_() 68 | { 69 | std::vector queue = {root, 0}; 70 | int dfs = 1; 71 | while (!queue.empty()) 72 | { 73 | int parent = queue.back(); 74 | queue.pop_back(); 75 | int block = queue.back(); 76 | queue.pop_back(); 77 | if (this->dfs[block] != 0) 78 | continue; 79 | this->dfs[block] = dfs; 80 | this->parent[dfs] = parent; 81 | vertex[dfs] = block; 82 | if (outbounds.count(block) != 0) 83 | { 84 | std::vector successors = outbounds.at(block); 85 | for (int i = static_cast(successors.size()) - 1; i >= 0; i--) 86 | { 87 | int succ = successors[i]; 88 | queue.emplace_back(succ); 89 | queue.emplace_back(dfs); 90 | } 91 | } 92 | ++dfs; 93 | } 94 | } 95 | 96 | void TarJan::Build_() 97 | { 98 | for (int w = length - 1; w >= 2; w--) 99 | { 100 | if (inbounds.count(vertex[w]) != 0) 101 | { 102 | for (auto predecessor : inbounds.at(vertex[w])) 103 | { 104 | int v = dfs[predecessor]; 105 | int u = Evaluate_(v); 106 | if (semi[u] < semi[w]) 107 | semi[w] = semi[u]; 108 | } 109 | } 110 | bucket[semi[w]].emplace_back(w); 111 | Link_(parent[w], w); 112 | std::vector &parent_bucket = bucket[parent[w]]; 113 | while (!parent_bucket.empty()) 114 | { 115 | int v = parent_bucket.back(); 116 | parent_bucket.pop_back(); 117 | int u = Evaluate_(v); 118 | if (semi[u] < semi[v]) 119 | dom[v] = u; 120 | else 121 | dom[v] = parent[w]; 122 | } 123 | } 124 | 125 | for (int w = 2; w < length; ++w) 126 | if (dom[w] != semi[w]) 127 | dom[w] = dom[dom[w]]; 128 | dom[1] = 0; 129 | } 130 | 131 | int TarJan::Evaluate_(int v) 132 | { 133 | if (ancestor[v] == 0) 134 | return label[v]; 135 | Compress_(v); 136 | if (semi[label[ancestor[v]]] >= semi[label[v]]) 137 | return label[v]; 138 | else 139 | return label[ancestor[v]]; 140 | } 141 | 142 | void TarJan::Link_(int v, int w) 143 | { 144 | int s = w; 145 | while (semi[label[w]] < semi[label[child[s]]]) 146 | { 147 | if (size[s] + size[child[child[s]]] >= 2 * size[child[s]]) 148 | { 149 | ancestor[child[s]] = s; 150 | child[s] = child[child[s]]; 151 | } 152 | else 153 | { 154 | size[child[s]] = size[s]; 155 | ancestor[s] = child[s]; 156 | s = ancestor[s]; 157 | } 158 | } 159 | label[s] = label[w]; 160 | size[v] += size[w]; 161 | if (size[v] < 2 * size[w]) 162 | { 163 | int t = s; 164 | s = child[v]; 165 | child[v] = t; 166 | } 167 | while (s != 0) 168 | { 169 | ancestor[s] = v; 170 | s = child[s]; 171 | } 172 | } 173 | 174 | void TarJan::Compress_(int v) 175 | { 176 | if (ancestor[ancestor[v]] == 0) 177 | return; 178 | Compress_(ancestor[v]); 179 | if (semi[label[ancestor[v]]] < semi[label[v]]) 180 | label[v] = label[ancestor[v]]; 181 | ancestor[v] = ancestor[ancestor[v]]; 182 | } 183 | 184 | void TarJan::ClearMiddleVariables() 185 | { 186 | length = 0; 187 | root = 0; 188 | delete dfs; 189 | delete vertex; 190 | delete semi; 191 | delete parent; 192 | bucket.clear(); 193 | delete dom; 194 | delete ancestor; 195 | delete label; 196 | delete size; 197 | delete child; 198 | inbounds.clear(); 199 | outbounds.clear(); 200 | } 201 | 202 | } // namespace tarjan -------------------------------------------------------------------------------- /worker/public/js/dashboard/component/tree_dominators.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var TreeDominators = { 3 | template: '#tree-template', 4 | data() { 5 | return { 6 | type: 'dominators', 7 | props: { label: 'name', isLeaf: 'exists' }, 8 | loadMoreStatus: { b1: false, b2: false, b3: false, b4: false }, 9 | limit: Devtoolx.limit, 10 | tooltipType: 'dominates', 11 | customizeStart: 0, 12 | showCustomizeStart: false 13 | } 14 | }, 15 | props: ['getNode', 'formatSize', 'getEdgeType', 'getTitle', 'getAdditional', 16 | 'contextmenu', 'tooltipStyle', 'nodeClick', 'tooltipData', 'rootid'], 17 | methods: { 18 | isString(data) { 19 | return data.type === 'concatenated string' || data.type === 'sliced string' || data.type === 'string'; 20 | }, 21 | formatNode(data, edge, raw) { 22 | raw = raw || {}; 23 | raw.id = data.id; 24 | raw.key = `${Math.random().toString(36).substr(2)}`; 25 | if (data.type === 'array') data.name = `${data.name || ''}[]`; 26 | if (data.type === 'closure') data.name = `${data.name || ''}()`; 27 | if (this.isString(data)) data.name = `"${data.name || ''}"`; 28 | if (typeof data.name === 'string' && data.name.length > 100) 29 | raw.name = data.name.substr(0, 100); 30 | else 31 | raw.name = data.name; 32 | raw.rawName = data.name; 33 | raw.nameClass = 'node-name'; 34 | if (this.isString(data)) 35 | raw.nameClass = `${raw.nameClass} string`; 36 | if (data.type === 'closure') 37 | raw.nameClass = `${raw.nameClass} closure`; 38 | if (data.is_gcroot) 39 | raw.nameClass = `${raw.nameClass} node-gcroot`; 40 | raw.address = data.address; 41 | raw.selfSize = data.self_size; 42 | raw.nodeType = data.type; 43 | raw.retainedSize = data.retained_size; 44 | raw.distance = data.distance; 45 | if (data.edge_name_or_index) { 46 | var edgeType = data.edge_type; 47 | if (edgeType === 'property' || edgeType === 'element' || edgeType === 'shortcut') { 48 | raw.edgeClass = 'property'; 49 | } 50 | if (edgeType === 'context') { 51 | raw.edgeClass = 'context'; 52 | } 53 | raw.fromEdge = data.edge_name_or_index || ''; 54 | raw.edgeType = edgeType; 55 | } 56 | raw.idomed = true; 57 | raw.dominatesCount = data.dominates_count; 58 | raw.index = data.index; 59 | return raw; 60 | }, 61 | loadNode(node, resolve) { 62 | var vm = this; 63 | if (node.level === 0) { 64 | vm.getNode(`/ordinal/${vm.rootid}?current=0&limit=${Devtoolx.limit}&type=retainers`) 65 | .then(data => resolve([vm.formatNode(data[0])])) 66 | .catch(err => vm.$message.error(err.message || 'Server Inner Error')); 67 | return; 68 | } 69 | vm.getNode(`/dominates/${node.data.id}?current=0&limit=${Devtoolx.limit}`) 70 | .then(data => { 71 | var dominates = data.dominates; 72 | var results = dominates.map(dominate => vm.formatNode(dominate)); 73 | if (!data.dominates_end) { 74 | results.push({ 75 | id: node.data.id, 76 | loadMore: true, 77 | dominatesCurrent: data.dominates_current, 78 | exists: true, 79 | dominatesLeft: data.dominates_left 80 | }); 81 | } 82 | resolve(results); 83 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 84 | }, 85 | loadMore(node, rawdata, number, customize) { 86 | var vm = this; 87 | var setKey = customize && 'b4' || number / Devtoolx.limit === 2 && 'b1' || number / Devtoolx.limit === 4 && 'b2' || 'b3'; 88 | vm.$set(vm.loadMoreStatus, setKey, true); 89 | vm.getNode(`/dominates/${rawdata.id}?current=${rawdata.dominatesCurrent}&limit=${number}`) 90 | .then(data => { 91 | data.dominates.forEach((dominate, i) => { 92 | var data = vm.formatNode(dominate); 93 | node.parent.insertBefore({ data }, node); 94 | }); 95 | if (data.dominates_end) 96 | node.parent.childNodes.pop(); 97 | else { 98 | rawdata.dominatesCurrent = data.dominates_current; 99 | rawdata.dominatesLeft = data.dominates_left; 100 | } 101 | vm.$set(vm.loadMoreStatus, setKey, false); 102 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error'));; 103 | }, 104 | uniqueContextmenu(event, data, node, component) { 105 | this.contextmenu(this.tooltipType, event, data, node, component); 106 | }, 107 | uniqueNodeClick() { 108 | this.showCustomizeStart = false; 109 | this.customizeStart = 0; 110 | this.nodeClick(); 111 | }, 112 | jump(node, rawdata) { 113 | if (this.showCustomizeStart) { 114 | if (isNaN(this.customizeStart)) { 115 | 116 | } else 117 | rawdata.dominatesCurrent = Number(this.customizeStart) + Number(rawdata.dominatesCurrent); 118 | this.loadMore(node, rawdata, this.limit * 2, true); 119 | } else { 120 | this.showCustomizeStart = true; 121 | } 122 | } 123 | } 124 | }; 125 | if (typeof Devtoolx === 'undefined') window.Devtoolx = {}; 126 | Devtoolx.TreeDominators = TreeDominators; 127 | })(); -------------------------------------------------------------------------------- /worker/view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | Object count: 15 | {{ statistics.nodeCount }}, Edge count: 16 | {{ statistics.edgeCount }}, GCRoots: 17 | {{ statistics.gcRoots }} 18 |

19 | 21 | 22 | 23 |

Edges:

24 | 27 |

Retainers:

28 | 31 |

DominatorsTree:

32 | 35 | 36 |
37 | 38 | 39 | 75 | 76 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /worker/public/js/dashboard/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | Devtoolx.limit = 25; 3 | Devtoolx.ratioLimit = 0.2; 4 | Devtoolx.tooltipsTyleSource = 'position:absolute;display:block;font-size:1.0em;' 5 | + 'border:1px solid #ccc;font-family:Menlo;' 6 | + 'left:0px;top:20px;z-index:0;'; 7 | Vue.component('my-tootip', Devtoolx.ToolTip); 8 | new Vue({ 9 | el: '#app', 10 | components: { 11 | 'tree-edges': Devtoolx.TreeEdges, 12 | 'tree-retainers': Devtoolx.TreeRetainers, 13 | 'tree-dominators': Devtoolx.TreeDominators 14 | }, 15 | data: function () { 16 | return { 17 | address: '', 18 | rootid: 0, 19 | nodeData: {}, 20 | statistics: { nodeCount: '-', edgeCount: '-', gcRoots: '-', totalSize: 0 }, 21 | tooltipStyle: Devtoolx.tooltipsTyleSource + 'opacity:0.0;', 22 | tooltipData: { 23 | index: null, 24 | type: 'normal', 25 | childName: '', 26 | childSize: -1, 27 | parentOrdinalId: -1, 28 | childOrdinalId: -1, 29 | copyedElement: '' 30 | } 31 | } 32 | }, 33 | mounted() { 34 | var vm = this; 35 | vm.getNode(`/statistics`).then(data => { 36 | vm.$set(vm.statistics, 'nodeCount', data.node_count || '-'); 37 | vm.$set(vm.statistics, 'edgeCount', data.edge_count || '-'); 38 | vm.$set(vm.statistics, 'gcRoots', data.gcroots || '-'); 39 | vm.$set(vm.statistics, 'totalSize', data.total_size || 0); 40 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 41 | document.onclick = this.cancelTooltip.bind(this); 42 | }, 43 | methods: { 44 | cleanInput() { 45 | this.address = ''; 46 | }, 47 | searchIdByAddress() { 48 | if (!this.address) return; 49 | if (this.address[0] !== '@' && isNaN(this.address)) { 50 | this.$message.error(`Error address: "${this.address}"`); 51 | return; 52 | } 53 | var vm = this; 54 | var task = ''; 55 | if (vm.address[0] === '@') { 56 | task = vm.getNode(`/address/${vm.address}?current=0&limit=${Devtoolx.limit}`); 57 | } else { 58 | task = vm.getNode(`/ordinal/${vm.address}?current=0&limit=${Devtoolx.limit}`).then(data => data[0]); 59 | } 60 | task.then(data => { 61 | Object.assign(vm.nodeData, data) 62 | vm.rootid = data.id; 63 | vm.cancelTooltip(); 64 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 65 | }, 66 | getNode(url, method) { 67 | var vm = this; 68 | method = method || 'get'; 69 | return axios[method](url).then(res => { 70 | var data = res.data; 71 | if (data.ok) { 72 | return data.data; 73 | } 74 | throw new Error(data.message); 75 | }); 76 | }, 77 | formatSize(size) { 78 | let str = ''; 79 | if (size / 1024 < 1) { 80 | str = `${(size).toFixed(2)} Bytes`; 81 | } else if (size / 1024 / 1024 < 1) { 82 | str = `${(size / 1024).toFixed(2)} KB`; 83 | } else if (size / 1024 / 1024 / 1024 < 1) { 84 | str = `${(size / 1024 / 1024).toFixed(2)} MB`; 85 | } else { 86 | str = `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`; 87 | } 88 | return str; 89 | }, 90 | getEdgeType(node) { 91 | return node.data.edgeType; 92 | }, 93 | getTitle(node) { 94 | var data = node.data || {}; 95 | return `id: ${data.id}, self_size: ${this.formatSize(data.selfSize)}`; 96 | }, 97 | getAdditional(node) { 98 | var data = node.data; 99 | var retainedSizeSource = `size: ${this.formatSize(data.retainedSize)}`; 100 | var retainedSize = retainedSizeSource; 101 | var parent = node && node.parent && node.parent || {}; 102 | var parentData = parent.data || {}; 103 | var parentRetainedSize = parentData.retainedSize; 104 | var parentSelfSize = parentData.selfSize; 105 | var parentDominatesCount = parentData.dominatesCount; 106 | if (this.totalSize && data.retainedSize / this.totalSize > Devtoolx.ratioLimit) 107 | retainedSize = `${retainedSizeSource}`; 108 | else if (this.totalSize && data.idomed && (parentRetainedSize / this.totalSize > Devtoolx.ratioLimit || parentData.warn) 109 | && data.retainedSize >= (parentRetainedSize - parentSelfSize) / parentDominatesCount) { 110 | retainedSize = `${retainedSizeSource}`; 111 | data.warn = true; 112 | } 113 | return `(type: ${data.nodeType}, ${retainedSize}, distance: ${data.distance})`; 114 | }, 115 | contextmenu(type, event, data, node, component) { 116 | var parentOrdinalId = node.parent && node.parent.data && node.parent.data.id; 117 | if (!parentOrdinalId && parentOrdinalId !== 0) parentOrdinalId = -1; 118 | var childOrdinalId = data.id; 119 | if (!childOrdinalId && childOrdinalId !== 0) childOrdinalId = -1; 120 | this.$set(this.tooltipData, 'index', data.index); 121 | this.$set(this.tooltipData, 'type', type); 122 | this.$set(this.tooltipData, 'childName', `${data.rawName} ${data.address}`); 123 | this.$set(this.tooltipData, 'childSize', data.retainedSize); 124 | this.$set(this.tooltipData, 'parentOrdinalId', parentOrdinalId); 125 | this.$set(this.tooltipData, 'childOrdinalId', childOrdinalId); 126 | this.tooltipStyle = Devtoolx.tooltipsTyleSource + `left:${event.pageX + 15}px;top:${event.pageY + 12}px`; 127 | }, 128 | cancelTooltip() { 129 | this.tooltipStyle = Devtoolx.tooltipsTyleSource + 'opacity:0.0;'; 130 | }, 131 | nodeClick() { 132 | this.cancelTooltip(); 133 | } 134 | }, 135 | computed: { 136 | totalSize() { 137 | return this.statistics.totalSize; 138 | } 139 | } 140 | }) 141 | })(); -------------------------------------------------------------------------------- /worker/public/js/dashboard/component/tree_edges.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var TreeEdges = { 3 | template: '#tree-template', 4 | data() { 5 | return { 6 | props: { label: 'name', isLeaf: 'exists' }, 7 | node: {}, 8 | type: 'edges', 9 | loadMoreStatus: { b1: false, b2: false, b3: false, b4: false }, 10 | limit: Devtoolx.limit, 11 | tooltipType: 'normal', 12 | customizeStart: 0, 13 | showCustomizeStart: false 14 | } 15 | }, 16 | props: ['rootid', 'nodeData', 'getNode', 'formatSize', 'getEdgeType', 'getTitle', 17 | 'getAdditional', 'contextmenu', 'tooltipStyle', 'nodeClick', 'tooltipData'], 18 | methods: { 19 | isString(data) { 20 | return data.type === 'concatenated string' || data.type === 'sliced string' || data.type === 'string'; 21 | }, 22 | formatNode(data, edge, raw) { 23 | raw = raw || {}; 24 | raw.id = data.id; 25 | raw.key = `${Math.random().toString(36).substr(2)}`; 26 | if (data.type === 'array') data.name = `${data.name || ''}[]`; 27 | if (data.type === 'closure') data.name = `${data.name || ''}()`; 28 | if (this.isString(data)) data.name = `"${data.name || ''}"`; 29 | if (typeof data.name === 'string' && data.name.length > 100) 30 | raw.name = data.name.substr(0, 100); 31 | else 32 | raw.name = data.name; 33 | raw.rawName = data.name; 34 | raw.nameClass = 'node-name'; 35 | if (this.isString(data)) 36 | raw.nameClass = `${raw.nameClass} string`; 37 | if (data.type === 'closure') 38 | raw.nameClass = `${raw.nameClass} closure`; 39 | raw.address = data.address; 40 | raw.selfSize = data.self_size; 41 | raw.nodeType = data.type; 42 | raw.retainedSize = data.retained_size; 43 | raw.distance = data.distance; 44 | raw.edges = data.edges; 45 | raw.edgesEnd = data.edges_end; 46 | raw.edgesCurrent = data.edges_current; 47 | raw.edgesLeft = data.edges_left; 48 | if (!edge) { 49 | raw.expandPaths = [data.address]; 50 | } 51 | if (edge) { 52 | if (edge.type === 'property' || edge.type === 'element' || edge.type === 'shortcut') { 53 | raw.edgeClass = 'property'; 54 | } 55 | if (edge.type === 'context') { 56 | raw.edgeClass = 'context'; 57 | } 58 | raw.fromEdge = `${edge.name_or_index}`; 59 | raw.edgeType = edge.type; 60 | raw.idomed = edge.idomed; 61 | raw.dominatesCount = data.dominates_count; 62 | raw.index = edge.index; 63 | } 64 | return raw; 65 | }, 66 | formatPaths(parent, child, address) { 67 | child.expandPaths = parent.expandPaths.concat([address]); 68 | if (~parent.expandPaths.indexOf(address)) { 69 | child.exists = true; 70 | child.class = 'disabled'; 71 | } 72 | }, 73 | loadNode(node, resolve) { 74 | var vm = this; 75 | if (node.level === 0) { 76 | vm.node = node; 77 | vm.getNode(`/ordinal/${vm.rootid}?current=0&limit=${Devtoolx.limit}&type=edges`) 78 | .then(data => resolve([vm.formatNode(data[0])])) 79 | .catch(err => vm.$message.error(err.message || 'Server Inner Error')); 80 | return; 81 | } 82 | var data = node.data; 83 | if (node.level > 0) { 84 | if (data.edges) { 85 | var ids = data.edges.map(e => e.to_node).join(','); 86 | if (ids == '') return resolve([]) 87 | vm.getNode(`/ordinal/${ids}/?current=0&limit=${Devtoolx.limit}&type=edges`) 88 | .then((list) => { 89 | var result = list.map((r, i) => { 90 | var result = vm.formatNode(r, data.edges[i]); 91 | vm.formatPaths(data, result, r.address); 92 | return result; 93 | }).filter(r => r); 94 | if (!data.edgesEnd) { 95 | result.push({ 96 | id: data.id, 97 | loadMore: true, 98 | edgesCurrent: data.edgesCurrent, 99 | exists: true, 100 | edgesLeft: data.edgesLeft 101 | }); 102 | } 103 | resolve(result); 104 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 105 | } 106 | } 107 | }, 108 | loadMore(node, rawdata, number, customize) { 109 | var vm = this; 110 | var p = null; 111 | var setKey = customize && 'b4' || number / Devtoolx.limit === 2 && 'b1' || number / Devtoolx.limit === 4 && 'b2' || 'b3'; 112 | vm.$set(vm.loadMoreStatus, setKey, true); 113 | vm.getNode(`/ordinal/${rawdata.id}?current=${rawdata.edgesCurrent}&limit=${number}&type=edges`) 114 | .then(parent => { 115 | p = parent = parent[0]; 116 | var ids = parent.edges.map(e => e.to_node).join(','); 117 | if (ids == '') return []; 118 | return vm.getNode(`/ordinal/${ids}/?current=0&limit=${Devtoolx.limit}&type=edges`) 119 | }).then(list => { 120 | list.forEach((r, i) => { 121 | var data = vm.formatNode(r, p.edges[i]); 122 | vm.formatPaths(node.parent.data, data, r.address); 123 | node.parent.insertBefore({ data }, node); 124 | }); 125 | if (p.edges_end) { 126 | node.parent.childNodes.pop(); 127 | } else { 128 | rawdata.edgesCurrent = p.edges_current; 129 | rawdata.edgesLeft = p.edges_left; 130 | } 131 | vm.$set(vm.loadMoreStatus, setKey, false); 132 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 133 | }, 134 | uniqueContextmenu(event, data, node, component) { 135 | this.contextmenu(this.tooltipType, event, data, node, component); 136 | }, 137 | uniqueNodeClick() { 138 | this.showCustomizeStart = false; 139 | this.customizeStart = 0; 140 | this.nodeClick(); 141 | }, 142 | jump(node, rawdata) { 143 | if (this.showCustomizeStart) { 144 | if (isNaN(this.customizeStart)) { 145 | 146 | } else 147 | rawdata.edgesCurrent = Number(this.customizeStart) + Number(rawdata.edgesCurrent); 148 | this.loadMore(node, rawdata, this.limit * 2, true); 149 | } else { 150 | this.showCustomizeStart = true; 151 | } 152 | } 153 | }, 154 | watch: { 155 | rootid() { 156 | var root = this.node.childNodes[0]; 157 | root.childNodes = []; 158 | root.expanded = false; 159 | root.isLeaf = false; 160 | root.loaded = false; 161 | this.formatNode(this.nodeData, null, root.data); 162 | } 163 | } 164 | }; 165 | if (typeof Devtoolx === 'undefined') window.Devtoolx = {}; 166 | Devtoolx.TreeEdges = TreeEdges; 167 | })(); -------------------------------------------------------------------------------- /worker/public/js/dashboard/component/tree_retainers.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var TreeRetainers = { 3 | template: '#tree-template', 4 | data() { 5 | return { 6 | props: { label: 'name', isLeaf: 'exists' }, 7 | node: {}, 8 | type: 'retainers', 9 | loadMoreStatus: { b1: false, b2: false, b3: false, b4: false }, 10 | limit: Devtoolx.limit, 11 | tooltipType: 'normal', 12 | customizeStart: 0, 13 | showCustomizeStart: false 14 | } 15 | }, 16 | props: ['rootid', 'nodeData', 'getNode', 'formatSize', 'getEdgeType', 'getTitle', 17 | 'getAdditional', 'contextmenu', 'tooltipStyle', 'nodeClick', 'tooltipData'], 18 | methods: { 19 | isString(data) { 20 | return data.type === 'concatenated string' || data.type === 'sliced string' || data.type === 'string'; 21 | }, 22 | formatNode(data, retainer, raw) { 23 | raw = raw || {}; 24 | raw.id = data.id; 25 | raw.key = `${Math.random().toString(36).substr(2)}`; 26 | if (data.type === 'array') data.name = `${data.name || ''}[]`; 27 | if (data.type === 'closure') data.name = `${data.name || ''}()`; 28 | if (this.isString(data) && !data.name.startsWith('"')) data.name = `"${data.name || ''}"`; 29 | if (typeof data.name === 'string' && data.name.length > 100) 30 | raw.name = data.name.substr(0, 100); 31 | else 32 | raw.name = data.name; 33 | raw.rawName = data.name; 34 | raw.nameClass = 'node-name'; 35 | if (this.isString(data)) 36 | raw.nameClass = `${raw.nameClass} string`; 37 | if (data.type === 'closure') 38 | raw.nameClass = `${raw.nameClass} closure`; 39 | if (data.is_gcroot) 40 | raw.nameClass = `${raw.nameClass} node-gcroot`; 41 | raw.address = data.address; 42 | raw.selfSize = data.self_size; 43 | raw.nodeType = data.type; 44 | raw.retainedSize = data.retained_size; 45 | raw.distance = data.distance; 46 | raw.retainers = data.retainers; 47 | raw.retainersEnd = data.retainers_end; 48 | raw.retainersCurrent = data.retainers_current; 49 | raw.retainersLeft = data.retainers_left; 50 | if (!retainer) { 51 | raw.expandPaths = [data.address]; 52 | } 53 | if (retainer) { 54 | if (retainer.type === 'property' || retainer.type === 'element' || retainer.type === 'shortcut') { 55 | raw.edgeClass = 'property'; 56 | } 57 | if (retainer.type === 'context') { 58 | raw.edgeClass = 'context'; 59 | } 60 | raw.fromEdge = `${retainer.name_or_index}`; 61 | raw.edgeType = retainer.type; 62 | raw.index = retainer.index; 63 | } 64 | return raw; 65 | }, 66 | formatPaths(parent, child, address) { 67 | child.expandPaths = parent.expandPaths.concat([address]); 68 | if (~parent.expandPaths.indexOf(address)) { 69 | child.exists = true; 70 | child.class = 'disabled'; 71 | } 72 | }, 73 | loadNode(node, resolve) { 74 | var vm = this; 75 | if (node.level === 0) { 76 | vm.node = node; 77 | vm.getNode(`/ordinal/${vm.rootid}?current=0&limit=${Devtoolx.limit}&type=retainers`) 78 | .then(data => resolve([vm.formatNode(data[0])])) 79 | .catch(err => vm.$message.error(err.message || 'Server Inner Error')); 80 | return; 81 | } 82 | var data = node.data; 83 | if (node.level > 0) { 84 | if (data.retainers) { 85 | var ids = data.retainers.map(r => r.from_node).join(','); 86 | if (ids == '') return resolve([]) 87 | vm.getNode(`/ordinal/${ids}/?current=0&limit=${Devtoolx.limit}&type=retainers`) 88 | .then((list) => { 89 | var result = list.map((r, i) => { 90 | var result = vm.formatNode(r, data.retainers[i]); 91 | vm.formatPaths(data, result, r.address); 92 | return result; 93 | }).filter(r => r); 94 | if (!data.retainersEnd) { 95 | result.push({ 96 | id: data.id, 97 | loadMore: true, 98 | retainersCurrent: data.retainersCurrent, 99 | exists: true, 100 | retainersLeft: data.retainersLeft 101 | }); 102 | } 103 | resolve(result); 104 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 105 | } 106 | } 107 | }, 108 | loadMore(node, rawdata, number, customize) { 109 | var vm = this; 110 | var p = null; 111 | var setKey = customize && 'b4' || number / Devtoolx.limit === 2 && 'b1' || number / Devtoolx.limit === 4 && 'b2' || 'b3'; 112 | vm.$set(vm.loadMoreStatus, setKey, true); 113 | vm.getNode(`/ordinal/${rawdata.id}?current=${rawdata.retainersCurrent}&limit=${number}&type=retainers`) 114 | .then(parent => { 115 | p = parent = parent[0]; 116 | var ids = parent.retainers.map(r => r.from_node).join(','); 117 | if (ids == '') return []; 118 | return vm.getNode(`/ordinal/${ids}/?current=0&limit=${Devtoolx.limit}&type=retainers`); 119 | }).then(list => { 120 | list.forEach((r, i) => { 121 | var data = vm.formatNode(r, p.retainers[i]); 122 | vm.formatPaths(node.parent.data, data, r.address); 123 | node.parent.insertBefore({ data }, node); 124 | }); 125 | if (p.retainers_end) { 126 | node.parent.childNodes.pop(); 127 | } else { 128 | rawdata.retainersCurrent = p.retainers_current; 129 | rawdata.retainersLeft = p.retainers_left; 130 | } 131 | vm.$set(vm.loadMoreStatus, setKey, false); 132 | }).catch(err => vm.$message.error(err.message || 'Server Inner Error')); 133 | }, 134 | uniqueContextmenu(event, data, node, component) { 135 | return false; 136 | }, 137 | uniqueNodeClick() { 138 | this.showCustomizeStart = false; 139 | this.customizeStart = 0; 140 | this.nodeClick(); 141 | }, 142 | jump(node, rawdata) { 143 | if (this.showCustomizeStart) { 144 | if (isNaN(this.customizeStart)) { 145 | 146 | } else 147 | rawdata.retainersCurrent = Number(this.customizeStart) + Number(rawdata.retainersCurrent); 148 | this.loadMore(node, rawdata, this.limit * 2, true); 149 | } else { 150 | this.showCustomizeStart = true; 151 | } 152 | } 153 | }, 154 | watch: { 155 | rootid() { 156 | var root = this.node.childNodes[0]; 157 | root.childNodes = []; 158 | root.expanded = false; 159 | root.isLeaf = false; 160 | root.loaded = false; 161 | this.formatNode(this.nodeData, null, root.data); 162 | } 163 | } 164 | }; 165 | if (typeof Devtoolx === 'undefined') window.Devtoolx = {}; 166 | Devtoolx.TreeRetainers = TreeRetainers; 167 | })(); -------------------------------------------------------------------------------- /worker/public/js/library/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.18.0 | (c) 2018 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(5),u=n(6),a=r(u);a.Axios=s,a.create=function(e){return r(o.merge(u,e))},a.Cancel=n(23),a.CancelToken=n(24),a.isCancel=n(20),a.all=function(e){return Promise.all(e)},a.spread=n(25),e.exports=a,e.exports.default=a},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"[object ArrayBuffer]"===R.call(e)}function i(e){return"undefined"!=typeof FormData&&e instanceof FormData}function s(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function u(e){return"string"==typeof e}function a(e){return"number"==typeof e}function c(e){return"undefined"==typeof e}function f(e){return null!==e&&"object"==typeof e}function p(e){return"[object Date]"===R.call(e)}function d(e){return"[object File]"===R.call(e)}function l(e){return"[object Blob]"===R.call(e)}function h(e){return"[object Function]"===R.call(e)}function m(e){return f(e)&&h(e.pipe)}function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function g(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function v(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n 6 | * @license MIT 7 | */ 8 | e.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new s,response:new s}}var o=n(6),i=n(2),s=n(17),u=n(18);r.prototype.request=function(e){"string"==typeof e&&(e=i.merge({url:arguments[0]},arguments[1])),e=i.merge(o,{method:"get"},this.defaults,e),e.method=e.method.toLowerCase();var t=[u,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},i.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(i.merge(n||{},{method:e,url:t}))}}),i.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(i.merge(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(8):"undefined"!=typeof process&&(e=n(8)),e}var i=n(2),s=n(7),u={"Content-Type":"application/x-www-form-urlencoded"},a={adapter:o(),transformRequest:[function(e,t){return s(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};a.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){a.headers[e]={}}),i.forEach(["post","put","patch"],function(e){a.headers[e]=i.merge(u)}),e.exports=a},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(9),i=n(12),s=n(13),u=n(14),a=n(10),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(15);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||u(e.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),e.auth){var y=e.auth.username||"",w=e.auth.password||"";d.Authorization="Basic "+c(y+":"+w)}if(l.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?s(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onerror=function(){f(a("Network Error",e,null,l)),l=null},l.ontimeout=function(){f(a("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(16),v=(e.withCredentials||u(e.url))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),e.withCredentials&&(l.withCredentials=!0),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(11);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e}},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(r(t)+"="+r(e))}))}),i=s.join("&")}return i&&(e+=(e.indexOf("?")===-1?"?":"&")+i),e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function n(){this.message="String contains an invalid character"}function r(e){for(var t,r,i=String(e),s="",u=0,a=o;i.charAt(0|u)||(a="=",u%1);s+=a.charAt(63&t>>8-u%1*8)){if(r=i.charCodeAt(u+=.75),r>255)throw new n;t=t<<8|r}return s}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";n.prototype=new Error,n.prototype.code=5,n.prototype.name="InvalidCharacterError",e.exports=r},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var u=[];u.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(o)&&u.push("path="+o),r.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),i=n(19),s=n(20),u=n(6),a=n(21),c=n(22);e.exports=function(e){r(e),e.baseURL&&!a(e.url)&&(e.url=c(e.baseURL,e.url)),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||u.adapter;return t(e).then(function(t){return r(e),t.data=i(t.data,t.headers,e.transformResponse),t},function(t){return s(t)||(r(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); 9 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /src/memory/parser.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "parser.h" 3 | 4 | namespace parser 5 | { 6 | using snapshot_parser::DOMINATOR_ALGORITHM; 7 | using snapshot_parser::snapshot_dominate_t; 8 | using snapshot_parser::snapshot_dominates_t; 9 | using snapshot_parser::snapshot_retainer_t; 10 | using v8::Array; 11 | using v8::Boolean; 12 | using v8::Function; 13 | using v8::FunctionTemplate; 14 | using v8::Local; 15 | using v8::Number; 16 | using v8::Object; 17 | using v8::String; 18 | using v8::Value; 19 | 20 | Nan::Persistent Parser::constructor; 21 | 22 | Parser::Parser(char *filename, int filelength) 23 | { 24 | filelength_ = filelength; 25 | filename_ = (char *)malloc(filelength); 26 | strncpy(filename_, filename, filelength); 27 | } 28 | 29 | Parser::~Parser() {} 30 | 31 | void Parser::Init(Local exports) 32 | { 33 | Local tpl = Nan::New(New); 34 | tpl->SetClassName(Nan::New("V8Parser").ToLocalChecked()); 35 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 36 | 37 | Nan::SetPrototypeMethod(tpl, "getFileName", GetFileName); 38 | Nan::SetPrototypeMethod(tpl, "parse", Parse); 39 | Nan::SetPrototypeMethod(tpl, "getNodeId", GetNodeId); 40 | Nan::SetPrototypeMethod(tpl, "getNodeByOrdinalId", GetNodeByOrdinalId); 41 | Nan::SetPrototypeMethod(tpl, "getNodeByAddress", GetNodeByAddress); 42 | Nan::SetPrototypeMethod(tpl, "getNodeIdByAddress", GetNodeIdByAddress); 43 | Nan::SetPrototypeMethod(tpl, "getStatistics", GetStatistics); 44 | Nan::SetPrototypeMethod(tpl, "getDominatorByIDom", GetDominatorByIDom); 45 | Nan::SetPrototypeMethod(tpl, "getChildRepeat", GetChildRepeat); 46 | Nan::SetPrototypeMethod(tpl, "getConsStringName", GetConsStringName); 47 | 48 | constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked()); 49 | Nan::Set(exports, Nan::New("V8Parser").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); 50 | } 51 | 52 | void Parser::New(const Nan::FunctionCallbackInfo &info) 53 | { 54 | if (!info[0]->IsString()) 55 | { 56 | Nan::ThrowTypeError(Nan::New("constrctor arguments 0 must be string!").ToLocalChecked()); 57 | info.GetReturnValue().Set(Nan::Undefined()); 58 | return; 59 | } 60 | // new instance 61 | if (info.IsConstructCall()) 62 | { 63 | Nan::Utf8String filename(Nan::To(info[0]).ToLocalChecked()); 64 | // last position for "\0" 65 | int length = static_cast(strlen(*filename)) + 1; 66 | Parser *parser = new Parser(*filename, length); 67 | parser->Wrap(info.This()); 68 | info.GetReturnValue().Set(info.This()); 69 | } 70 | else 71 | { 72 | const int argc = 1; 73 | Local argv[argc] = {info[0]}; 74 | Local cons = Nan::New(constructor); 75 | info.GetReturnValue().Set((Nan::NewInstance(cons, argc, argv)).ToLocalChecked()); 76 | } 77 | } 78 | 79 | void Print(Local cb, std::string type, std::string status, int64_t cost) 80 | { 81 | if (cb->IsFunction()) 82 | { 83 | Local progress = Nan::New(); 84 | Nan::Set(progress, Nan::New("type").ToLocalChecked(), Nan::New(type).ToLocalChecked()); 85 | Nan::Set(progress, Nan::New("status").ToLocalChecked(), Nan::New(status).ToLocalChecked()); 86 | Nan::Set(progress, Nan::New("cost").ToLocalChecked(), Nan::New(cost / 10e5)); 87 | Local argv[1] = {progress}; 88 | Nan::Call(cb.As(), Nan::New(), 1, argv); 89 | } 90 | } 91 | 92 | void Parser::Parse(const Nan::FunctionCallbackInfo &info) 93 | { 94 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 95 | // get json from heapsnapshot 96 | Print(info[1], "json parse", "start", 0); 97 | int64_t jsonparse_start = uv_hrtime(); 98 | std::ifstream jsonfile(parser->filename_); 99 | if (!jsonfile.is_open()) { 100 | std::cout << "\nfailed to open " << parser->filename_ << '\n'; 101 | std::cerr << "ParseError: " << strerror(errno); 102 | std::exit(1); 103 | return; 104 | } 105 | json profile; 106 | jsonfile >> profile; 107 | jsonfile.close(); 108 | Print(info[1], "json parse", "end", uv_hrtime() - jsonparse_start); 109 | // get snapshot parser 110 | Print(info[1], "snapshot parser init", "start", 0); 111 | int64_t snapshot_parser_init_start = uv_hrtime(); 112 | parser->snapshot_parser = new snapshot_parser::SnapshotParser(profile); 113 | Print(info[1], "snapshot parser init", "end", uv_hrtime() - snapshot_parser_init_start); 114 | if (info[0]->IsObject()) 115 | { 116 | Local options = Nan::To(info[0]).ToLocalChecked(); 117 | // chose algorithm 118 | Local method = Nan::Get(options, Nan::New("method").ToLocalChecked()).ToLocalChecked(); 119 | Nan::Utf8String method_s(Nan::To(method).ToLocalChecked()); 120 | std::string tarjan = "tarjan"; 121 | std::string data_iteration = "data_iteration"; 122 | if (strcmp(*method_s, tarjan.c_str()) == 0) 123 | { 124 | parser->snapshot_parser->SetAlgorithm(DOMINATOR_ALGORITHM::TARJAN); 125 | } 126 | if (strcmp(*method_s, data_iteration.c_str()) == 0) 127 | { 128 | parser->snapshot_parser->SetAlgorithm(DOMINATOR_ALGORITHM::DATA_ITERATION); 129 | } 130 | // get result 131 | Local mode = Nan::Get(options, Nan::New("mode").ToLocalChecked()).ToLocalChecked(); 132 | Nan::Utf8String mode_s(Nan::To(mode).ToLocalChecked()); 133 | std::string mode_search = "search"; 134 | if (strcmp(*mode_s, mode_search.c_str()) == 0) 135 | { 136 | Print(info[1], "get address map", "start", 0); 137 | int64_t get_address_map_start = uv_hrtime(); 138 | parser->snapshot_parser->CreateAddressMap(); 139 | Print(info[1], "get address map", "end", uv_hrtime() - get_address_map_start); 140 | Print(info[1], "build retainer", "start", 0); 141 | int64_t build_retainer_start = uv_hrtime(); 142 | parser->snapshot_parser->BuildTotalRetainer(); 143 | Print(info[1], "build retainer", "end", uv_hrtime() - build_retainer_start); 144 | Print(info[1], "build distance", "start", 0); 145 | int64_t build_distance_start = uv_hrtime(); 146 | parser->snapshot_parser->BuildDistances(); 147 | Print(info[1], "build distance", "end", uv_hrtime() - build_distance_start); 148 | Print(info[1], "build dominator tree", "start", 0); 149 | int64_t build_dominator_tree_start = uv_hrtime(); 150 | parser->snapshot_parser->BuildDominatorTree(); 151 | Print(info[1], "build dominator tree", "end", uv_hrtime() - build_dominator_tree_start); 152 | } 153 | } 154 | } 155 | 156 | Local Parser::SetNormalInfo_(int id) 157 | { 158 | Local node = Nan::New(); 159 | if (!snapshot_parser->node_util->CheckOrdinalId(id)) 160 | return node; 161 | Nan::Set(node, Nan::New("id").ToLocalChecked(), Nan::New(id)); 162 | std::string type = snapshot_parser->node_util->GetType(id); 163 | Nan::Set(node, Nan::New("type").ToLocalChecked(), Nan::New(type).ToLocalChecked()); 164 | int type_int = snapshot_parser->node_util->GetTypeForInt(id); 165 | std::string name; 166 | if (type_int == snapshot_node::NodeTypes::KCONCATENATED_STRING) 167 | name = snapshot_parser->node_util->GetConsStringName(id); 168 | else 169 | name = snapshot_parser->node_util->GetName(id); 170 | Nan::Set(node, Nan::New("name").ToLocalChecked(), Nan::New(name).ToLocalChecked()); 171 | std::string address = "@" + std::to_string(snapshot_parser->node_util->GetAddress(id)); 172 | Nan::Set(node, Nan::New("address").ToLocalChecked(), Nan::New(address).ToLocalChecked()); 173 | int self_size = snapshot_parser->node_util->GetSelfSize(id); 174 | Nan::Set(node, Nan::New("self_size").ToLocalChecked(), Nan::New(self_size)); 175 | int retained_size = snapshot_parser->GetRetainedSize(id); 176 | Nan::Set(node, Nan::New("retained_size").ToLocalChecked(), Nan::New(retained_size)); 177 | int distance = snapshot_parser->GetDistance(id); 178 | Nan::Set(node, Nan::New("distance").ToLocalChecked(), Nan::New(distance)); 179 | bool is_gcroot = snapshot_parser->IsGCRoot(id); 180 | Nan::Set(node, Nan::New("is_gcroot").ToLocalChecked(), Nan::New(is_gcroot)); 181 | snapshot_dominates_t *dominates = snapshot_parser->GetSortedDominates(id); 182 | Nan::Set(node, Nan::New("dominates_count").ToLocalChecked(), Nan::New(dominates->length)); 183 | return node; 184 | } 185 | 186 | Local Parser::GetNodeById_(int id, int current, int limit, GetNodeTypes get_node_type) 187 | { 188 | Local node = SetNormalInfo_(id); 189 | // get edges 190 | if (get_node_type == KALL || get_node_type == KEDGES) 191 | { 192 | if (!snapshot_parser->node_util->CheckOrdinalId(id)) 193 | { 194 | Nan::ThrowTypeError(Nan::New("argument 0 is wrong!").ToLocalChecked()); 195 | return node; 196 | } 197 | int *edges_local = snapshot_parser->GetSortedEdges(id); 198 | int edges_length = snapshot_parser->node_util->GetEdgeCount(id); 199 | int start_edge_index = current; 200 | int stop_edge_index = current + limit; 201 | if (start_edge_index >= edges_length) 202 | { 203 | start_edge_index = edges_length; 204 | } 205 | if (stop_edge_index >= edges_length) 206 | { 207 | stop_edge_index = edges_length; 208 | } 209 | Local edges = Nan::New(stop_edge_index - start_edge_index); 210 | for (int i = start_edge_index; i < stop_edge_index; i++) 211 | { 212 | Local edge = Nan::New(); 213 | std::string edge_type = snapshot_parser->edge_util->GetType(edges_local[i], true); 214 | std::string name_or_index = snapshot_parser->edge_util->GetNameOrIndex(edges_local[i], true); 215 | int to_node = snapshot_parser->edge_util->GetTargetNode(edges_local[i], true); 216 | int dominator = snapshot_parser->GetImmediateDominator(to_node); 217 | bool idomed = dominator == id; 218 | Nan::Set(edge, Nan::New("type").ToLocalChecked(), Nan::New(edge_type).ToLocalChecked()); 219 | Nan::Set(edge, Nan::New("name_or_index").ToLocalChecked(), Nan::New(name_or_index).ToLocalChecked()); 220 | Nan::Set(edge, Nan::New("to_node").ToLocalChecked(), Nan::New(to_node)); 221 | Nan::Set(edge, Nan::New("idomed").ToLocalChecked(), Nan::New(idomed)); 222 | Nan::Set(edge, Nan::New("index").ToLocalChecked(), Nan::New(i)); 223 | Nan::Set(edges, (i - start_edge_index), edge); 224 | } 225 | if (stop_edge_index < edges_length) 226 | { 227 | Nan::Set(node, Nan::New("edges_end").ToLocalChecked(), Nan::New(false)); 228 | Nan::Set(node, Nan::New("edges_current").ToLocalChecked(), Nan::New(stop_edge_index)); 229 | Nan::Set(node, Nan::New("edges_left").ToLocalChecked(), Nan::New(edges_length - stop_edge_index)); 230 | } 231 | else 232 | { 233 | Nan::Set(node, Nan::New("edges_end").ToLocalChecked(), Nan::New(true)); 234 | } 235 | Nan::Set(node, Nan::New("edges").ToLocalChecked(), edges); 236 | } 237 | // get retainers 238 | if (get_node_type == KALL || get_node_type == KRETAINERS) 239 | { 240 | snapshot_retainer_t **retainers_local = snapshot_parser->GetRetainers(id); 241 | int retainers_length = snapshot_parser->GetRetainersCount(id); 242 | int start_retainer_index = current; 243 | int stop_retainer_index = current + limit; 244 | if (start_retainer_index >= retainers_length) 245 | { 246 | start_retainer_index = retainers_length; 247 | } 248 | if (stop_retainer_index >= retainers_length) 249 | { 250 | stop_retainer_index = retainers_length; 251 | } 252 | Local retainers = Nan::New(stop_retainer_index - start_retainer_index); 253 | for (int i = start_retainer_index; i < stop_retainer_index; i++) 254 | { 255 | snapshot_retainer_t *retainer_local = *(retainers_local + i); 256 | int node = retainer_local->ordinal; 257 | int edge = retainer_local->edge; 258 | Local retainer = Nan::New(); 259 | std::string edge_type = snapshot_parser->edge_util->GetType(edge, true); 260 | std::string name_or_index = snapshot_parser->edge_util->GetNameOrIndex(edge, true); 261 | Nan::Set(retainer, Nan::New("type").ToLocalChecked(), Nan::New(edge_type).ToLocalChecked()); 262 | Nan::Set(retainer, Nan::New("name_or_index").ToLocalChecked(), Nan::New(name_or_index).ToLocalChecked()); 263 | Nan::Set(retainer, Nan::New("from_node").ToLocalChecked(), Nan::New(node)); 264 | Nan::Set(retainer, Nan::New("index").ToLocalChecked(), Nan::New(i)); 265 | Nan::Set(retainers, (i - start_retainer_index), retainer); 266 | } 267 | if (stop_retainer_index < retainers_length) 268 | { 269 | Nan::Set(node, Nan::New("retainers_end").ToLocalChecked(), Nan::New(false)); 270 | Nan::Set(node, Nan::New("retainers_current").ToLocalChecked(), Nan::New(stop_retainer_index)); 271 | Nan::Set(node, Nan::New("retainers_left").ToLocalChecked(), Nan::New(retainers_length - stop_retainer_index)); 272 | } 273 | else 274 | { 275 | Nan::Set(node, Nan::New("retainers_end").ToLocalChecked(), Nan::New(true)); 276 | } 277 | Nan::Set(node, Nan::New("retainers").ToLocalChecked(), retainers); 278 | // do not free snapshot_retainer_t** retainers_local because it'll be cached 279 | // delete[] retainers_local; 280 | // retainers_local = nullptr; 281 | } 282 | return node; 283 | } 284 | 285 | void Parser::GetNodeId(const Nan::FunctionCallbackInfo &info) 286 | { 287 | if (!info[0]->IsNumber()) 288 | { 289 | Nan::ThrowTypeError(Nan::New("argument must be number!").ToLocalChecked()); 290 | return; 291 | } 292 | int source = static_cast(Nan::To(info[0]).ToChecked()); 293 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 294 | int nodeid = parser->snapshot_parser->node_util->GetNodeId(source); 295 | info.GetReturnValue().Set(Nan::New(nodeid)); 296 | } 297 | 298 | void Parser::GetNodeByOrdinalId(const Nan::FunctionCallbackInfo &info) 299 | { 300 | if (!info[0]->IsArray()) 301 | { 302 | Nan::ThrowTypeError(Nan::New("argument 0 must be array!").ToLocalChecked()); 303 | return; 304 | } 305 | Local list = Nan::To(info[0]).ToLocalChecked(); 306 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 307 | 308 | int length = static_cast(Nan::To(Nan::Get(list, Nan::New("length").ToLocalChecked()).ToLocalChecked()).ToChecked()); 309 | Local nodes = Nan::New(length); 310 | int current = 0; 311 | if (info[1]->IsNumber()) 312 | { 313 | current = static_cast(Nan::To(info[1]).ToChecked()); 314 | } 315 | int limit = 0; 316 | if (info[2]->IsNumber()) 317 | { 318 | limit = static_cast(Nan::To(info[2]).ToChecked()); 319 | } 320 | GetNodeTypes type = KALL; 321 | if (!info[3]->IsUndefined() && !info[3]->IsNull()) 322 | { 323 | Local options = Nan::To(info[3]).ToLocalChecked(); 324 | Local raw_option = Nan::To(Nan::Get(options, Nan::New("type").ToLocalChecked()).ToLocalChecked()).ToLocalChecked(); 325 | std::string type_edges = "edges"; 326 | std::string type_retainers = "retainers"; 327 | Nan::Utf8String option_type(raw_option); 328 | if (strcmp(*option_type, type_edges.c_str()) == 0) 329 | { 330 | type = KEDGES; 331 | } 332 | if (strcmp(*option_type, type_retainers.c_str()) == 0) 333 | { 334 | type = KRETAINERS; 335 | } 336 | } 337 | int error_id_count = 0; 338 | for (int i = 0; i < length; i++) 339 | { 340 | int id = static_cast(Nan::To(Nan::Get(list, Nan::New(i)).ToLocalChecked()).ToChecked()); 341 | if (id >= parser->snapshot_parser->node_count) 342 | { 343 | error_id_count++; 344 | break; 345 | } 346 | if (!info[2]->IsNumber()) 347 | { 348 | limit = parser->snapshot_parser->node_util->GetEdgeCount(id); 349 | } 350 | Nan::Set(nodes, i, parser->GetNodeById_(id, current, limit, type)); 351 | } 352 | if (error_id_count > 0) 353 | { 354 | Nan::ThrowTypeError(Nan::New("node ordinal id wrong!").ToLocalChecked()); 355 | return; 356 | } 357 | info.GetReturnValue().Set(nodes); 358 | } 359 | 360 | void Parser::GetNodeByAddress(const Nan::FunctionCallbackInfo &info) 361 | { 362 | if (!info[0]->IsString()) 363 | { 364 | Nan::ThrowTypeError(Nan::New("argument 0 must be string!").ToLocalChecked()); 365 | return; 366 | } 367 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 368 | Nan::Utf8String addr(Nan::To(info[0]).ToLocalChecked()); 369 | char start = '@'; 370 | if (strncmp(*addr, &start, 1) != 0) 371 | { 372 | Nan::ThrowTypeError(Nan::New("argument 0 must be startwith \"@\"!").ToLocalChecked()); 373 | return; 374 | } 375 | int id = parser->snapshot_parser->SearchOrdinalByAddress(atoi((*addr) + 1)); 376 | if (id == -1) 377 | { 378 | std::string addrs = *addr; 379 | std::string error = "address \"" + addrs + "\" is wrong!"; 380 | Nan::ThrowTypeError(Nan::New(error).ToLocalChecked()); 381 | return; 382 | } 383 | int current = 0; 384 | if (info[1]->IsNumber()) 385 | { 386 | current = static_cast(Nan::To(info[1]).ToChecked()); 387 | } 388 | int limit = parser->snapshot_parser->node_util->GetEdgeCount(id); 389 | if (info[2]->IsNumber()) 390 | { 391 | limit = static_cast(Nan::To(info[2]).ToChecked()); 392 | } 393 | Local node = parser->GetNodeById_(id, current, limit, KALL); 394 | info.GetReturnValue().Set(node); 395 | } 396 | 397 | void Parser::GetNodeIdByAddress(const Nan::FunctionCallbackInfo &info) 398 | { 399 | if (!info[0]->IsString()) 400 | { 401 | Nan::ThrowTypeError(Nan::New("argument must be string!").ToLocalChecked()); 402 | return; 403 | } 404 | Nan::Utf8String addr(Nan::To(info[0]).ToLocalChecked()); 405 | char start = '@'; 406 | if (strncmp(*addr, &start, 1) != 0) 407 | { 408 | Nan::ThrowTypeError(Nan::New("argument 0 must be startwith \"@\"!").ToLocalChecked()); 409 | return; 410 | } 411 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 412 | int id = parser->snapshot_parser->SearchOrdinalByAddress(atoi((*addr) + 1)); 413 | if (id == -1) 414 | { 415 | std::string addrs = *addr; 416 | std::string error = "address \"" + addrs + "\" is wrong!"; 417 | Nan::ThrowTypeError(Nan::New(error).ToLocalChecked()); 418 | return; 419 | } 420 | info.GetReturnValue().Set(Nan::New(id)); 421 | } 422 | 423 | void Parser::GetFileName(const Nan::FunctionCallbackInfo &info) 424 | { 425 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 426 | info.GetReturnValue().Set(Nan::New(parser->filename_).ToLocalChecked()); 427 | } 428 | 429 | void Parser::GetStatistics(const Nan::FunctionCallbackInfo &info) 430 | { 431 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 432 | Local statistics = Nan::New(); 433 | Nan::Set(statistics, Nan::New("node_count").ToLocalChecked(), Nan::New(parser->snapshot_parser->node_count)); 434 | Nan::Set(statistics, Nan::New("edge_count").ToLocalChecked(), Nan::New(parser->snapshot_parser->edge_count)); 435 | Nan::Set(statistics, Nan::New("gcroots").ToLocalChecked(), Nan::New(parser->snapshot_parser->gcroots)); 436 | Nan::Set(statistics, Nan::New("total_size").ToLocalChecked(), Nan::New(parser->snapshot_parser->GetRetainedSize(parser->snapshot_parser->root_index))); 437 | info.GetReturnValue().Set(statistics); 438 | } 439 | 440 | void Parser::GetDominatorByIDom(const Nan::FunctionCallbackInfo &info) 441 | { 442 | if (!info[0]->IsNumber()) 443 | { 444 | Nan::ThrowTypeError(Nan::New("argument 0 must be number!").ToLocalChecked()); 445 | return; 446 | } 447 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 448 | snapshot_dominates_t *ptr = 449 | parser->snapshot_parser->GetSortedDominates(static_cast(Nan::To(info[0]).ToChecked())); 450 | int current = 0; 451 | if (info[1]->IsNumber()) 452 | current = static_cast(Nan::To(info[1]).ToChecked()); 453 | int limit = 0; 454 | if (info[2]->IsNumber()) 455 | limit = static_cast(Nan::To(info[2]).ToChecked()); 456 | else 457 | limit = ptr->length; 458 | if (current >= ptr->length) 459 | current = ptr->length; 460 | int end = current + limit; 461 | if (end >= ptr->length) 462 | end = ptr->length; 463 | Local dominates = Nan::New(end - current); 464 | for (int i = current; i < end; i++) 465 | { 466 | snapshot_dominate_t *sdom = *(ptr->dominates + i); 467 | int id = sdom->dominate; 468 | Local node = parser->SetNormalInfo_(id); 469 | if (sdom->edge != -1) 470 | { 471 | std::string edge_name_or_index = parser->snapshot_parser->edge_util->GetNameOrIndex(sdom->edge, true); 472 | Nan::Set(node, Nan::New("edge_name_or_index").ToLocalChecked(), Nan::New(edge_name_or_index).ToLocalChecked()); 473 | std::string edge_type = parser->snapshot_parser->edge_util->GetType(sdom->edge, true); 474 | Nan::Set(node, Nan::New("edge_type").ToLocalChecked(), Nan::New(edge_type).ToLocalChecked()); 475 | } 476 | Nan::Set(node, Nan::New("index").ToLocalChecked(), Nan::New(i)); 477 | Nan::Set(dominates, (i - current), node); 478 | } 479 | Local result = Nan::New(); 480 | Nan::Set(result, Nan::New("dominates").ToLocalChecked(), dominates); 481 | if (end < ptr->length) 482 | { 483 | Nan::Set(result, Nan::New("dominates_end").ToLocalChecked(), Nan::New(false)); 484 | Nan::Set(result, Nan::New("dominates_current").ToLocalChecked(), Nan::New(end)); 485 | Nan::Set(result, Nan::New("dominates_left").ToLocalChecked(), Nan::New(ptr->length - end)); 486 | } 487 | else 488 | Nan::Set(result, Nan::New("dominates_end").ToLocalChecked(), Nan::New(true)); 489 | info.GetReturnValue().Set(result); 490 | } 491 | 492 | void Parser::GetChildRepeat(const Nan::FunctionCallbackInfo &info) 493 | { 494 | if (!info[0]->IsNumber() || !info[1]->IsNumber()) 495 | { 496 | Nan::ThrowTypeError(Nan::New("argument 0 & 1 must be number!").ToLocalChecked()); 497 | return; 498 | } 499 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 500 | int parent_id = static_cast(Nan::To(info[0]).ToChecked()); 501 | int child_id = static_cast(Nan::To(info[1]).ToChecked()); 502 | if (!parser->snapshot_parser->node_util->CheckOrdinalId(child_id) || 503 | !parser->snapshot_parser->node_util->CheckOrdinalId(parent_id)) 504 | { 505 | Nan::ThrowTypeError(Nan::New("argument 0 is wrong!").ToLocalChecked()); 506 | return; 507 | } 508 | int child_name = parser->snapshot_parser->node_util->GetNameForInt(child_id); 509 | int child_self_size = parser->snapshot_parser->node_util->GetSelfSize(child_id); 510 | int child_distance = parser->snapshot_parser->GetDistance(child_id); 511 | // search 512 | int count = 0; 513 | int total_retained_size = 0; 514 | bool done = false; 515 | if (info[2]->IsString()) 516 | { 517 | Nan::Utf8String type(Nan::To(info[2]).ToLocalChecked()); 518 | std::string dominates_type = "dominates"; 519 | if (strcmp(dominates_type.c_str(), *type) == 0) 520 | { 521 | done = true; 522 | snapshot_dominates_t *ptr = parser->snapshot_parser->GetSortedDominates(parent_id); 523 | int childs_length = ptr->length; 524 | snapshot_dominate_t **childs = ptr->dominates; 525 | for (int i = 0; i < childs_length; i++) 526 | { 527 | snapshot_dominate_t *child = *(childs + i); 528 | int target_node = child->dominate; 529 | if (!parser->snapshot_parser->node_util->CheckOrdinalId(target_node)) 530 | continue; 531 | int name = parser->snapshot_parser->node_util->GetNameForInt(target_node); 532 | int self_size = parser->snapshot_parser->node_util->GetSelfSize(target_node); 533 | int distance = parser->snapshot_parser->GetDistance(target_node); 534 | if (name == child_name && self_size == child_self_size && child_distance == distance) 535 | { 536 | count++; 537 | total_retained_size += parser->snapshot_parser->GetRetainedSize(target_node); 538 | } 539 | } 540 | } 541 | } 542 | if (!done) 543 | { 544 | int childs_length = parser->snapshot_parser->node_util->GetEdgeCount(parent_id); 545 | int *childs = parser->snapshot_parser->GetSortedEdges(parent_id); 546 | for (int i = 0; i < childs_length; i++) 547 | { 548 | int target_node = parser->snapshot_parser->edge_util->GetTargetNode(*(childs + i), true); 549 | if (!parser->snapshot_parser->node_util->CheckOrdinalId(target_node)) 550 | continue; 551 | int name = parser->snapshot_parser->node_util->GetNameForInt(target_node); 552 | int self_size = parser->snapshot_parser->node_util->GetSelfSize(target_node); 553 | int distance = parser->snapshot_parser->GetDistance(target_node); 554 | if (name == child_name && self_size == child_self_size && child_distance == distance) 555 | { 556 | count++; 557 | total_retained_size += parser->snapshot_parser->GetRetainedSize(target_node); 558 | } 559 | } 560 | } 561 | Local result = Nan::New(); 562 | Nan::Set(result, Nan::New("count").ToLocalChecked(), Nan::New(count)); 563 | Nan::Set(result, Nan::New("total_retained_size").ToLocalChecked(), Nan::New(total_retained_size)); 564 | info.GetReturnValue().Set(result); 565 | } 566 | 567 | void Parser::GetConsStringName(const Nan::FunctionCallbackInfo &info) 568 | { 569 | if (!info[0]->IsNumber()) 570 | { 571 | Nan::ThrowTypeError(Nan::New("argument 0 must be number!").ToLocalChecked()); 572 | return; 573 | } 574 | Parser *parser = ObjectWrap::Unwrap(info.Holder()); 575 | int id = static_cast(Nan::To(info[0]).ToChecked()); 576 | if (!parser->snapshot_parser->node_util->CheckOrdinalId(id)) 577 | { 578 | Nan::ThrowTypeError(Nan::New("argument 0 is wrong!").ToLocalChecked()); 579 | return; 580 | } 581 | Local cons_name = Nan::New(parser->snapshot_parser->node_util 582 | ->GetConsStringName(id)) 583 | .ToLocalChecked(); 584 | info.GetReturnValue().Set(cons_name); 585 | } 586 | } -------------------------------------------------------------------------------- /src/memory/snapshot_parser.cc: -------------------------------------------------------------------------------- 1 | #include "snapshot_parser.h" 2 | 3 | namespace snapshot_parser 4 | { 5 | SnapshotParser::SnapshotParser(json profile) 6 | { 7 | nodes = profile["nodes"]; 8 | edges = profile["edges"]; 9 | strings = profile["strings"]; 10 | snapshot = profile["snapshot"]; 11 | if (snapshot["root_index"] != nullptr) 12 | { 13 | root_index = snapshot["root_index"]; 14 | } 15 | json node_fields = snapshot["meta"]["node_fields"]; 16 | json edge_fields = snapshot["meta"]["edge_fields"]; 17 | node_field_length = static_cast(node_fields.size()); 18 | edge_field_length = static_cast(edge_fields.size()); 19 | node_count = static_cast(nodes.size() / node_field_length); 20 | edge_count = static_cast(edges.size() / edge_field_length); 21 | node_types = snapshot["meta"]["node_types"][0]; 22 | edge_types = snapshot["meta"]["edge_types"][0]; 23 | node_type_offset = IndexOf_(node_fields, "type"); 24 | node_name_offset = IndexOf_(node_fields, "name"); 25 | node_address_offset = IndexOf_(node_fields, "id"); 26 | node_self_size_offset = IndexOf_(node_fields, "self_size"); 27 | node_edge_count_offset = IndexOf_(node_fields, "edge_count"); 28 | node_trace_nodeid_offset = IndexOf_(node_fields, "trace_node_id"); 29 | edge_type_offset = IndexOf_(edge_fields, "type"); 30 | edge_name_or_index_offset = IndexOf_(edge_fields, "name_or_index"); 31 | edge_to_node_offset = IndexOf_(edge_fields, "to_node"); 32 | // edge_from_node = new int[edge_count](); 33 | first_edge_indexes = GetFirstEdgeIndexes_(); 34 | node_util = new snapshot_node::Node(this); 35 | edge_util = new snapshot_edge::Edge(this); 36 | } 37 | 38 | int SnapshotParser::IndexOf_(json array, std::string target) 39 | { 40 | const char *t = target.c_str(); 41 | int size = static_cast(array.size()); 42 | for (int i = 0; i < size; i++) 43 | { 44 | std::string str1 = array[i]; 45 | if (strcmp(str1.c_str(), t) == 0) 46 | { 47 | return i; 48 | } 49 | } 50 | return -1; 51 | } 52 | 53 | void SnapshotParser::FillArray_(int *array, int length, int fill) 54 | { 55 | for (int i = 0; i < length; i++) 56 | { 57 | *(array + i) = fill; 58 | } 59 | } 60 | 61 | int *SnapshotParser::GetFirstEdgeIndexes_() 62 | { 63 | int *first_edge_indexes = new int[node_count + 1](); 64 | first_edge_indexes[node_count] = static_cast(edges.size()); 65 | for (int node_ordinal = 0, edge_index = 0; node_ordinal < node_count; node_ordinal++) 66 | { 67 | first_edge_indexes[node_ordinal] = edge_index; 68 | int offset = static_cast(nodes[node_ordinal * node_field_length + node_edge_count_offset]) * edge_field_length; 69 | if (node_ordinal == root_index) 70 | for (int i = edge_index; i < edge_index + offset; i += edge_field_length) 71 | { 72 | // edge_from_node[i / edge_field_length] = node_ordinal; 73 | int child = static_cast(edges[i + edge_to_node_offset]); 74 | if (child % node_field_length == 0) 75 | { 76 | long long key = (static_cast(node_ordinal) << 32) + (child / node_field_length); 77 | edge_searching_map_.insert(EdgeSearchingMap::value_type(key, i)); 78 | } 79 | } 80 | edge_index += offset; 81 | } 82 | return first_edge_indexes; 83 | } 84 | 85 | void SnapshotParser::CreateAddressMap() 86 | { 87 | for (int ordinal = 0; ordinal < node_count; ordinal++) 88 | { 89 | if (!node_util->CheckOrdinalId(ordinal)) 90 | continue; 91 | long address = node_util->GetAddress(ordinal); 92 | address_map_.insert(AddressMap::value_type(address, ordinal)); 93 | } 94 | } 95 | 96 | void SnapshotParser::ClearAddressMap() 97 | { 98 | address_map_.clear(); 99 | } 100 | 101 | int SnapshotParser::SearchOrdinalByAddress(long address) 102 | { 103 | int count = static_cast(address_map_.count(address)); 104 | if (count == 0) 105 | { 106 | return -1; 107 | } 108 | int ordinal = address_map_.at(address); 109 | return ordinal; 110 | } 111 | 112 | void SnapshotParser::BuildTotalRetainer() 113 | { 114 | retaining_nodes_ = new int[edge_count](); 115 | retaining_edges_ = new int[edge_count](); 116 | first_retainer_index_ = new int[node_count + 1](); 117 | // every node's retainer count 118 | for (int to_node_field_index = edge_to_node_offset, l = static_cast(edges.size()); to_node_field_index < l; to_node_field_index += edge_field_length) 119 | { 120 | int to_node_index = static_cast(edges[to_node_field_index]); 121 | if (to_node_index % node_field_length != 0) 122 | { 123 | Nan::ThrowTypeError(Nan::New("node index id is wrong!").ToLocalChecked()); 124 | return; 125 | } 126 | int ordinal_id = to_node_index / node_field_length; 127 | first_retainer_index_[ordinal_id] += 1; 128 | } 129 | // set first retainer index 130 | for (int i = 0, first_unused_retainer_slot = 0; i < node_count; i++) 131 | { 132 | int retainers_count = first_retainer_index_[i]; 133 | first_retainer_index_[i] = first_unused_retainer_slot; 134 | retaining_nodes_[first_unused_retainer_slot] = retainers_count; 135 | first_unused_retainer_slot += retainers_count; 136 | } 137 | // for (index ~ index + 1) 138 | first_retainer_index_[node_count] = edge_count; 139 | // set retaining slot 140 | int next_node_first_edge_index = first_edge_indexes[0]; 141 | for (int src_node_ordinal = 0; src_node_ordinal < node_count; src_node_ordinal++) 142 | { 143 | int first_edge_index = next_node_first_edge_index; 144 | next_node_first_edge_index = first_edge_indexes[src_node_ordinal + 1]; 145 | for (int edge_index = first_edge_index; edge_index < next_node_first_edge_index; edge_index += edge_field_length) 146 | { 147 | int to_node_index = static_cast(edges[edge_index + edge_to_node_offset]); 148 | if (to_node_index % node_field_length != 0) 149 | { 150 | Nan::ThrowTypeError(Nan::New("to_node id is wrong!").ToLocalChecked()); 151 | return; 152 | } 153 | int first_retainer_slot_index = first_retainer_index_[to_node_index / node_field_length]; 154 | int next_unused_retainer_slot_index = first_retainer_slot_index + (--retaining_nodes_[first_retainer_slot_index]); 155 | // save retainer & edge 156 | retaining_nodes_[next_unused_retainer_slot_index] = src_node_ordinal; 157 | retaining_edges_[next_unused_retainer_slot_index] = edge_index; 158 | } 159 | } 160 | } 161 | 162 | int SnapshotParser::GetRetainersCount(int id) 163 | { 164 | int first_retainer_index = first_retainer_index_[id]; 165 | int next_retainer_index = first_retainer_index_[id + 1]; 166 | // count may not be larger than 2^31 167 | return static_cast(next_retainer_index - first_retainer_index); 168 | } 169 | 170 | snapshot_retainer_t **SnapshotParser::GetRetainers(int id) 171 | { 172 | if (ordered_retainers_map_.count(id) != 0) 173 | return ordered_retainers_map_.at(id); 174 | int first_retainer_index = first_retainer_index_[id]; 175 | int next_retainer_index = first_retainer_index_[id + 1]; 176 | int length = static_cast(next_retainer_index - first_retainer_index); 177 | snapshot_retainer_t **retainers = new snapshot_retainer_t *[length]; 178 | for (int i = first_retainer_index; i < next_retainer_index; i++) 179 | { 180 | snapshot_retainer_t *retainer = new snapshot_retainer_t; 181 | retainer->ordinal = retaining_nodes_[i]; 182 | retainer->edge = retaining_edges_[i]; 183 | retainers[i - first_retainer_index] = retainer; 184 | } 185 | std::sort(retainers, retainers + length, [this](snapshot_retainer_t *lhs, snapshot_retainer_t *rhs) 186 | { 187 | int lhs_distance = this->node_distances_[lhs->ordinal]; 188 | int rhs_distance = this->node_distances_[rhs->ordinal]; 189 | return lhs_distance < rhs_distance; }); 190 | ordered_retainers_map_.insert(OrderedRetainersMap::value_type(id, retainers)); 191 | return retainers; 192 | } 193 | 194 | void SnapshotParser::EnqueueNode_(snapshot_distance_t *t) 195 | { 196 | if (t->node_distances_[t->ordinal] != NO_DISTANCE) 197 | return; 198 | t->node_distances_[t->ordinal] = t->distance; 199 | t->node_to_visit[*(t->node_to_visit_length)] = t->ordinal; 200 | *(t->node_to_visit_length) += 1; 201 | } 202 | 203 | bool SnapshotParser::Filter_(int ordinal, int edge) 204 | { 205 | if (!node_util->CheckOrdinalId(ordinal)) 206 | return false; 207 | int node_type = node_util->GetTypeForInt(ordinal); 208 | if (node_type == snapshot_node::NodeTypes::KHIDDEN) 209 | { 210 | std::string edge_name = edge_util->GetNameOrIndex(edge, true); 211 | std::string node_name = node_util->GetName(ordinal); 212 | std::string slow_function_map_name = "sloppy_function_map"; 213 | std::string native_context = "system / NativeContext"; 214 | return (strcmp(edge_name.c_str(), slow_function_map_name.c_str()) != 0) || (strcmp(node_name.c_str(), native_context.c_str()) != 0); 215 | } 216 | if (node_type == snapshot_node::NodeTypes::KARRAY) 217 | { 218 | std::string node_name = node_util->GetName(ordinal); 219 | std::string map_descriptors_name = "(map descriptors)"; 220 | if (strcmp(node_name.c_str(), map_descriptors_name.c_str()) != 0) 221 | return true; 222 | std::string edge_name = edge_util->GetNameOrIndex(edge, true); 223 | int index = atoi(edge_name.c_str()); 224 | return index < 2 || (index % 3) != 1; 225 | } 226 | return true; 227 | } 228 | 229 | void SnapshotParser::ForEachRoot_(void (*action)(snapshot_distance_t *t), snapshot_distance_t *user_root, bool user_root_only) 230 | { 231 | std::unordered_map visit_nodes; 232 | int gc_roots = -1; 233 | if (!node_util->CheckOrdinalId(root_index)) 234 | return; 235 | int *edges = node_util->GetEdges(root_index); 236 | int length = node_util->GetEdgeCount(root_index); 237 | for (int i = 0; i < length; i++) 238 | { 239 | int target_node = edge_util->GetTargetNode(*(edges + i), true); 240 | if (!node_util->CheckOrdinalId(target_node)) 241 | continue; 242 | std::string node_name = node_util->GetName(target_node); 243 | std::string gc_root_name = "(GC roots)"; 244 | if (strcmp(node_name.c_str(), gc_root_name.c_str()) == 0) 245 | { 246 | gc_roots = target_node; 247 | } 248 | } 249 | if (gc_roots == -1 || !node_util->CheckOrdinalId(gc_roots)) 250 | return; 251 | if (user_root_only) 252 | { 253 | // iterator the "true" root, set user root distance 1 -> global 254 | for (int i = 0; i < length; i++) 255 | { 256 | int target_node = edge_util->GetTargetNode(*(edges + i), true); 257 | if (!node_util->CheckOrdinalId(target_node)) 258 | continue; 259 | std::string node_name = node_util->GetName(target_node); 260 | int type = node_util->GetTypeForInt(target_node); 261 | // type != synthetic, means user root 262 | if (type != snapshot_node::NodeTypes::KSYNTHETIC) 263 | { 264 | if (visit_nodes.count(target_node) == 0) 265 | { 266 | user_root->ordinal = target_node; 267 | action(user_root); 268 | visit_nodes.insert(std::unordered_map::value_type(target_node, true)); 269 | } 270 | } 271 | } 272 | delete[] edges; 273 | // set user root gc roots -> synthetic roots -> true roots 274 | // int* sub_root_edges = node_util->GetEdges(gc_roots, false); 275 | // int sub_root_edge_length = node_util->GetEdgeCount(gc_roots, false); 276 | // for(int i = 0; i < sub_root_edge_length; i++) { 277 | // int sub_root_ordinal = edge_util->GetTargetNode(*(sub_root_edges + i), true); 278 | // int* sub2_root_edges = node_util->GetEdges(sub_root_ordinal, false); 279 | // int sub2_root_edge_length = node_util->GetEdgeCount(sub_root_ordinal, false); 280 | // for(int j = 0; j < sub2_root_edge_length; j++) { 281 | // int sub2_root_ordinal = edge_util->GetTargetNode(*(sub2_root_edges + j), true); 282 | // // mark sub sub gc roots 283 | // if(visit_nodes.count(sub2_root_ordinal) == 0) { 284 | // user_root->ordinal = sub2_root_ordinal; 285 | // action(user_root); 286 | // visit_nodes.insert(std::unordered_map::value_type(sub2_root_ordinal, true)); 287 | // } 288 | // } 289 | // } 290 | } 291 | else 292 | { 293 | int *sub_root_edges = node_util->GetEdges(gc_roots); 294 | int sub_root_edge_length = node_util->GetEdgeCount(gc_roots); 295 | for (int i = 0; i < sub_root_edge_length; i++) 296 | { 297 | int sub_root_ordinal = edge_util->GetTargetNode(*(sub_root_edges + i), true); 298 | if (!node_util->CheckOrdinalId(sub_root_ordinal)) 299 | continue; 300 | int *sub2_root_edges = node_util->GetEdges(sub_root_ordinal); 301 | int sub2_root_edge_length = node_util->GetEdgeCount(sub_root_ordinal); 302 | bool need_add_gc_root = true; 303 | std::string sub_root_name = node_util->GetName(sub_root_ordinal); 304 | if (sub_root_name.compare("(Internalized strings)") == 0 || sub_root_name.compare("(External strings)") == 0 || sub_root_name.compare("(Smi roots)") == 0) 305 | { 306 | need_add_gc_root = false; 307 | } 308 | for (int j = 0; j < sub2_root_edge_length; j++) 309 | { 310 | int sub2_root_ordinal = edge_util->GetTargetNode(*(sub2_root_edges + j), true); 311 | // mark sub sub gc roots 312 | if (visit_nodes.count(sub2_root_ordinal) == 0) 313 | { 314 | user_root->ordinal = sub2_root_ordinal; 315 | action(user_root); 316 | visit_nodes.insert(std::unordered_map::value_type(sub2_root_ordinal, true)); 317 | } 318 | // add gc root 319 | if (need_add_gc_root) 320 | { 321 | int sub_to_sub2_edge_type = edge_util->GetTypeForInt(*(sub2_root_edges + j), true); 322 | if (sub_to_sub2_edge_type != snapshot_edge::EdgeTypes::KWEAK) 323 | { 324 | gcroots++; 325 | gcroots_map_.insert(GCRootsMap::value_type(sub2_root_ordinal, true)); 326 | } 327 | } 328 | } 329 | delete[] sub2_root_edges; 330 | // mark sub gc roots 331 | if (visit_nodes.count(sub_root_ordinal) == 0) 332 | { 333 | user_root->ordinal = sub_root_ordinal; 334 | action(user_root); 335 | visit_nodes.insert(std::unordered_map::value_type(sub_root_ordinal, true)); 336 | } 337 | } 338 | delete[] sub_root_edges; 339 | // mark sub roots 340 | for (int i = 0; i < length; i++) 341 | { 342 | int target_node = edge_util->GetTargetNode(*(edges + i), true); 343 | if (visit_nodes.count(target_node) == 0) 344 | { 345 | user_root->ordinal = target_node; 346 | action(user_root); 347 | visit_nodes.insert(std::unordered_map::value_type(target_node, true)); 348 | } 349 | } 350 | } 351 | } 352 | 353 | void SnapshotParser::BFS_(int *node_to_visit, int node_to_visit_length) 354 | { 355 | int index = 0; 356 | int temp = 0; 357 | while (index < node_to_visit_length) 358 | { 359 | int ordinal = node_to_visit[index++]; 360 | if (!node_util->CheckOrdinalId(ordinal)) 361 | continue; 362 | int distance = node_distances_[ordinal] + 1; 363 | int *edges = node_util->GetEdges(ordinal); 364 | int edge_length = node_util->GetEdgeCount(ordinal); 365 | for (int i = 0; i < edge_length; i++) 366 | { 367 | int edge_type = edge_util->GetTypeForInt(*(edges + i), true); 368 | // ignore weak edge 369 | if (edge_type == snapshot_edge::EdgeTypes::KWEAK) 370 | continue; 371 | int child_ordinal = edge_util->GetTargetNode(*(edges + i), true); 372 | if (node_distances_[child_ordinal] != NO_DISTANCE) 373 | continue; 374 | // need optimized filter 375 | // if(!Filter_(ordinal, *(edges + i))) 376 | // continue; 377 | node_distances_[child_ordinal] = distance; 378 | node_to_visit[node_to_visit_length++] = child_ordinal; 379 | temp++; 380 | } 381 | delete[] edges; 382 | } 383 | if (node_to_visit_length > node_count) 384 | { 385 | std::string error = "BFS failed. Nodes to visit (" + std::to_string(node_to_visit_length) + ") is more than nodes count (" + std::to_string(node_count) + ")"; 386 | Nan::ThrowTypeError(Nan::New(error).ToLocalChecked()); 387 | } 388 | } 389 | 390 | void SnapshotParser::BuildDistances() 391 | { 392 | node_distances_ = new int[node_count]; 393 | for (int i = 0; i < node_count; i++) 394 | { 395 | *(node_distances_ + i) = NO_DISTANCE; 396 | } 397 | int *node_to_visit = new int[node_count](); 398 | int node_to_visit_length = 0; 399 | // add user root 400 | snapshot_distance_t *user_root = new snapshot_distance_t; 401 | user_root->distance = 1; 402 | user_root->node_to_visit = node_to_visit; 403 | user_root->node_to_visit_length = &node_to_visit_length; 404 | user_root->node_distances_ = node_distances_; 405 | ForEachRoot_(EnqueueNode_, user_root, true); 406 | BFS_(node_to_visit, node_to_visit_length); 407 | // add rest 408 | node_to_visit_length = 0; 409 | user_root->distance = BASE_SYSTEMDISTANCE; 410 | ForEachRoot_(EnqueueNode_, user_root, false); 411 | BFS_(node_to_visit, node_to_visit_length); 412 | user_root = nullptr; 413 | } 414 | 415 | int SnapshotParser::GetRetainedSize(int id) 416 | { 417 | return retained_sizes_[id]; 418 | } 419 | 420 | int SnapshotParser::GetDistance(int id) 421 | { 422 | return node_distances_[id]; 423 | } 424 | 425 | int SnapshotParser::IsGCRoot(int id) 426 | { 427 | int count = static_cast(gcroots_map_.count(id)); 428 | if (count == 0) 429 | return 0; 430 | return 1; 431 | } 432 | 433 | void SnapshotParser::BuildDominatorTree() 434 | { 435 | CalculateFlags_(); 436 | snapshot_post_order_t *ptr = BuildPostOrderIndex_(); 437 | if (algorithm_ == TARJAN) 438 | BuildDominatorTree_(); 439 | else if (algorithm_ == DATA_ITERATION) 440 | BuildDominatorTree_(ptr); 441 | CalculateRetainedSizes_(ptr); 442 | // free memory 443 | delete[] ptr->post_order_index_to_ordinal; 444 | delete[] ptr->ordinal_to_post_order_index; 445 | delete ptr; 446 | } 447 | 448 | bool SnapshotParser::IsEssentialEdge_(int ordinal, int type) 449 | { 450 | return type != snapshot_edge::EdgeTypes::KWEAK && 451 | (type != snapshot_edge::EdgeTypes::KSHORTCUT || ordinal == root_index); 452 | } 453 | 454 | bool SnapshotParser::HasOnlyWeakRetainers_(int ordinal) 455 | { 456 | int begin_retainer_index = first_retainer_index_[ordinal]; 457 | int end_retainer_index = first_retainer_index_[ordinal + 1]; 458 | for (int retainer_index = begin_retainer_index; retainer_index < end_retainer_index; ++retainer_index) 459 | { 460 | int retainer_edge_index = retaining_edges_[retainer_index]; 461 | int retainer_edge_type = edge_util->GetTypeForInt(retainer_edge_index, true); 462 | if (retainer_edge_type != snapshot_edge::EdgeTypes::KWEAK && retainer_edge_type != snapshot_edge::EdgeTypes::KSHORTCUT) 463 | return false; 464 | } 465 | return true; 466 | } 467 | 468 | void SnapshotParser::CalculateFlags_() 469 | { 470 | flags_ = new int[node_count](); 471 | int *node_to_visit = new int[node_count](); 472 | int node_to_visit_length = 0; 473 | for (int edge_index = first_edge_indexes[root_index], 474 | end_edge_index = first_edge_indexes[root_index + 1]; 475 | edge_index < end_edge_index; edge_index += edge_field_length) 476 | { 477 | int target_node = edge_util->GetTargetNode(edge_index, true); 478 | if (!node_util->CheckOrdinalId(target_node)) 479 | continue; 480 | int edge_type = edge_util->GetTypeForInt(edge_index, true); 481 | if (edge_type == snapshot_edge::EdgeTypes::KELEMENT) 482 | { 483 | int node_type = node_util->GetTypeForInt(target_node); 484 | std::string node_name = node_util->GetName(target_node); 485 | if (!(node_type == snapshot_node::NodeTypes::KSYNTHETIC && node_name.compare("(Document DOM trees)") == 0)) 486 | continue; 487 | } 488 | else if (edge_type != snapshot_edge::EdgeTypes::KSHORTCUT) 489 | continue; 490 | node_to_visit[node_to_visit_length++] = target_node; 491 | flags_[target_node] |= page_object_flag_; 492 | } 493 | // mark object from global/window/dom 494 | while (node_to_visit_length) 495 | { 496 | int ordinal = node_to_visit[--node_to_visit_length]; 497 | int begin_edge_index = first_edge_indexes[ordinal]; 498 | int end_edge_index = first_edge_indexes[ordinal + 1]; 499 | for (int edge_index = begin_edge_index; 500 | edge_index < end_edge_index; edge_index += edge_field_length) 501 | { 502 | int child_ordinal = edge_util->GetTargetNode(edge_index, true); 503 | // has been marked 504 | if (flags_[child_ordinal] & page_object_flag_) 505 | continue; 506 | int child_type = edge_util->GetTypeForInt(edge_index, true); 507 | if (child_type == snapshot_edge::EdgeTypes::KWEAK) 508 | continue; 509 | node_to_visit[node_to_visit_length++] = child_ordinal; 510 | flags_[child_ordinal] |= page_object_flag_; 511 | } 512 | } 513 | } 514 | 515 | snapshot_post_order_t *SnapshotParser::BuildPostOrderIndex_() 516 | { 517 | int *stack_nodes = new int[node_count](); 518 | int *stack_current_edge = new int[node_count](); 519 | int *post_order_index_to_ordinal = new int[node_count](); 520 | int *ordinal_to_post_order_index = new int[node_count](); 521 | int *visited = new int[node_count](); 522 | int post_order_index = 0; 523 | // set stack 524 | int stack_top = 0; 525 | stack_nodes[0] = root_index; 526 | stack_current_edge[0] = first_edge_indexes[root_index]; 527 | visited[root_index] = 1; 528 | int iteration = 0; 529 | while (true) 530 | { 531 | ++iteration; 532 | // dfs 533 | while (stack_top >= 0) 534 | { 535 | int ordinal = stack_nodes[stack_top]; 536 | int edge_index = stack_current_edge[stack_top]; 537 | int end_edge_index = first_edge_indexes[ordinal + 1]; 538 | if (edge_index < end_edge_index) 539 | { 540 | // stack edge current offset to next edge 541 | stack_current_edge[stack_top] += edge_field_length; 542 | int edge_type = edge_util->GetTypeForInt(edge_index, true); 543 | if (!IsEssentialEdge_(ordinal, edge_type)) 544 | continue; 545 | int target_node = edge_util->GetTargetNode(edge_index, true); 546 | if (visited[target_node] == 1) 547 | continue; 548 | int node_flag = flags_[ordinal] & page_object_flag_; 549 | int child_node_flag = flags_[target_node] & page_object_flag_; 550 | if (ordinal != root_index && child_node_flag != 0 && node_flag == 0) 551 | continue; 552 | ++stack_top; 553 | stack_nodes[stack_top] = target_node; 554 | stack_current_edge[stack_top] = first_edge_indexes[target_node]; 555 | visited[target_node] = 1; 556 | } 557 | else 558 | { 559 | ordinal_to_post_order_index[ordinal] = post_order_index; 560 | post_order_index_to_ordinal[post_order_index++] = ordinal; 561 | --stack_top; 562 | } 563 | } 564 | if (post_order_index == node_count || iteration > 1) 565 | break; 566 | // may be have some unreachable object fromm root_index, can give warnings 567 | --post_order_index; 568 | stack_top = 0; 569 | stack_nodes[0] = root_index; 570 | stack_current_edge[0] = first_edge_indexes[root_index + 1]; 571 | for (int i = 0; i < node_count; ++i) 572 | { 573 | if (visited[i] == 1 || !HasOnlyWeakRetainers_(i)) 574 | continue; 575 | stack_nodes[++stack_top] = i; 576 | stack_current_edge[stack_top] = first_edge_indexes[i]; 577 | visited[i] = 1; 578 | } 579 | } 580 | if (post_order_index != node_count) 581 | { 582 | --post_order_index; 583 | for (int i = 0; i < node_count; ++i) 584 | { 585 | if (visited[i] == 1) 586 | continue; 587 | ordinal_to_post_order_index[i] = post_order_index; 588 | post_order_index_to_ordinal[post_order_index++] = i; 589 | } 590 | ordinal_to_post_order_index[root_index] = post_order_index; 591 | post_order_index_to_ordinal[post_order_index++] = root_index; 592 | } 593 | // return struct 594 | snapshot_post_order_t *ptr = new snapshot_post_order_t; 595 | ptr->ordinal_to_post_order_index = ordinal_to_post_order_index; 596 | ptr->post_order_index_to_ordinal = post_order_index_to_ordinal; 597 | return ptr; 598 | } 599 | 600 | void SnapshotParser::BuildDominatorTree_() 601 | { 602 | // get tarjan data 603 | tarjan::tarjan_bound_list_t *data = new tarjan::tarjan_bound_list_t; 604 | data->count = node_count; 605 | for (int node_ordinal = 0; node_ordinal < node_count; ++node_ordinal) 606 | { 607 | int first_edge_index = first_edge_indexes[node_ordinal]; 608 | int next_edge_index = first_edge_indexes[node_ordinal + 1]; 609 | int node_flag = flags_[node_ordinal] & page_object_flag_; 610 | for (int edge_index = first_edge_index; edge_index < next_edge_index; edge_index += edge_field_length) 611 | { 612 | int edge_type = edge_util->GetTypeForInt(edge_index, true); 613 | if (!IsEssentialEdge_(node_ordinal, edge_type)) 614 | continue; 615 | int target_node = edge_util->GetTargetNode(edge_index, true); 616 | int child_node_flag = flags_[target_node] & page_object_flag_; 617 | if (node_ordinal != root_index && child_node_flag != 0 && node_flag == 0) 618 | continue; 619 | data->inbounds[target_node].emplace_back(node_ordinal); 620 | data->outbounds[node_ordinal].emplace_back(target_node); 621 | } 622 | } 623 | tarjan::TarJan *t = new tarjan::TarJan(data); 624 | t->Compute(); 625 | // clear middle variables 626 | t->ClearMiddleVariables(); 627 | delete data; 628 | // get results 629 | dominators_tree_ = t->idominator; 630 | dominators_ = t->dominators; 631 | } 632 | 633 | void SnapshotParser::BuildDominatorTree_(snapshot_post_order_t *ptr) 634 | { 635 | int root_post_ordered_index = node_count - 1; 636 | int no_entry = node_count; 637 | int *dominators = new int[node_count]; 638 | for (int i = 0; i < root_post_ordered_index; ++i) 639 | dominators[i] = no_entry; 640 | dominators[root_post_ordered_index] = root_post_ordered_index; 641 | int *affected = new int[node_count](); 642 | int ordinal; 643 | { 644 | ordinal = root_index; 645 | int end_edge_index = first_edge_indexes[ordinal + 1]; 646 | for (int edge_index = first_edge_indexes[ordinal]; 647 | edge_index < end_edge_index; edge_index += edge_field_length) 648 | { 649 | int edge_type = edge_util->GetTypeForInt(edge_index, true); 650 | if (!IsEssentialEdge_(root_index, edge_type)) 651 | continue; 652 | int child_ordinal = edge_util->GetTargetNode(edge_index, true); 653 | affected[ptr->ordinal_to_post_order_index[child_ordinal]] = 1; 654 | } 655 | } 656 | bool changed = true; 657 | while (changed) 658 | { 659 | changed = false; 660 | for (int post_order_index = root_post_ordered_index - 1; 661 | post_order_index >= 0; --post_order_index) 662 | { 663 | if (affected[post_order_index] == 0) 664 | continue; 665 | affected[post_order_index] = 0; 666 | if (dominators[post_order_index] == root_post_ordered_index) 667 | continue; 668 | ordinal = ptr->post_order_index_to_ordinal[post_order_index]; 669 | int node_flag = flags_[ordinal] & page_object_flag_; 670 | int new_dominator_index = no_entry; 671 | int begin_retainer_index = first_retainer_index_[ordinal]; 672 | int end_retainer_index = first_retainer_index_[ordinal + 1]; 673 | bool orphan_node = true; 674 | for (int retainer_index = begin_retainer_index; 675 | retainer_index < end_retainer_index; ++retainer_index) 676 | { 677 | int retainer_edge_index = retaining_edges_[retainer_index]; 678 | int retainer_edge_type = edge_util->GetTypeForInt(retainer_edge_index, true); 679 | int retainer_node_ordinal = retaining_nodes_[retainer_index]; 680 | if (!IsEssentialEdge_(retainer_node_ordinal, retainer_edge_type)) 681 | continue; 682 | orphan_node = false; 683 | int retainer_node_flag = flags_[retainer_node_ordinal] & page_object_flag_; 684 | if (retainer_node_ordinal != root_index && node_flag != 0 && retainer_node_flag == 0) 685 | continue; 686 | int retaner_post_order_index = ptr->ordinal_to_post_order_index[retainer_node_ordinal]; 687 | if (dominators[retaner_post_order_index] != no_entry) 688 | { 689 | if (new_dominator_index == no_entry) 690 | { 691 | new_dominator_index = retaner_post_order_index; 692 | } 693 | else 694 | { 695 | while (retaner_post_order_index != new_dominator_index) 696 | { 697 | while (retaner_post_order_index < new_dominator_index) 698 | retaner_post_order_index = dominators[retaner_post_order_index]; 699 | while (new_dominator_index < retaner_post_order_index) 700 | new_dominator_index = dominators[new_dominator_index]; 701 | } 702 | } 703 | if (new_dominator_index == root_post_ordered_index) 704 | break; 705 | } 706 | } 707 | if (orphan_node) 708 | new_dominator_index = root_post_ordered_index; 709 | if (new_dominator_index != no_entry && dominators[post_order_index] != new_dominator_index) 710 | { 711 | dominators[post_order_index] = new_dominator_index; 712 | changed = true; 713 | ordinal = ptr->post_order_index_to_ordinal[post_order_index]; 714 | int begin_edge_index = first_edge_indexes[ordinal]; 715 | int end_edge_index = first_edge_indexes[ordinal + 1]; 716 | for (int edge_index = begin_edge_index; 717 | edge_index < end_edge_index; edge_index += edge_field_length) 718 | { 719 | int child_ordinal = edge_util->GetTargetNode(edge_index, true); 720 | affected[ptr->ordinal_to_post_order_index[child_ordinal]] = 1; 721 | } 722 | } 723 | } 724 | } 725 | dominators_tree_ = new int[node_count](); 726 | for (int post_order_index = 0, l = node_count; post_order_index < l; ++post_order_index) 727 | { 728 | ordinal = ptr->post_order_index_to_ordinal[post_order_index]; 729 | dominators_tree_[ordinal] = ptr->post_order_index_to_ordinal[dominators[post_order_index]]; 730 | } 731 | } 732 | 733 | void SnapshotParser::CalculateRetainedSizes_(snapshot_post_order_t *ptr) 734 | { 735 | retained_sizes_ = new int[node_count](); 736 | for (int ordinal = 0; ordinal < node_count; ++ordinal) 737 | { 738 | if (!node_util->CheckOrdinalId(ordinal)) 739 | continue; 740 | retained_sizes_[ordinal] = node_util->GetSelfSize(ordinal); 741 | } 742 | for (int post_order_index = 0; post_order_index < node_count - 1; ++post_order_index) 743 | { 744 | int ordinal = ptr->post_order_index_to_ordinal[post_order_index]; 745 | // dominator_ordinal immediately dominated ordinal 746 | int dominator_ordinal = dominators_tree_[ordinal]; 747 | if (dominator_ordinal != tarjan::ERROR_VALUE) 748 | retained_sizes_[dominator_ordinal] += retained_sizes_[ordinal]; 749 | } 750 | } 751 | 752 | int *SnapshotParser::GetSortedEdges(int id) 753 | { 754 | if (ordered_edges_map_.count(id) != 0) 755 | return ordered_edges_map_.at(id); 756 | int *edges = node_util->GetEdges(id); 757 | int length = node_util->GetEdgeCount(id); 758 | std::sort(edges, edges + length, [this](int lhs, int rhs) 759 | { 760 | int lhs_ordinal = this->edge_util->GetTargetNode(lhs, true); 761 | int lhs_retained_size = this->retained_sizes_[lhs_ordinal]; 762 | int rhs_ordinal = this->edge_util->GetTargetNode(rhs, true); 763 | int rhs_retained_size = this->retained_sizes_[rhs_ordinal]; 764 | return lhs_retained_size > rhs_retained_size; }); 765 | ordered_edges_map_.insert(OrderedEdgesMap::value_type(id, edges)); 766 | return edges; 767 | } 768 | 769 | void SnapshotParser::MarkEdge_(int ordinal) 770 | { 771 | if (!node_util->CheckOrdinalId(ordinal)) 772 | return; 773 | int length = node_util->GetEdgeCount(ordinal); 774 | int *edges = node_util->GetEdges(ordinal); 775 | for (int i = 0; i < length; i++) 776 | { 777 | int edge = *(edges + i); 778 | int child = edge_util->GetTargetNode(edge, true); 779 | long long key = (static_cast(ordinal) << 32) + child; 780 | if (edge_searching_map_.count(key) != 0) 781 | continue; 782 | edge_searching_map_.insert(EdgeSearchingMap::value_type(key, edge)); 783 | } 784 | delete[] edges; 785 | } 786 | 787 | snapshot_dominates_t *SnapshotParser::GetSortedDominates(int id) 788 | { 789 | if (ordered_dominates_map_.count(id) != 0) 790 | { 791 | return ordered_dominates_map_.at(id); 792 | } 793 | 794 | snapshot_dominates_t *doms = new snapshot_dominates_t; 795 | snapshot_dominate_t **dominates; 796 | // tarjan result 797 | if (algorithm_ == TARJAN) 798 | { 799 | if (dominators_.count(id) == 0) 800 | return doms; 801 | int length = doms->length = static_cast(dominators_.at(id).size()); 802 | dominates = new snapshot_dominate_t *[length]; 803 | for (auto child : dominators_.at(id)) 804 | { 805 | // MarkEdge_(child); 806 | snapshot_dominate_t *dominate = new snapshot_dominate_t; 807 | dominate->dominate = child; 808 | dominate->edge = GetEdgeByParentAndChild_(id, child); 809 | dominates[--length] = dominate; 810 | } 811 | } 812 | // data iteration result 813 | if (algorithm_ == DATA_ITERATION) 814 | { 815 | int length = 0; 816 | for (int ordinal = 0; ordinal < node_count; ordinal++) 817 | { 818 | if (ordinal != root_index && dominators_tree_[ordinal] == id) 819 | length++; 820 | } 821 | doms->length = length; 822 | dominates = new snapshot_dominate_t *[length]; 823 | for (int ordinal = 0; ordinal < node_count; ordinal++) 824 | { 825 | if (ordinal != root_index && dominators_tree_[ordinal] == id) 826 | { 827 | // MarkEdge_(ordinal); 828 | snapshot_dominate_t *dominate = new snapshot_dominate_t; 829 | dominate->dominate = ordinal; 830 | dominate->edge = GetEdgeByParentAndChild_(id, ordinal); 831 | dominates[--length] = dominate; 832 | } 833 | } 834 | } 835 | 836 | std::sort(dominates, dominates + doms->length, [this](snapshot_dominate_t *lhs, snapshot_dominate_t *rhs) 837 | { 838 | int lhs_retained_size = this->retained_sizes_[lhs->dominate]; 839 | int rhs_retained_size = this->retained_sizes_[rhs->dominate]; 840 | // if(lhs_retained_size == rhs_retained_size && lhs->edge != -1 && rhs->edge != -1) { 841 | // int lhs_type = edge_util->GetTypeForInt(lhs->edge, true); 842 | // int rhs_type = edge_util->GetTypeForInt(rhs->edge, true); 843 | // if(lhs_type == snapshot_edge::EdgeTypes::KELEMENT && rhs_type == snapshot_edge::EdgeTypes::KELEMENT) { 844 | // std::string lhs_edge_name = edge_util->GetNameOrIndex(lhs->edge, true); 845 | // std::string rhs_edge_name = edge_util->GetNameOrIndex(rhs->edge, true); 846 | // int lhs_index = atoi(lhs_edge_name.c_str() + 1); 847 | // int rhs_index = atoi(rhs_edge_name.c_str() + 1); 848 | // return lhs_index > rhs_index; 849 | // } 850 | // } 851 | return lhs_retained_size > rhs_retained_size; }); 852 | doms->dominates = dominates; 853 | ordered_dominates_map_.insert(OrderedDominatesMap::value_type(id, doms)); 854 | return doms; 855 | } 856 | 857 | int SnapshotParser::GetEdgeByParentAndChild_(int parent, int child) 858 | { 859 | long long key = (static_cast(parent) << 32) + child; 860 | if (edge_searching_map_.count(key) == 0) 861 | return -1; 862 | return edge_searching_map_.at(key); 863 | } 864 | 865 | int SnapshotParser::GetImmediateDominator(int id) 866 | { 867 | if (id >= node_count) 868 | return -1; 869 | return dominators_tree_[id]; 870 | } 871 | 872 | void SnapshotParser::SetAlgorithm(DOMINATOR_ALGORITHM algo) 873 | { 874 | algorithm_ = algo; 875 | } 876 | 877 | } --------------------------------------------------------------------------------