Skip to content

Commit

Permalink
src: add a detailed output to findjsobjects
Browse files Browse the repository at this point in the history
Add a detailed output to findjsobjects. Inspired on mdb_v8 output, which
prints a representative object, the number of properties and array
indexes, and the first few properties for a map. This option will also
group types by its properties instead of only grouping by constructor
name.

Fixes: nodejs#149
PR-URL: nodejs#155
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
  • Loading branch information
Matheus Marchini authored and joyeecheung committed Jan 24, 2018
1 parent 28896bb commit 20b14d9
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 38 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ The following subcommands are supported:
findjsinstances -- List every object with the specified type name.
Use -v or --verbose to display detailed `v8 inspect` output for each object.
Accepts the same options as `v8 inspect`
findjsobjects -- List all object types and instance counts grouped by typename and sorted by instance count.
findjsobjects -- List all object types and instance counts grouped by typename and sorted by instance count. Use
-d or --detailed to get an output grouped by type name, properties, and array length, as well as
more information regarding each type.
With lldb < 3.9, requires the `LLNODE_RANGESFILE` environment variable to be set to a file
containing memory ranges for the core file being debugged.
There are scripts for generating this file on Linux and Mac in the scripts directory of the llnode
Expand Down
10 changes: 7 additions & 3 deletions src/llnode.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ char** CommandBase::ParseInspectOptions(char** cmd,
{"print-map", no_argument, nullptr, 'm'},
{"print-source", no_argument, nullptr, 's'},
{"verbose", no_argument, nullptr, 'v'},
{"detailed", no_argument, nullptr, 'd'},
{nullptr, 0, nullptr, 0}};

