Skip to content

Commit

Permalink
add: statistics & mark gc root with light blue
Browse files Browse the repository at this point in the history
  • Loading branch information
hyj1991 committed Apr 23, 2018
1 parent 59e2268 commit 1abb0f0
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 46 deletions.
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
# DevTools X
# <span style="font-family:Menlo;">DevTools X</span>

JavaScript Developer Toolbox
<span style="font-family:Menlo;">JavaScript Developer Toolbox</span>

* Aid in the analysis of memory
* <span style="font-family:Menlo;">Aid in the analysis of memory</span>

## Installation
## <span style="font-family:Menlo;">Installation</span>

```bash
npm install devtoolx -g
```

## Compatibility
## <span style="font-family:Menlo;">Compatibility</span>

* Node.js v4.x
* Node.js v6.x
* Node.js v8.x
* <span style="font-family:Menlo;">Node.js v4.x</span>
* <span style="font-family:Menlo;">Node.js v6.x</span>
* <span style="font-family:Menlo;">Node.js v8.x</span>

Attention: local compilation needs gcc version >= v4.9.2
<span style="font-family:Menlo;">Attention: local compilation needs gcc version >= v4.9.2</span>

## Usage
## <span style="font-family:Menlo;">Usage</span>

### Search node by address
### <span style="font-family:Menlo;">Search node by address or ordinal id</span>

```bash
devtoolx -s <heapsnapshot file> [-p <port>]
```

example:
<span style="font-family:Menlo;">example:</span>

