// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "v8.h"
#include "heap-profiler.h"
#include "frames-inl.h"
#include "global-handles.h"
#include "string-stream.h"
namespace v8 {
namespace internal {
#ifdef ENABLE_LOGGING_AND_PROFILING
namespace {
// Clusterizer is a set of helper functions for converting
// object references into clusters.
class Clusterizer : public AllStatic {
public:
static JSObjectsCluster Clusterize(HeapObject* obj) {
return Clusterize(obj, true);
}
static void InsertIntoTree(JSObjectsClusterTree* tree,
HeapObject* obj, bool fine_grain);
static void InsertReferenceIntoTree(JSObjectsClusterTree* tree,
const JSObjectsCluster& cluster) {
InsertIntoTree(tree, cluster, 0);
}
private:
static JSObjectsCluster Clusterize(HeapObject* obj, bool fine_grain);
static int CalculateNetworkSize(JSObject* obj);
static int GetObjectSize(HeapObject* obj) {
return obj->IsJSObject() ?
CalculateNetworkSize(JSObject::cast(obj)) : obj->Size();
}
static void InsertIntoTree(JSObjectsClusterTree* tree,
const JSObjectsCluster& cluster, int size);
};
JSObjectsCluster Clusterizer::Clusterize(HeapObject* obj, bool fine_grain) {
if (obj->IsJSObject()) {
JSObject* js_obj = JSObject::cast(obj);
String* constructor = JSObject::cast(js_obj)->constructor_name();
// Differentiate Object and Array instances.
if (fine_grain && (constructor == Heap::Object_symbol() ||
constructor == Heap::Array_symbol())) {
return JSObjectsCluster(constructor, obj);
} else {
return JSObjectsCluster(constructor);
}
} else if (obj->IsString()) {
return JSObjectsCluster(Heap::String_symbol());
} else if (obj->IsJSGlobalPropertyCell()) {
return JSObjectsCluster(JSObjectsCluster::GLOBAL_PROPERTY);
} else if (obj->IsCode() || obj->IsSharedFunctionInfo() || obj->IsScript()) {
return JSObjectsCluster(JSObjectsCluster::CODE);
}
return JSObjectsCluster();
}
void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
HeapObject* obj, bool fine_grain) {
JSObjectsCluster cluster = Clusterize(obj, fine_grain);
if (cluster.is_null()) return;
InsertIntoTree(tree, cluster, GetObjectSize(obj));
}
void Clusterizer::InsertIntoTree(JSObjectsClusterTree* tree,
const JSObjectsCluster& cluster, int size) {
JSObjectsClusterTree::Locator loc;
tree->Insert(cluster, &loc);
NumberAndSizeInfo number_and_size = loc.value();
number_and_size.increment_number(1);
number_and_size.increment_bytes(size);
loc.set_value(number_and_size);
}
int Clusterizer::CalculateNetworkSize(JSObject* obj) {
int size = obj->Size();
// If 'properties' and 'elements' are non-empty (thus, non-shared),
// take their size into account.
if (FixedArray::cast(obj->properties())->length() != 0) {
size += obj->properties()->Size();
}
if (FixedArray::cast(obj->elements())->length() != 0) {
size += obj->elements()->Size();
}
// For functions, also account non-empty context and literals sizes.
if (obj->IsJSFunction()) {
JSFunction* f = JSFunction::cast(obj);
if (f->unchecked_context()->IsContext()) {
size += f->context()->Size();
}
if (f->literals()->length() != 0) {
size += f->literals()->Size();
}
}
return size;
}
// A helper class for recording back references.
class ReferencesExtractor : public ObjectVisitor {
public:
ReferencesExtractor(const JSObjectsCluster& cluster,
RetainerHeapProfile* profile)
: cluster_(cluster),
profile_(profile),
inside_array_(false) {
}
void VisitPointer(Object** o) {
if ((*o)->IsFixedArray() && !inside_array_) {
// Traverse one level deep for data members that are fixed arrays.
// This covers the case of 'elements' and 'properties' of JSObject,
// and function contexts.
inside_array_ = true;
FixedArray::cast(*o)->Iterate(this);
inside_array_ = false;
} else if ((*o)->IsHeapObject()) {
profile_->StoreReference(cluster_, HeapObject::cast(*o));
}
}
void VisitPointers(Object** start, Object** end) {
for (Object** p = start; p < end; p++) VisitPointer(p);
}
private:
const JSObjectsCluster& cluster_;
RetainerHeapProfile* profile_;
bool inside_array_;
};
// A printer interface implementation for the Retainers profile.
class RetainersPrinter : public RetainerHeapProfile::Printer {
public:
void PrintRetainers(const JSObjectsCluster& cluster,
const StringStream& retainers) {
HeapStringAllocator allocator;
StringStream stream(&allocator);
cluster.Print(&stream);
LOG(HeapSampleJSRetainersEvent(
*(stream.ToCString()), *(retainers.ToCString())));
}
};
// Visitor for printing a cluster tree.
class ClusterTreePrinter BASE_EMBEDDED {
public:
explicit ClusterTreePrinter(StringStream* stream) : stream_(stream) {}
void Call(const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size) {
Print(stream_, cluster, number_and_size);
}
static void Print(StringStream* stream,
const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size);
private:
StringStream* stream_;
};
void ClusterTreePrinter::Print(StringStream* stream,
const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size) {
stream->Put(',');
cluster.Print(stream);
stream->Add(";%d", number_and_size.number());
}
// Visitor for printing a retainer tree.
class SimpleRetainerTreePrinter BASE_EMBEDDED {
public:
explicit SimpleRetainerTreePrinter(RetainerHeapProfile::Printer* printer)
: printer_(printer) {}
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
private:
RetainerHeapProfile::Printer* printer_;
};
void SimpleRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
JSObjectsClusterTree* tree) {
HeapStringAllocator allocator;
StringStream stream(&allocator);
ClusterTreePrinter retainers_printer(&stream);
tree->ForEach(&retainers_printer);
printer_->PrintRetainers(cluster, stream);
}
// Visitor for aggregating references count of equivalent clusters.
class RetainersAggregator BASE_EMBEDDED {
public:
RetainersAggregator(ClustersCoarser* coarser, JSObjectsClusterTree* dest_tree)
: coarser_(coarser), dest_tree_(dest_tree) {}
void Call(const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size);
private:
ClustersCoarser* coarser_;
JSObjectsClusterTree* dest_tree_;
};
void RetainersAggregator::Call(const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size) {
JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
if (eq.is_null()) eq = cluster;
JSObjectsClusterTree::Locator loc;
dest_tree_->Insert(eq, &loc);
NumberAndSizeInfo aggregated_number = loc.value();
aggregated_number.increment_number(number_and_size.number());
loc.set_value(aggregated_number);
}
// Visitor for printing retainers tree. Aggregates equivalent retainer clusters.
class AggregatingRetainerTreePrinter BASE_EMBEDDED {
public:
AggregatingRetainerTreePrinter(ClustersCoarser* coarser,
RetainerHeapProfile::Printer* printer)
: coarser_(coarser), printer_(printer) {}
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
private:
ClustersCoarser* coarser_;
RetainerHeapProfile::Printer* printer_;
};
void AggregatingRetainerTreePrinter::Call(const JSObjectsCluster& cluster,
JSObjectsClusterTree* tree) {
if (!coarser_->GetCoarseEquivalent(cluster).is_null()) return;
JSObjectsClusterTree dest_tree_;
RetainersAggregator retainers_aggregator(coarser_, &dest_tree_);
tree->ForEach(&retainers_aggregator);
HeapStringAllocator allocator;
StringStream stream(&allocator);
ClusterTreePrinter retainers_printer(&stream);
dest_tree_.ForEach(&retainers_printer);
printer_->PrintRetainers(cluster, stream);
}
// A helper class for building a retainers tree, that aggregates
// all equivalent clusters.
class RetainerTreeAggregator BASE_EMBEDDED {
public:
explicit RetainerTreeAggregator(ClustersCoarser* coarser)
: coarser_(coarser) {}
void Process(JSObjectsRetainerTree* input_tree) {
input_tree->ForEach(this);
}
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
JSObjectsRetainerTree& output_tree() { return output_tree_; }
private:
ClustersCoarser* coarser_;
JSObjectsRetainerTree output_tree_;
};
void RetainerTreeAggregator::Call(const JSObjectsCluster& cluster,
JSObjectsClusterTree* tree) {
JSObjectsCluster eq = coarser_->GetCoarseEquivalent(cluster);
if (eq.is_null()) return;
JSObjectsRetainerTree::Locator loc;
if (output_tree_.Insert(eq, &loc)) {
loc.set_value(new JSObjectsClusterTree());
}
RetainersAggregator retainers_aggregator(coarser_, loc.value());
tree->ForEach(&retainers_aggregator);
}
} // namespace
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue;
ConstructorHeapProfile::ConstructorHeapProfile()
: zscope_(DELETE_ON_EXIT) {
}
void ConstructorHeapProfile::Call(const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size) {
HeapStringAllocator allocator;
StringStream stream(&allocator);
cluster.Print(&stream);
LOG(HeapSampleJSConstructorEvent(*(stream.ToCString()),
number_and_size.number(),
number_and_size.bytes()));
}
void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
Clusterizer::InsertIntoTree(&js_objects_info_tree_, obj, false);
}
void ConstructorHeapProfile::PrintStats() {
js_objects_info_tree_.ForEach(this);
}
static const char* GetConstructorName(const char* name) {
return name[0] != '\0' ? name : "(anonymous)";
}
void JSObjectsCluster::Print(StringStream* accumulator) const {
ASSERT(!is_null());
if (constructor_ == FromSpecialCase(ROOTS)) {
accumulator->Add("(roots)");
} else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) {
accumulator->Add("(global property)");
} else if (constructor_ == FromSpecialCase(CODE)) {
accumulator->Add("(code)");
} else if (constructor_ == FromSpecialCase(SELF)) {
accumulator->Add("(self)");
} else {
SmartPointer<char> s_name(
constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
accumulator->Add("%s", GetConstructorName(*s_name));
if (instance_ != NULL) {
accumulator->Add(":%p", static_cast<void*>(instance_));
}
}
}
void JSObjectsCluster::DebugPrint(StringStream* accumulator) const {
if (!is_null()) {
Print(accumulator);
} else {
accumulator->Add("(null cluster)");
}
}
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
const JSObjectsCluster& cluster_)
: cluster(cluster_), refs(kInitialBackrefsListCapacity) {
}
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
const ClustersCoarser::ClusterBackRefs& src)
: cluster(src.cluster), refs(src.refs.capacity()) {
refs.AddAll(src.refs);
}
inline ClustersCoarser::ClusterBackRefs&
ClustersCoarser::ClusterBackRefs::operator=(
const ClustersCoarser::ClusterBackRefs& src) {
if (this == &src) return *this;
cluster = src.cluster;
refs.Clear();
refs.AddAll(src.refs);
return *this;
}
inline int ClustersCoarser::ClusterBackRefs::Compare(
const ClustersCoarser::ClusterBackRefs& a,
const ClustersCoarser::ClusterBackRefs& b) {
int cmp = JSObjectsCluster::CompareConstructors(a.cluster, b.cluster);
if (cmp != 0) return cmp;
if (a.refs.length() < b.refs.length()) return -1;
if (a.refs.length() > b.refs.length()) return 1;
for (int i = 0; i < a.refs.length(); ++i) {
int cmp = JSObjectsCluster::Compare(a.refs[i], b.refs[i]);
if (cmp != 0) return cmp;
}
return 0;
}
ClustersCoarser::ClustersCoarser()
: zscope_(DELETE_ON_EXIT),
sim_list_(ClustersCoarser::kInitialSimilarityListCapacity),
current_pair_(NULL),
current_set_(NULL),
self_(NULL) {
}
void ClustersCoarser::Call(const JSObjectsCluster& cluster,
JSObjectsClusterTree* tree) {
if (!cluster.can_be_coarsed()) return;
ClusterBackRefs pair(cluster);
ASSERT(current_pair_ == NULL);
current_pair_ = &pair;
current_set_ = new JSObjectsRetainerTree();
self_ = &cluster;
tree->ForEach(this);
sim_list_.Add(pair);
current_pair_ = NULL;
current_set_ = NULL;
self_ = NULL;
}
void ClustersCoarser::Call(const JSObjectsCluster& cluster,
const NumberAndSizeInfo& number_and_size) {
ASSERT(current_pair_ != NULL);
ASSERT(current_set_ != NULL);
ASSERT(self_ != NULL);
JSObjectsRetainerTree::Locator loc;
if (JSObjectsCluster::Compare(*self_, cluster) == 0) {
current_pair_->refs.Add(JSObjectsCluster(JSObjectsCluster::SELF));
return;
}
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
if (!eq.is_null()) {
if (current_set_->Find(eq, &loc)) return;
current_pair_->refs.Add(eq);
current_set_->Insert(eq, &loc);
} else {
current_pair_->refs.Add(cluster);
}
}
void ClustersCoarser::Process(JSObjectsRetainerTree* tree) {
int last_eq_clusters = -1;
for (int i = 0; i < kMaxPassesCount; ++i) {
sim_list_.Clear();
const int curr_eq_clusters = DoProcess(tree);
// If no new cluster equivalents discovered, abort processing.
if (last_eq_clusters == curr_eq_clusters) break;
last_eq_clusters = curr_eq_clusters;
}
}
int ClustersCoarser::DoProcess(JSObjectsRetainerTree* tree) {
tree->ForEach(this);
sim_list_.Iterate(ClusterBackRefs::SortRefsIterator);
sim_list_.Sort(ClusterBackRefsCmp);
return FillEqualityTree();
}
JSObjectsCluster ClustersCoarser::GetCoarseEquivalent(
const JSObjectsCluster& cluster) {
if (!cluster.can_be_coarsed()) return JSObjectsCluster();
EqualityTree::Locator loc;
return eq_tree_.Find(cluster, &loc) ? loc.value() : JSObjectsCluster();
}
bool ClustersCoarser::HasAnEquivalent(const JSObjectsCluster& cluster) {
// Return true for coarsible clusters that have a non-identical equivalent.
if (!cluster.can_be_coarsed()) return false;
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
return !eq.is_null() && JSObjectsCluster::Compare(cluster, eq) != 0;
}
int ClustersCoarser::FillEqualityTree() {
int eq_clusters_count = 0;
int eq_to = 0;
bool first_added = false;
for (int i = 1; i < sim_list_.length(); ++i) {
if (ClusterBackRefs::Compare(sim_list_[i], sim_list_[eq_to]) == 0) {
EqualityTree::Locator loc;
if (!first_added) {
// Add self-equivalence, if we have more than one item in this
// equivalence class.
eq_tree_.Insert(sim_list_[eq_to].cluster, &loc);
loc.set_value(sim_list_[eq_to].cluster);
first_added = true;
}
eq_tree_.Insert(sim_list_[i].cluster, &loc);
loc.set_value(sim_list_[eq_to].cluster);
++eq_clusters_count;
} else {
eq_to = i;
first_added = false;
}
}
return eq_clusters_count;
}
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
const JSObjectsRetainerTreeConfig::Key JSObjectsRetainerTreeConfig::kNoKey;
const JSObjectsRetainerTreeConfig::Value JSObjectsRetainerTreeConfig::kNoValue =
NULL;
RetainerHeapProfile::RetainerHeapProfile()
: zscope_(DELETE_ON_EXIT) {
JSObjectsCluster roots(JSObjectsCluster::ROOTS);
ReferencesExtractor extractor(roots, this);
Heap::IterateRoots(&extractor, VISIT_ONLY_STRONG);
}
void RetainerHeapProfile::StoreReference(const JSObjectsCluster& cluster,
HeapObject* ref) {
JSObjectsCluster ref_cluster = Clusterizer::Clusterize(ref);
if (ref_cluster.is_null()) return;
JSObjectsRetainerTree::Locator ref_loc;
if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
ref_loc.set_value(new JSObjectsClusterTree());
}
JSObjectsClusterTree* referenced_by = ref_loc.value();
Clusterizer::InsertReferenceIntoTree(referenced_by, cluster);
}
void RetainerHeapProfile::CollectStats(HeapObject* obj) {
const JSObjectsCluster cluster = Clusterizer::Clusterize(obj);
if (cluster.is_null()) return;
ReferencesExtractor extractor(cluster, this);
obj->Iterate(&extractor);
}
void RetainerHeapProfile::DebugPrintStats(
RetainerHeapProfile::Printer* printer) {
coarser_.Process(&retainers_tree_);
// Print clusters that have no equivalents, aggregating their retainers.
AggregatingRetainerTreePrinter agg_printer(&coarser_, printer);
retainers_tree_.ForEach(&agg_printer);
// Now aggregate clusters that have equivalents...
RetainerTreeAggregator aggregator(&coarser_);
aggregator.Process(&retainers_tree_);
// ...and print them.
SimpleRetainerTreePrinter s_printer(printer);
aggregator.output_tree().ForEach(&s_printer);
}
void RetainerHeapProfile::PrintStats() {
RetainersPrinter printer;
DebugPrintStats(&printer);
}
//
// HeapProfiler class implementation.
//
void HeapProfiler::CollectStats(HeapObject* obj, HistogramInfo* info) {
InstanceType type = obj->map()->instance_type();
ASSERT(0 <= type && type <= LAST_TYPE);
if (!FreeListNode::IsFreeListNode(obj)) {
info[type].increment_number(1);
info[type].increment_bytes(obj->Size());
}
}
static void StackWeakReferenceCallback(Persistent<Value> object,
void* trace) {
DeleteArray(static_cast<Address*>(trace));
object.Dispose();
}
static void PrintProducerStackTrace(Object* obj, void* trace) {
if (!obj->IsJSObject()) return;
String* constructor = JSObject::cast(obj)->constructor_name();
SmartPointer<char> s_name(
constructor->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
LOG(HeapSampleJSProducerEvent(GetConstructorName(*s_name),
reinterpret_cast<Address*>(trace)));
}
void HeapProfiler::WriteSample() {
LOG(HeapSampleBeginEvent("Heap", "allocated"));
LOG(HeapSampleStats(
"Heap", "allocated", Heap::CommittedMemory(), Heap::SizeOfObjects()));
HistogramInfo info[LAST_TYPE+1];
#define DEF_TYPE_NAME(name) info[name].set_name(#name);
INSTANCE_TYPE_LIST(DEF_TYPE_NAME)
#undef DEF_TYPE_NAME
ConstructorHeapProfile js_cons_profile;
RetainerHeapProfile js_retainer_profile;
HeapIterator iterator;
for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
CollectStats(obj, info);
js_cons_profile.CollectStats(obj);
js_retainer_profile.CollectStats(obj);
}
// Lump all the string types together.
int string_number = 0;
int string_bytes = 0;
#define INCREMENT_SIZE(type, size, name, camel_name) \
string_number += info[type].number(); \
string_bytes += info[type].bytes();
STRING_TYPE_LIST(INCREMENT_SIZE)
#undef INCREMENT_SIZE
if (string_bytes > 0) {
LOG(HeapSampleItemEvent("STRING_TYPE", string_number, string_bytes));
}
for (int i = FIRST_NONSTRING_TYPE; i <= LAST_TYPE; ++i) {
if (info[i].bytes() > 0) {
LOG(HeapSampleItemEvent(info[i].name(), info[i].number(),
info[i].bytes()));
}
}
js_cons_profile.PrintStats();
js_retainer_profile.PrintStats();
GlobalHandles::IterateWeakRoots(PrintProducerStackTrace,
StackWeakReferenceCallback);
LOG(HeapSampleEndEvent("Heap", "allocated"));
}
bool ProducerHeapProfile::can_log_ = false;
void ProducerHeapProfile::Setup() {
can_log_ = true;
}
void ProducerHeapProfile::DoRecordJSObjectAllocation(Object* obj) {
ASSERT(FLAG_log_producers);
if (!can_log_) return;
int framesCount = 0;
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
++framesCount;
}
if (framesCount == 0) return;
++framesCount; // Reserve place for the terminator item.
Vector<Address> stack(NewArray<Address>(framesCount), framesCount);
int i = 0;
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) {
stack[i++] = it.frame()->pc();
}
stack[i] = NULL;
Handle<Object> handle = GlobalHandles::Create(obj);
GlobalHandles::MakeWeak(handle.location(),
static_cast<void*>(stack.start()),
StackWeakReferenceCallback);
}
#endif // ENABLE_LOGGING_AND_PROFILING
} } // namespace v8::internal