int argc = 1;
Expand All @@ -56,7 +57,7 @@ char** CommandBase::ParseInspectOptions(char** cmd,
optind = 0;
opterr = 1;
do {
int arg = getopt_long(argc, args, "Fmsvl:", opts, nullptr);
int arg = getopt_long(argc, args, "Fmsdvl:", opts, nullptr);
if (arg == -1) break;

switch (arg) {
Expand All @@ -72,6 +73,7 @@ char** CommandBase::ParseInspectOptions(char** cmd,
case 's':
options->print_source = true;
break;
case 'd':
case 'v':
options->detailed = true;
break;
Expand Down Expand Up @@ -358,8 +360,10 @@ bool PluginInitialize(SBDebugger d) {
"Alias for `v8 source list`");

v8.AddCommand("findjsobjects", new llnode::FindObjectsCmd(),
"List all object types and instance counts grouped by type"
"name and sorted by instance count.\n"
"List all object types and instance counts grouped by type "
"name and sorted by instance count. Use -d or --detailed to "
"get an output grouped by type name, properties, and array "
"length, as well as more information regarding each type.\n"
#ifndef LLDB_SBMemoryRegionInfoList_h_
"Requires `LLNODE_RANGESFILE` environment variable to be set "
"to a file containing memory ranges for the core file being "
Expand Down
194 changes: 163 additions & 31 deletions src/llscan.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ bool FindObjectsCmd::DoExecute(SBDebugger d, char** cmd,
return false;
}

v8::Value::InspectOptions inspect_options;
ParseInspectOptions(cmd, &inspect_options);

if (inspect_options.detailed) {
DetailedOutput(result);
} else {
SimpleOutput(result);
}

result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}


void FindObjectsCmd::SimpleOutput(SBCommandReturnObject& result) {
/* Create a vector to hold the entries sorted by instance count
* TODO(hhellyer) - Make sort type an option (by count, size or name)
*/
Expand Down Expand Up @@ -78,9 +93,39 @@ bool FindObjectsCmd::DoExecute(SBDebugger d, char** cmd,

result.Printf(" ---------- ---------- \n");
result.Printf(" %10" PRId64 " %10" PRId64 " \n", total_objects, total_size);
}

result.SetStatus(eReturnStatusSuccessFinishResult);
return true;

void FindObjectsCmd::DetailedOutput(SBCommandReturnObject& result) {
std::vector<DetailedTypeRecord*> sorted_by_count;
for (auto kv : llscan.GetDetailedMapsToInstances()) {
sorted_by_count.push_back(kv.second);
}

std::sort(sorted_by_count.begin(), sorted_by_count.end(),
TypeRecord::CompareInstanceCounts);
uint64_t total_objects = 0;
uint64_t total_size = 0;

result.Printf(
" Sample Obj. Instances Total Size Properties Elements Name\n");
result.Printf(
" ------------- ---------- ----------- ----------- --------- -----\n");

for (auto t : sorted_by_count) {
result.Printf(" %13" PRIx64 " %10" PRId64 " %11" PRId64 " %11" PRId64
" %9" PRId64 " %s\n",
*(t->GetInstances().begin()), t->GetInstanceCount(),
t->GetTotalInstanceSize(), t->GetOwnDescriptorsCount(),
t->GetIndexedPropertiesCount(), t->GetTypeName().c_str());
total_objects += t->GetInstanceCount();
total_size += t->GetTotalInstanceSize();
}

result.Printf(
" ------------ ---------- ----------- ----------- ----------- ----\n");
result.Printf(" %11" PRId64 " %11" PRId64 " \n", total_objects,
total_size);
}


Expand Down Expand Up @@ -1062,9 +1107,8 @@ ReferencesVector* FindReferencesCmd::StringScanner::GetReferences() {
}


FindJSObjectsVisitor::FindJSObjectsVisitor(SBTarget& target,
TypeRecordMap& mapstoinstances)
: target_(target), mapstoinstances_(mapstoinstances) {
FindJSObjectsVisitor::FindJSObjectsVisitor(SBTarget& target, LLScan* llscan)
: target_(target), llscan_(llscan) {
found_count_ = 0;
address_byte_size_ = target_.GetProcess().GetAddressByteSize();
// Load V8 constants from postmortem data
Expand Down Expand Up @@ -1092,13 +1136,10 @@ uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) {

MapCacheEntry map_info;
if (map_cache_.count(map.raw()) == 0) {
// Check type first
map_info.is_histogram = IsAHistogramType(map, err);

// On success load type name
if (map_info.is_histogram)
map_info.type_name = heap_object.GetTypeName(err);

map_info.Load(map, heap_object, err);
if (err.Fail()) {
return address_byte_size_;
}
// Cache result
map_cache_.emplace(map.raw(), map_info);

Expand All @@ -1109,24 +1150,8 @@ uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) {

if (!map_info.is_histogram) return address_byte_size_;

/* No entry in the map, create a new one. */
if (mapstoinstances_.count(map_info.type_name) == 0) {
TypeRecord* t = new TypeRecord(map_info.type_name);

t->AddInstance(word, map.InstanceSize(err));
mapstoinstances_.emplace(map_info.type_name, t);

} else {
/* Update an existing instance, if we haven't seen this instance before. */
TypeRecord* t = mapstoinstances_.at(map_info.type_name);
/* Determine if this is a new instance.
* (We are scanning pointers to objects, we may have seen this location
* before.)
*/
if (t->GetInstances().count(word) == 0) {
t->AddInstance(word, map.InstanceSize(err));
}
}
InsertOnMapsToInstances(word, map, map_info, err);
InsertOnDetailedMapsToInstances(word, map, map_info, err);

if (err.Fail()) {
return address_byte_size_;
Expand All @@ -1141,6 +1166,54 @@ uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) {
}


void FindJSObjectsVisitor::InsertOnMapsToInstances(
uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info,
v8::Error& err) {
TypeRecord* t;

auto entry = std::make_pair(map_info.type_name, nullptr);
auto pp = &llscan_->GetMapsToInstances().insert(entry).first->second;
// No entry in the map, create a new one.
if (*pp == nullptr) *pp = new TypeRecord(map_info.type_name);
t = *pp;

// Determine if this is a new instance.
// (We are scanning pointers to objects, we may have seen this location
// before.)
if (t->GetInstances().count(word) == 0) {
t->AddInstance(word, map.InstanceSize(err));
}
}

void FindJSObjectsVisitor::InsertOnDetailedMapsToInstances(
uint64_t word, v8::Map map, FindJSObjectsVisitor::MapCacheEntry map_info,
v8::Error& err) {
DetailedTypeRecord* t;

auto type_name_with_properties = map_info.GetTypeNameWithProperties();

auto entry = std::make_pair(type_name_with_properties, nullptr);
auto pp = &llscan_->GetDetailedMapsToInstances().insert(entry).first->second;
// No entry in the map, create a new one.
if (*pp == nullptr) {
auto type_name_with_three_properties = map_info.GetTypeNameWithProperties(
MapCacheEntry::kDontShowArrayLength,
kNumberOfPropertiesForDetailedOutput);
*pp = new DetailedTypeRecord(type_name_with_three_properties,
map_info.own_descriptors_count_,
map_info.indexed_properties_count_);
}
t = *pp;

// Determine if this is a new instance.
// (We are scanning pointers to objects, we may have seen this location
// before.)
if (t->GetInstances().count(word) == 0) {
t->AddInstance(word, map.InstanceSize(err));
}
}


bool FindJSObjectsVisitor::IsAHistogramType(v8::Map& map, v8::Error& err) {
int64_t type = map.GetType(err);
if (err.Fail()) return false;
Expand Down Expand Up @@ -1202,14 +1275,73 @@ bool LLScan::ScanHeapForObjects(lldb::SBTarget target,

/* Populate the map of objects. */
if (mapstoinstances_.empty()) {
FindJSObjectsVisitor v(target, GetMapsToInstances());
FindJSObjectsVisitor v(target, this);

ScanMemoryRanges(v);
}

return true;
}

std::string
FindJSObjectsVisitor::MapCacheEntry::GetTypeNameWithProperties(
ShowArrayLength show_array_length, size_t max_properties) {
std::string type_name_with_properties(type_name);

if (show_array_length == kShowArrayLength) {
type_name_with_properties +=
"[" + std::to_string(indexed_properties_count_) + "]";
}

size_t i = 0;
max_properties = max_properties ? std::min(max_properties, properties_.size())
: properties_.size();
for (auto it = properties_.begin(); i < max_properties; ++it, i++) {
type_name_with_properties += (i ? ", " : ": ") + *it;
}
if (max_properties < properties_.size()) {
type_name_with_properties += ", ...";
}

return type_name_with_properties;
}


bool FindJSObjectsVisitor::MapCacheEntry::Load(v8::Map map,
v8::HeapObject heap_object,
v8::Error& err) {
// Check type first
is_histogram = FindJSObjectsVisitor::IsAHistogramType(map, err);

// On success load type name
if (is_histogram) type_name = heap_object.GetTypeName(err);

v8::HeapObject descriptors_obj = map.InstanceDescriptors(err);
if (err.Fail()) return false;

v8::DescriptorArray descriptors(descriptors_obj);
own_descriptors_count_ = map.NumberOfOwnDescriptors(err);
if (err.Fail()) return false;

int64_t type = heap_object.GetType(err);
indexed_properties_count_ = 0;
if (v8::JSObject::IsObjectType(&llv8, type) ||
(type == llv8.types()->kJSArrayType)) {
v8::JSObject js_obj(heap_object);
indexed_properties_count_ = js_obj.GetArrayLength(err);
if (err.Fail()) return false;
}

for (uint64_t i = 0; i < own_descriptors_count_; i++) {
v8::Value key = descriptors.GetKey(i, err);
if (err.Fail()) continue;
properties_.emplace_back(key.ToString(err));
}

return true;
}


inline static ByteOrder GetHostByteOrder() {
union {
uint8_t a[2];
Expand Down
Loading

0 comments on commit 20b14d9

Please sign in to comment.