![devtoox.gif](https://raw.githubusercontent.com/hyj1991/devtoolx/master/assets/devtoolx.gif)

### <span style="font-family:Menlo;">Color</span>

### Help
* <span style="font-family:Menlo;background:#c0eafd">light bule: means gc root</span>

### <span style="font-family:Menlo;">Help</span>

```bash
devtoolx -h
devtoolx --help
```

## License
## <span style="font-family:Menlo;">License</span>

[MIT License](LICENSE)
[<span style="font-family:Menlo;">MIT License</span>](LICENSE)

Copyright (c) 2018 hyj1991
<span style="font-family:Menlo;">Copyright (c) 2018 hyj1991</span>
Binary file modified assets/devtoolx.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "devtoolx",
"version": "0.1.7",
"version": "0.1.8",
"description": "dev tools box",
"main": "devtoolx.js",
"bin": {
Expand Down
60 changes: 36 additions & 24 deletions src/memory/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void Parser::Init(Local<Object> exports) {
Nan::SetPrototypeMethod(tpl, "getNodeByOrdinalId", GetNodeByOrdinalId);
Nan::SetPrototypeMethod(tpl, "getNodeByAddress", GetNodeByAddress);
Nan::SetPrototypeMethod(tpl, "getNodeIdByAddress", GetNodeIdByAddress);
Nan::SetPrototypeMethod(tpl, "getStatistics", GetStatistics);

constructor.Reset(tpl->GetFunction());
exports->Set(Nan::New("V8Parser").ToLocalChecked(), tpl->GetFunction());
Expand Down Expand Up @@ -69,36 +70,38 @@ void Parser::Parse(const Nan::FunctionCallbackInfo<Value>& info) {
jsonfile >> profile;
jsonfile.close();
// get snapshot parser
parser->snapshotParser = new snapshot_parser::SnapshotParser(profile);
parser->snapshot_parser = new snapshot_parser::SnapshotParser(profile);
if(info[0]->IsObject()) {
Local<Object> options = info[0]->ToObject();
Nan::Utf8String mode(options->Get(Nan::New<String>("mode").ToLocalChecked()));
std::string mode_search = "search";
if(strcmp(*mode, mode_search.c_str()) == 0) {
parser->snapshotParser->CreateAddressMap();
parser->snapshotParser->BuildTotalRetainer();
parser->snapshotParser->BuildDistances();
parser->snapshot_parser->CreateAddressMap();
parser->snapshot_parser->BuildTotalRetainer();
parser->snapshot_parser->BuildDistances();
}
}
}

Local<Object> Parser::GetNodeById_(long id, int current, int limit, GetNodeTypes get_node_type) {
Local<Object> node = Nan::New<Object>();
node->Set(Nan::New<String>("id").ToLocalChecked(), Nan::New<Number>(id));
std::string type = snapshotParser->node_util->GetType(id, false);
std::string type = snapshot_parser->node_util->GetType(id, false);
node->Set(Nan::New<String>("type").ToLocalChecked(), Nan::New<String>(type).ToLocalChecked());
std::string name = snapshotParser->node_util->GetName(id, false);
std::string name = snapshot_parser->node_util->GetName(id, false);
node->Set(Nan::New<String>("name").ToLocalChecked(), Nan::New<String>(name).ToLocalChecked());
std::string address = "@" + std::to_string(snapshotParser->node_util->GetAddress(id, false));
std::string address = "@" + std::to_string(snapshot_parser->node_util->GetAddress(id, false));
node->Set(Nan::New<String>("address").ToLocalChecked(), Nan::New<String>(address).ToLocalChecked());
long self_size = snapshotParser->node_util->GetSelfSize(id, false);
long self_size = snapshot_parser->node_util->GetSelfSize(id, false);
node->Set(Nan::New<String>("self_size").ToLocalChecked(), Nan::New<Number>(self_size));
int distance = snapshotParser->GetDistance(id);
int distance = snapshot_parser->GetDistance(id);
node->Set(Nan::New<String>("distance").ToLocalChecked(), Nan::New<Number>(distance));
bool is_gcroot = snapshot_parser->IsGCRoot(id);
node->Set(Nan::New<String>("is_gcroot").ToLocalChecked(), Nan::New<Number>(is_gcroot));
// get edges
if(get_node_type == KALL || get_node_type == KEDGES) {
long* edges_local = snapshotParser->node_util->GetEdges(id, false);
int edges_length = snapshotParser->node_util->GetEdgeCount(id, false);
long* edges_local = snapshot_parser->node_util->GetEdges(id, false);
int edges_length = snapshot_parser->node_util->GetEdgeCount(id, false);
int start_edge_index = current;
int stop_edge_index = current + limit;
if(start_edge_index >= edges_length) {
Expand All @@ -110,9 +113,9 @@ Local<Object> Parser::GetNodeById_(long id, int current, int limit, GetNodeTypes
Local<Array> edges = Nan::New<Array>(stop_edge_index - start_edge_index);
for(int i = start_edge_index; i < stop_edge_index; i++) {
Local<Object> edge = Nan::New<Object>();
std::string edge_type = snapshotParser->edge_util->GetType(edges_local[i], true);
std::string name_or_index = snapshotParser->edge_util->GetNameOrIndex(edges_local[i], true);
long to_node = snapshotParser->edge_util->GetTargetNode(edges_local[i], true);
std::string edge_type = snapshot_parser->edge_util->GetType(edges_local[i], true);
std::string name_or_index = snapshot_parser->edge_util->GetNameOrIndex(edges_local[i], true);
long to_node = snapshot_parser->edge_util->GetTargetNode(edges_local[i], true);
edge->Set(Nan::New<String>("type").ToLocalChecked(), Nan::New<String>(edge_type).ToLocalChecked());
edge->Set(Nan::New<String>("name_or_index").ToLocalChecked(), Nan::New<String>(name_or_index).ToLocalChecked());
edge->Set(Nan::New<String>("to_node").ToLocalChecked(), Nan::New<Number>(to_node));
Expand All @@ -129,8 +132,8 @@ Local<Object> Parser::GetNodeById_(long id, int current, int limit, GetNodeTypes
}
// get retainers
if(get_node_type == KALL || get_node_type == KRETAINERS) {
long* retainers_local = snapshotParser->GetRetainers(id);
int retainers_length = snapshotParser->GetRetainersCount(id);
long* retainers_local = snapshot_parser->GetRetainers(id);
int retainers_length = snapshot_parser->GetRetainersCount(id);
int start_retainer_index = current;
int stop_retainer_index = current + limit;
if(start_retainer_index >= retainers_length) {
Expand All @@ -144,8 +147,8 @@ Local<Object> Parser::GetNodeById_(long id, int current, int limit, GetNodeTypes
long node = retainers_local[i * 2];
long edge = retainers_local[i * 2 + 1];
Local<Object> retainer = Nan::New<Object>();
std::string edge_type = snapshotParser->edge_util->GetType(edge, true);
std::string name_or_index = snapshotParser->edge_util->GetNameOrIndex(edge, true);
std::string edge_type = snapshot_parser->edge_util->GetType(edge, true);
std::string name_or_index = snapshot_parser->edge_util->GetNameOrIndex(edge, true);
retainer->Set(Nan::New<String>("type").ToLocalChecked(), Nan::New<String>(edge_type).ToLocalChecked());
retainer->Set(Nan::New<String>("name_or_index").ToLocalChecked(), Nan::New<String>(name_or_index).ToLocalChecked());
retainer->Set(Nan::New<String>("from_node").ToLocalChecked(), Nan::New<Number>(node));
Expand All @@ -170,7 +173,7 @@ void Parser::GetNodeId(const Nan::FunctionCallbackInfo<Value>& info) {
}
long source = static_cast<long>(info[0]->ToInteger()->Value());
Parser* parser = ObjectWrap::Unwrap<Parser>(info.Holder());
long nodeid = parser->snapshotParser->node_util->GetNodeId(source);
long nodeid = parser->snapshot_parser->node_util->GetNodeId(source);
info.GetReturnValue().Set(Nan::New<Number>(nodeid));
}

Expand Down Expand Up @@ -208,12 +211,12 @@ void Parser::GetNodeByOrdinalId(const Nan::FunctionCallbackInfo<Value>& info) {
int error_id_count = 0;
for(int i = 0; i < length; i++) {
long id = static_cast<long>(list->Get(Nan::New<Number>(i))->ToInteger()->Value());
if(id >= parser->snapshotParser->node_count) {
if(id >= parser->snapshot_parser->node_count) {
error_id_count++;
break;
}
if(!info[2]->IsNumber()) {
limit = parser->snapshotParser->node_util->GetEdgeCount(id, false);
limit = parser->snapshot_parser->node_util->GetEdgeCount(id, false);
}
nodes->Set(i, parser->GetNodeById_(id, current, limit, type));
}
Expand All @@ -236,7 +239,7 @@ void Parser::GetNodeByAddress(const Nan::FunctionCallbackInfo<Value>& info) {
Nan::ThrowTypeError(Nan::New<String>("argument 0 must be startwith \"@\"!").ToLocalChecked());
return;
}
long id = parser->snapshotParser->SearchOrdinalByAddress(atol((*addr) + 1));
long id = parser->snapshot_parser->SearchOrdinalByAddress(atol((*addr) + 1));
if(id == -1) {
std::string addrs = *addr;
std::string error = "address \"" + addrs + "\" is wrong!";
Expand All @@ -247,7 +250,7 @@ void Parser::GetNodeByAddress(const Nan::FunctionCallbackInfo<Value>& info) {
if(info[1]->IsNumber()) {
current = static_cast<int>(info[1]->ToInteger()->Value());
}
int limit = parser->snapshotParser->node_util->GetEdgeCount(id, false);
int limit = parser->snapshot_parser->node_util->GetEdgeCount(id, false);
if(info[2]->IsNumber()) {
limit = static_cast<int>(info[2]->ToInteger()->Value());
}
Expand All @@ -267,7 +270,7 @@ void Parser::GetNodeIdByAddress(const Nan::FunctionCallbackInfo<Value>& info) {
return;
}
Parser* parser = ObjectWrap::Unwrap<Parser>(info.Holder());
long id = parser->snapshotParser->SearchOrdinalByAddress(atol((*addr) + 1));
long id = parser->snapshot_parser->SearchOrdinalByAddress(atol((*addr) + 1));
if(id == -1) {
std::string addrs = *addr;
std::string error = "address \"" + addrs + "\" is wrong!";
Expand All @@ -281,4 +284,13 @@ void Parser::GetFileName(const Nan::FunctionCallbackInfo<Value>& info) {
Parser* parser = ObjectWrap::Unwrap<Parser>(info.Holder());
info.GetReturnValue().Set(Nan::New(parser->filename_).ToLocalChecked());
}

void Parser::GetStatistics(const Nan::FunctionCallbackInfo<Value>& info) {
Parser* parser = ObjectWrap::Unwrap<Parser>(info.Holder());
Local<Object> statistics = Nan::New<Object>();
statistics->Set(Nan::New<String>("node_count").ToLocalChecked(), Nan::New<Number>(parser->snapshot_parser->node_count));
statistics->Set(Nan::New<String>("edge_count").ToLocalChecked(), Nan::New<Number>(parser->snapshot_parser->edge_count));
statistics->Set(Nan::New<String>("gcroots").ToLocalChecked(), Nan::New<Number>(parser->snapshot_parser->gcroots));
info.GetReturnValue().Set(statistics);
}
}
3 changes: 2 additions & 1 deletion src/memory/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ class Parser : public node::ObjectWrap {
static void GetNodeByOrdinalId(const Nan::FunctionCallbackInfo<v8::Value>& info);
static void GetNodeByAddress(const Nan::FunctionCallbackInfo<v8::Value>& info);
static void GetNodeIdByAddress(const Nan::FunctionCallbackInfo<v8::Value>& info);
static void GetStatistics(const Nan::FunctionCallbackInfo<v8::Value>& info);
static Nan::Persistent<v8::Function> constructor;
// snapshot info
int filelength_;
char* filename_;
snapshot_parser::SnapshotParser* snapshotParser;
snapshot_parser::SnapshotParser* snapshot_parser;
};
}

Expand Down
22 changes: 22 additions & 0 deletions src/memory/snapshot_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ void SnapshotParser::ForEachRoot_(void (*action)(snapshot_distance_t* t), snapsh
long sub_root_ordinal = edge_util->GetTargetNode(*(sub_root_edges + i), true);
long* sub2_root_edges = node_util->GetEdges(sub_root_ordinal, false);
int sub2_root_edge_length = node_util->GetEdgeCount(sub_root_ordinal, false);
bool need_add_gc_root = true;
std::string sub_root_name = node_util->GetName(sub_root_ordinal, false);
if(sub_root_name.compare("(Internalized strings)") == 0
|| sub_root_name.compare("(External strings)") == 0
|| sub_root_name.compare("(Smi roots)") == 0) {
need_add_gc_root = false;
}
for(int j = 0; j < sub2_root_edge_length; j++) {
long sub2_root_ordinal = edge_util->GetTargetNode(*(sub2_root_edges + j), true);
// mark sub sub gc roots
Expand All @@ -232,6 +239,14 @@ void SnapshotParser::ForEachRoot_(void (*action)(snapshot_distance_t* t), snapsh
action(user_root);
visit_nodes.insert(std::unordered_map<long, bool>::value_type(sub2_root_ordinal, true));
}
// add gc root
if(need_add_gc_root) {
int sub_to_sub2_edge_type = edge_util->GetTypeForInt(*(sub2_root_edges + j), true);
if(sub_to_sub2_edge_type != snapshot_edge::EdgeTypes::KWEAK) {
gcroots++;
gcroots_map_.insert(GCRootsMap::value_type(sub2_root_ordinal, true));
}
}
}
// mark sub gc roots
if(visit_nodes.count(sub_root_ordinal) == 0) {
Expand Down Expand Up @@ -309,4 +324,11 @@ void SnapshotParser::BuildDistances() {
int SnapshotParser::GetDistance(long id) {
return node_distances_[id];
}

int SnapshotParser::IsGCRoot(long id) {
int count = gcroots_map_.count(id);
if(count == 0)
return 0;
return 1;
}
}
6 changes: 6 additions & 0 deletions src/memory/snapshot_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ typedef struct {
int* node_to_visit_length;
int* node_distances_;
} snapshot_distance_t;

typedef std::unordered_map<long, long> AddressMap;
typedef std::unordered_map<long, bool> GCRootsMap;

const int NO_DISTANCE = -5;
const int BASE_SYSTEMDISTANCE = 100000000;
Expand All @@ -35,6 +37,7 @@ class SnapshotParser {
long* GetRetainers(long id);
void BuildDistances();
int GetDistance(long id);
int IsGCRoot(long id);
static int IndexOf(json array, std::string target);
json nodes;
json edges;
Expand All @@ -60,6 +63,7 @@ class SnapshotParser {
long* first_edge_indexes;
snapshot_node::Node* node_util;
snapshot_edge::Edge* edge_util;
int gcroots;

private:
long* GetFirstEdgeIndexes();
Expand All @@ -69,6 +73,8 @@ class SnapshotParser {
bool Filter(long ordinal, long edge);
// address -> node ordinal id
AddressMap address_map_;
// ordinal id -> bool
GCRootsMap gcroots_map_;
// total retainers
long* retaining_nodes_;
long* retaining_edges_;
Expand Down
2 changes: 2 additions & 0 deletions util.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Usage: devtoolx [CMD]... [ARGS]
${logger.infoConsole(`-s <file> [-p <port>]`)} analysis js heapsnapshot, start web sever
with default port 3000, change port by "-p <port>"
${logger.infoConsole(`--no-open`)} don't auto open browser when analysis completed
`;

exports.formatTime = function (ts) {
Expand Down
20 changes: 19 additions & 1 deletion worker/public/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
}

.node-name {
color: rgb(48, 57, 66)
color: rgb(48, 57, 66);
}

.node-gcroot {
color: rgb(48, 57, 66);
background: #c0eafd;
}

.address {
Expand All @@ -42,4 +47,17 @@
color: #673ab7;
margin-top:5px;
margin-left: -15px;
margin-bottom: 1px;
}

.statistics{
font-family: Menlo;
margin-left: 20px;
margin-bottom: 0px;
margin-top: 23px;
font-size: 15px;
}

.statistics-value{
color: #d20d0d;
}
3 changes: 3 additions & 0 deletions worker/public/js/dashboard/component/tree_edges.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
raw = raw || {};
raw.id = data.id;
raw.key = `${Math.random().toString(36).substr(2)}`;
if (!data.name && data.type === 'array') data.name = '[]';
if (!data.name && data.type === 'closure') data.name = '()';
if (typeof data.name === 'string' && data.name.length > 54) {
raw.name = data.name.substr(0, 54);
} else {
raw.name = data.name;
}
raw.nameClass = 'node-name';
raw.address = data.address;
raw.additional = `(type: ${data.type}, self_size: ${this.formatSize(data.self_size)}, distance: ${data.distance})`;
raw.edges = data.edges;
Expand Down
3 changes: 3 additions & 0 deletions worker/public/js/dashboard/component/tree_retainers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
raw = raw || {};
raw.id = data.id;
raw.key = `${Math.random().toString(36).substr(2)}`;
if (!data.name && data.type === 'array') data.name = '[]';
if (!data.name && data.type === 'closure') data.name = '()';
if (typeof data.name === 'string' && data.name.length > 50) {
raw.name = data.name.substr(0, 50);
} else {
raw.name = data.name;
}
raw.nameClass = data.is_gcroot && 'node-name node-gcroot' || 'node-name';
raw.address = data.address;
raw.additional = `(type: ${data.type}, self_size: ${this.formatSize(data.self_size)}, distance: ${data.distance})`;
raw.retainers = data.retainers;
Expand Down
Loading

0 comments on commit 1abb0f0

Please sign in to comment.