Index: src/heap/object-stats.cc |
diff --git a/src/heap/object-stats.cc b/src/heap/object-stats.cc |
index e7d90b3e92b0adc1bd21e993dccf99c0e57a607e..3c82c7f7852bf307ed6a1b4bd86577a2fc2fd8bc 100644 |
--- a/src/heap/object-stats.cc |
+++ b/src/heap/object-stats.cc |
@@ -18,60 +18,85 @@ static base::LazyMutex object_stats_mutex = LAZY_MUTEX_INITIALIZER; |
void ObjectStats::ClearObjectStats(bool clear_last_time_stats) { |
memset(object_counts_, 0, sizeof(object_counts_)); |
memset(object_sizes_, 0, sizeof(object_sizes_)); |
+ memset(over_allocated_, 0, sizeof(over_allocated_)); |
+ memset(size_histogram_, 0, sizeof(size_histogram_)); |
+ memset(over_allocated_histogram_, 0, sizeof(over_allocated_histogram_)); |
if (clear_last_time_stats) { |
memset(object_counts_last_time_, 0, sizeof(object_counts_last_time_)); |
memset(object_sizes_last_time_, 0, sizeof(object_sizes_last_time_)); |
} |
} |
- |
-void ObjectStats::TraceObjectStat(const char* name, int count, int size, |
- double time) { |
- int ms_count = heap()->ms_count(); |
- PrintIsolate(isolate(), |
- "heap:%p, time:%f, gc:%d, type:%s, count:%d, size:%d\n", |
- static_cast<void*>(heap()), time, ms_count, name, count, size); |
+static void PrintJSONArray(size_t* array, const int len) { |
+ PrintF("[ "); |
+ for (int i = 0; i < len; i++) { |
+ PrintF("%zu", array[i]); |
+ if (i != (len - 1)) PrintF(", "); |
+ } |
+ PrintF(" ]"); |
} |
- |
-void ObjectStats::TraceObjectStats() { |
- base::LockGuard<base::Mutex> lock_guard(object_stats_mutex.Pointer()); |
- int index; |
- int count; |
- int size; |
- int total_size = 0; |
+void ObjectStats::PrintJSON(const char* key) { |
double time = isolate()->time_millis_since_init(); |
-#define TRACE_OBJECT_COUNT(name) \ |
- count = static_cast<int>(object_counts_[name]); \ |
- size = static_cast<int>(object_sizes_[name]) / KB; \ |
- total_size += size; \ |
- TraceObjectStat(#name, count, size, time); |
- INSTANCE_TYPE_LIST(TRACE_OBJECT_COUNT) |
-#undef TRACE_OBJECT_COUNT |
-#define TRACE_OBJECT_COUNT(name) \ |
- index = FIRST_CODE_KIND_SUB_TYPE + Code::name; \ |
- count = static_cast<int>(object_counts_[index]); \ |
- size = static_cast<int>(object_sizes_[index]) / KB; \ |
- TraceObjectStat("*CODE_" #name, count, size, time); |
- CODE_KIND_LIST(TRACE_OBJECT_COUNT) |
-#undef TRACE_OBJECT_COUNT |
-#define TRACE_OBJECT_COUNT(name) \ |
- index = FIRST_FIXED_ARRAY_SUB_TYPE + name; \ |
- count = static_cast<int>(object_counts_[index]); \ |
- size = static_cast<int>(object_sizes_[index]) / KB; \ |
- TraceObjectStat("*FIXED_ARRAY_" #name, count, size, time); |
- FIXED_ARRAY_SUB_INSTANCE_TYPE_LIST(TRACE_OBJECT_COUNT) |
-#undef TRACE_OBJECT_COUNT |
-#define TRACE_OBJECT_COUNT(name) \ |
- index = \ |
- FIRST_CODE_AGE_SUB_TYPE + Code::k##name##CodeAge - Code::kFirstCodeAge; \ |
- count = static_cast<int>(object_counts_[index]); \ |
- size = static_cast<int>(object_sizes_[index]) / KB; \ |
- TraceObjectStat("*CODE_AGE_" #name, count, size, time); |
- CODE_AGE_LIST_COMPLETE(TRACE_OBJECT_COUNT) |
-#undef TRACE_OBJECT_COUNT |
-} |
+ int gc_count = heap()->gc_count(); |
+ |
+#define PRINT_KEY_AND_ID() \ |
+ PrintF("\"isolate\": \"%p\", \"id\": %d, \"key\": \"%s\", ", \ |
+ reinterpret_cast<void*>(isolate()), gc_count, key); |
+ |
+ // gc_descriptor |
+ PrintF("{ "); |
+ PRINT_KEY_AND_ID(); |
+ PrintF("\"type\": \"gc_descriptor\", \"time\": %f }\n", time); |
+ // bucket_sizes |
+ PrintF("{ "); |
+ PRINT_KEY_AND_ID(); |
+ PrintF("\"type\": \"bucket_sizes\", \"sizes\": [ "); |
+ for (int i = 0; i < kNumberOfBuckets; i++) { |
+ PrintF("%d", 1 << (kFirstBucketShift + i)); |
+ if (i != (kNumberOfBuckets - 1)) PrintF(", "); |
+ } |
+ PrintF(" ] }\n"); |
+// instance_type_data |
+#define PRINT_INSTANCE_TYPE_DATA(name, index) \ |
+ PrintF("{ "); \ |
+ PRINT_KEY_AND_ID(); \ |
+ PrintF("\"type\": \"instance_type_data\", "); \ |
+ PrintF("\"instance_type\": %d, ", index); \ |
+ PrintF("\"instance_type_name\": \"%s\", ", name); \ |
+ PrintF("\"overall\": %zu, ", object_sizes_[index]); \ |
+ PrintF("\"count\": %zu, ", object_counts_[index]); \ |
+ PrintF("\"over_allocated\": %zu, ", over_allocated_[index]); \ |
+ PrintF("\"histogram\": "); \ |
+ PrintJSONArray(size_histogram_[index], kNumberOfBuckets); \ |
+ PrintF(","); \ |
+ PrintF("\"over_allocated_histogram\": "); \ |
+ PrintJSONArray(over_allocated_histogram_[index], kNumberOfBuckets); \ |
+ PrintF(" }\n"); |
+#define INSTANCE_TYPE_WRAPPER(name) PRINT_INSTANCE_TYPE_DATA(#name, name) |
+#define CODE_KIND_WRAPPER(name) \ |
+ PRINT_INSTANCE_TYPE_DATA("*CODE_" #name, \ |
+ FIRST_CODE_KIND_SUB_TYPE + Code::name) |
+#define FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER(name) \ |
+ PRINT_INSTANCE_TYPE_DATA("*FIXED_ARRAY_" #name, \ |
+ FIRST_FIXED_ARRAY_SUB_TYPE + name) |
+#define CODE_AGE_WRAPPER(name) \ |
+ PRINT_INSTANCE_TYPE_DATA( \ |
+ "*CODE_AGE_" #name, \ |
+ FIRST_CODE_AGE_SUB_TYPE + Code::k##name##CodeAge - Code::kFirstCodeAge) |
+ |
+ INSTANCE_TYPE_LIST(INSTANCE_TYPE_WRAPPER) |
+ CODE_KIND_LIST(CODE_KIND_WRAPPER) |
+ FIXED_ARRAY_SUB_INSTANCE_TYPE_LIST(FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER) |
+ CODE_AGE_LIST_COMPLETE(CODE_AGE_WRAPPER) |
+ |
+#undef INSTANCE_TYPE_WRAPPER |
+#undef CODE_KIND_WRAPPER |
+#undef FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER |
+#undef CODE_AGE_WRAPPER |
+#undef PRINT_INSTANCE_TYPE_DATA |
+} |
void ObjectStats::CheckpointObjectStats() { |
base::LockGuard<base::Mutex> lock_guard(object_stats_mutex.Pointer()); |
@@ -134,135 +159,213 @@ void ObjectStats::CheckpointObjectStats() { |
Isolate* ObjectStats::isolate() { return heap()->isolate(); } |
-void ObjectStatsCollector::CountFixedArray( |
- FixedArrayBase* fixed_array, FixedArraySubInstanceType fast_type, |
- FixedArraySubInstanceType dictionary_type) { |
- Heap* heap = fixed_array->map()->GetHeap(); |
- if (fixed_array->map() != heap->fixed_cow_array_map() && |
- fixed_array->map() != heap->fixed_double_array_map() && |
- fixed_array != heap->empty_fixed_array()) { |
- if (fixed_array->IsDictionary()) { |
- heap->object_stats_->RecordFixedArraySubTypeStats(dictionary_type, |
- fixed_array->Size()); |
- } else { |
- heap->object_stats_->RecordFixedArraySubTypeStats(fast_type, |
- fixed_array->Size()); |
- } |
+void ObjectStatsCollector::CollectStatistics(ObjectStats* stats, |
+ HeapObject* obj) { |
+ Map* map = obj->map(); |
+ Heap* heap = obj->GetHeap(); |
+ |
+ // Record for the InstanceType. |
+ int object_size = obj->Size(); |
+ stats->RecordObjectStats(map->instance_type(), object_size); |
+ |
+ // Record specific sub types where possible. |
+ if (obj->IsMap()) { |
+ RecordMapDetails(stats, heap, obj); |
+ } |
+ if (obj->IsCode()) { |
+ RecordCodeDetails(stats, heap, obj); |
+ } |
+ if (obj->IsSharedFunctionInfo()) { |
+ RecordSharedFunctionInfoDetails(stats, heap, obj); |
+ } |
+ if (obj->IsFixedArray()) { |
+ RecordFixedArrayDetails(stats, heap, obj); |
+ } |
+ if (obj->IsJSObject()) { |
+ RecordJSObjectDetails(stats, heap, JSObject::cast(obj)); |
+ } |
+ if (obj->IsJSWeakCollection()) { |
+ RecordJSWeakCollectionDetails(stats, heap, JSWeakCollection::cast(obj)); |
} |
} |
-void ObjectStatsCollector::CollectStatistics(StaticVisitorBase::VisitorId id, |
- Map* map, HeapObject* obj) { |
- // Record any type specific statistics here. |
- switch (id) { |
- case StaticVisitorBase::kVisitMap: |
- RecordMapStats(map, obj); |
- break; |
- case StaticVisitorBase::kVisitCode: |
- RecordCodeStats(map, obj); |
- break; |
- case StaticVisitorBase::kVisitSharedFunctionInfo: |
- RecordSharedFunctionInfoStats(map, obj); |
- break; |
- case StaticVisitorBase::kVisitFixedArray: |
- RecordFixedArrayStats(map, obj); |
- break; |
- default: |
- break; |
+static bool CanRecordFixedArray(Heap* heap, FixedArrayBase* array) { |
+ return array->map() != heap->fixed_cow_array_map() && |
+ array->map() != heap->fixed_double_array_map() && |
+ array != heap->empty_fixed_array(); |
+} |
+ |
+void ObjectStatsCollector::RecordJSObjectDetails(ObjectStats* stats, Heap* heap, |
+ JSObject* object) { |
+ DCHECK(object->IsJSObject()); |
+ |
+ size_t overhead = 0; |
+ FixedArrayBase* elements = object->elements(); |
+ if (CanRecordFixedArray(heap, elements)) { |
+ if (elements->IsDictionary()) { |
+ SeededNumberDictionary* dict = object->element_dictionary(); |
+ int used = dict->NumberOfElements() * SeededNumberDictionary::kEntrySize; |
+ CHECK_GE(elements->Size(), used); |
+ overhead = elements->Size() - used; |
+ stats->RecordFixedArraySubTypeStats(DICTIONARY_ELEMENTS_SUB_TYPE, |
+ elements->Size(), overhead); |
+ } else { |
+ if (IsFastHoleyElementsKind(object->GetElementsKind())) { |
+ int used = object->GetFastElementsUsage() * kPointerSize; |
+ if (object->GetElementsKind() == FAST_HOLEY_DOUBLE_ELEMENTS) used *= 2; |
+ CHECK_GE(elements->Size(), used); |
+ overhead = elements->Size() - used; |
+ } |
+ stats->RecordFixedArraySubTypeStats(FAST_ELEMENTS_SUB_TYPE, |
+ elements->Size(), overhead); |
+ } |
} |
- Heap* heap = map->GetHeap(); |
- int object_size = obj->Size(); |
- heap->object_stats_->RecordObjectStats(map->instance_type(), object_size); |
+ overhead = 0; |
+ FixedArrayBase* properties = object->properties(); |
+ if (CanRecordFixedArray(heap, properties)) { |
+ if (properties->IsDictionary()) { |
+ NameDictionary* dict = object->property_dictionary(); |
+ int used = dict->NumberOfElements() * NameDictionary::kEntrySize; |
+ CHECK_GE(properties->Size(), used); |
+ overhead = properties->Size() - used; |
+ stats->RecordFixedArraySubTypeStats(DICTIONARY_PROPERTIES_SUB_TYPE, |
+ properties->Size(), overhead); |
+ } else { |
+ stats->RecordFixedArraySubTypeStats(FAST_PROPERTIES_SUB_TYPE, |
+ properties->Size(), overhead); |
+ } |
+ } |
} |
-void ObjectStatsCollector::CollectFixedArrayStatistics(HeapObject* obj) { |
- if (obj->IsJSObject()) { |
- JSObject* object = JSObject::cast(obj); |
- CountFixedArray(object->elements(), DICTIONARY_ELEMENTS_SUB_TYPE, |
- FAST_ELEMENTS_SUB_TYPE); |
- CountFixedArray(object->properties(), DICTIONARY_PROPERTIES_SUB_TYPE, |
- FAST_PROPERTIES_SUB_TYPE); |
+void ObjectStatsCollector::RecordJSWeakCollectionDetails( |
+ ObjectStats* stats, Heap* heap, JSWeakCollection* obj) { |
+ if (obj->table()->IsHashTable()) { |
+ ObjectHashTable* table = ObjectHashTable::cast(obj->table()); |
+ int used = table->NumberOfElements() * ObjectHashTable::kEntrySize; |
+ size_t overhead = table->Size() - used; |
+ stats->RecordFixedArraySubTypeStats(WEAK_COLLECTION_SUB_TYPE, table->Size(), |
+ overhead); |
} |
} |
-void ObjectStatsCollector::RecordMapStats(Map* map, HeapObject* obj) { |
- Heap* heap = map->GetHeap(); |
+void ObjectStatsCollector::RecordMapDetails(ObjectStats* stats, Heap* heap, |
+ HeapObject* obj) { |
Map* map_obj = Map::cast(obj); |
- DCHECK(map->instance_type() == MAP_TYPE); |
+ DCHECK(obj->map()->instance_type() == MAP_TYPE); |
DescriptorArray* array = map_obj->instance_descriptors(); |
if (map_obj->owns_descriptors() && array != heap->empty_descriptor_array()) { |
int fixed_array_size = array->Size(); |
- heap->object_stats_->RecordFixedArraySubTypeStats(DESCRIPTOR_ARRAY_SUB_TYPE, |
- fixed_array_size); |
+ stats->RecordFixedArraySubTypeStats(DESCRIPTOR_ARRAY_SUB_TYPE, |
+ fixed_array_size, 0); |
+ if (array->HasEnumCache()) { |
+ stats->RecordFixedArraySubTypeStats(ENUM_CACHE_SUB_TYPE, |
+ array->GetEnumCache()->Size(), 0); |
+ } |
+ if (array->HasEnumIndicesCache()) { |
+ stats->RecordFixedArraySubTypeStats( |
+ ENUM_INDICES_CACHE_SUB_TYPE, array->GetEnumIndicesCache()->Size(), 0); |
+ } |
} |
+ |
if (map_obj->has_code_cache()) { |
FixedArray* cache = map_obj->code_cache(); |
- heap->object_stats_->RecordFixedArraySubTypeStats(MAP_CODE_CACHE_SUB_TYPE, |
- cache->Size()); |
+ stats->RecordFixedArraySubTypeStats(MAP_CODE_CACHE_SUB_TYPE, cache->Size(), |
+ 0); |
} |
} |
-void ObjectStatsCollector::RecordCodeStats(Map* map, HeapObject* obj) { |
- Heap* heap = map->GetHeap(); |
+void ObjectStatsCollector::RecordCodeDetails(ObjectStats* stats, Heap* heap, |
+ HeapObject* obj) { |
int object_size = obj->Size(); |
- DCHECK(map->instance_type() == CODE_TYPE); |
+ DCHECK(obj->map()->instance_type() == CODE_TYPE); |
Code* code_obj = Code::cast(obj); |
- heap->object_stats_->RecordCodeSubTypeStats(code_obj->kind(), |
- code_obj->GetAge(), object_size); |
+ stats->RecordCodeSubTypeStats(code_obj->kind(), code_obj->GetAge(), |
+ object_size); |
+ Code* code = Code::cast(obj); |
+ if (code->deoptimization_data() != heap->empty_fixed_array()) { |
+ stats->RecordFixedArraySubTypeStats(DEOPTIMIZATION_DATA_SUB_TYPE, |
+ code->deoptimization_data()->Size(), 0); |
+ } |
+ FixedArrayBase* reloc_info = |
+ reinterpret_cast<FixedArrayBase*>(code->unchecked_relocation_info()); |
+ if (reloc_info != heap->empty_fixed_array()) { |
+ stats->RecordFixedArraySubTypeStats(RELOC_INFO_SUB_TYPE, |
+ code->relocation_info()->Size(), 0); |
+ } |
+ FixedArrayBase* source_pos_table = |
+ reinterpret_cast<FixedArrayBase*>(code->source_position_table()); |
+ if (source_pos_table != heap->empty_fixed_array()) { |
+ stats->RecordFixedArraySubTypeStats(SOURCE_POS_SUB_TYPE, |
+ source_pos_table->Size(), 0); |
+ } |
} |
-void ObjectStatsCollector::RecordSharedFunctionInfoStats(Map* map, |
- HeapObject* obj) { |
- Heap* heap = map->GetHeap(); |
+void ObjectStatsCollector::RecordSharedFunctionInfoDetails(ObjectStats* stats, |
+ Heap* heap, |
+ HeapObject* obj) { |
SharedFunctionInfo* sfi = SharedFunctionInfo::cast(obj); |
if (sfi->scope_info() != heap->empty_fixed_array()) { |
- heap->object_stats_->RecordFixedArraySubTypeStats( |
- SCOPE_INFO_SUB_TYPE, FixedArray::cast(sfi->scope_info())->Size()); |
+ stats->RecordFixedArraySubTypeStats(SCOPE_INFO_SUB_TYPE, |
+ sfi->scope_info()->Size(), 0); |
+ } |
+ if (sfi->feedback_metadata() != heap->empty_fixed_array()) { |
+ stats->RecordFixedArraySubTypeStats(TYPE_FEEDBACK_METADATA_SUB_TYPE, |
+ sfi->feedback_metadata()->Size(), 0); |
+ } |
+ if (!sfi->OptimizedCodeMapIsCleared()) { |
+ FixedArray* optimized_code_map = sfi->optimized_code_map(); |
+ // Optimized code map should be small, so skip accounting. |
+ int len = optimized_code_map->length(); |
+ for (int i = SharedFunctionInfo::kEntriesStart; i < len; |
+ i += SharedFunctionInfo::kEntryLength) { |
+ Object* slot = |
+ optimized_code_map->get(i + SharedFunctionInfo::kLiteralsOffset); |
+ LiteralsArray* literals = nullptr; |
+ if (slot->IsWeakCell()) { |
+ WeakCell* cell = WeakCell::cast(slot); |
+ if (!cell->cleared()) { |
+ literals = LiteralsArray::cast(cell->value()); |
+ } |
+ } else { |
+ literals = LiteralsArray::cast(slot); |
+ } |
+ if (literals != nullptr) { |
+ stats->RecordFixedArraySubTypeStats(LITERALS_ARRAY_SUB_TYPE, |
+ literals->Size(), 0); |
+ TypeFeedbackVector* tfv = literals->feedback_vector(); |
+ |
+ stats->RecordFixedArraySubTypeStats(TYPE_FEEDBACK_VECTOR_SUB_TYPE, |
+ tfv->Size(), 0); |
+ } |
+ } |
} |
} |
-void ObjectStatsCollector::RecordFixedArrayStats(Map* map, HeapObject* obj) { |
- Heap* heap = map->GetHeap(); |
+void ObjectStatsCollector::RecordFixedArrayDetails(ObjectStats* stats, |
+ Heap* heap, |
+ HeapObject* obj) { |
FixedArray* fixed_array = FixedArray::cast(obj); |
if (fixed_array == heap->string_table()) { |
- heap->object_stats_->RecordFixedArraySubTypeStats(STRING_TABLE_SUB_TYPE, |
- fixed_array->Size()); |
+ stats->RecordFixedArraySubTypeStats(STRING_TABLE_SUB_TYPE, |
+ fixed_array->Size(), 0); |
+ } |
+ if (fixed_array == heap->weak_object_to_code_table()) { |
+ WeakHashTable* table = reinterpret_cast<WeakHashTable*>(fixed_array); |
+ int used = table->NumberOfElements() * WeakHashTable::kEntrySize; |
+ CHECK_GE(fixed_array->Size(), used); |
+ size_t overhead = fixed_array->Size() - used; |
+ stats->RecordFixedArraySubTypeStats(OBJECT_TO_CODE_SUB_TYPE, |
+ fixed_array->Size(), overhead); |
+ } |
+ if (obj->IsContext()) { |
+ stats->RecordFixedArraySubTypeStats(CONTEXT_SUB_TYPE, fixed_array->Size(), |
+ 0); |
+ } |
+ if (fixed_array->map() == heap->fixed_cow_array_map()) { |
+ stats->RecordFixedArraySubTypeStats(COPY_ON_WRITE_SUB_TYPE, |
+ fixed_array->Size(), 0); |
} |
-} |
- |
-void MarkCompactObjectStatsVisitor::Initialize( |
- VisitorDispatchTable<Callback>* original) { |
- // Copy the original visitor table to make call-through possible. After we |
- // preserved a copy locally, we patch the original table to call us. |
- table_.CopyFrom(original); |
-#define COUNT_FUNCTION(id) original->Register(kVisit##id, Visit<kVisit##id>); |
- VISITOR_ID_LIST(COUNT_FUNCTION) |
-#undef COUNT_FUNCTION |
-} |
- |
-template <MarkCompactObjectStatsVisitor::VisitorId id> |
-void MarkCompactObjectStatsVisitor::Visit(Map* map, HeapObject* obj) { |
- ObjectStatsCollector::CollectStatistics(id, map, obj); |
- table_.GetVisitorById(id)(map, obj); |
- ObjectStatsCollector::CollectFixedArrayStatistics(obj); |
-} |
- |
-void IncrementalMarkingObjectStatsVisitor::Initialize( |
- VisitorDispatchTable<Callback>* original) { |
- // Copy the original visitor table to make call-through possible. After we |
- // preserved a copy locally, we patch the original table to call us. |
- table_.CopyFrom(original); |
-#define COUNT_FUNCTION(id) original->Register(kVisit##id, Visit<kVisit##id>); |
- VISITOR_ID_LIST(COUNT_FUNCTION) |
-#undef COUNT_FUNCTION |
-} |
- |
-template <IncrementalMarkingObjectStatsVisitor::VisitorId id> |
-void IncrementalMarkingObjectStatsVisitor::Visit(Map* map, HeapObject* obj) { |
- ObjectStatsCollector::CollectStatistics(id, map, obj); |
- table_.GetVisitorById(id)(map, obj); |
- ObjectStatsCollector::CollectFixedArrayStatistics(obj); |
} |
} // namespace internal |