DEFINE_bool(log_handles, false, "Log global handle events.")
DEFINE_bool(log_state_changes, false, "Log state changes.")
DEFINE_bool(log_suspect, false, "Log suspect operations.")
+DEFINE_bool(log_producers, false, "Log stack traces of JS objects allocations.")
DEFINE_bool(compress_log, false,
"Compress log to save space (makes log less human-readable).")
DEFINE_bool(prof, false,
}
+void GlobalHandles::IterateWeakRoots(WeakReferenceGuest f,
+ WeakReferenceCallback callback) {
+ for (Node* current = head_; current != NULL; current = current->next()) {
+ if (current->IsWeak() && current->callback() == callback) {
+ f(current->object_, current->parameter());
+ }
+ }
+}
+
+
void GlobalHandles::IdentifyWeakHandles(WeakSlotCallback f) {
for (Node* current = head_; current != NULL; current = current->next()) {
if (current->state_ == Node::WEAK) {
};
+typedef void (*WeakReferenceGuest)(Object* object, void* parameter);
+
class GlobalHandles : public AllStatic {
public:
// Creates a new global handle that is alive until Destroy is called.
// Iterates over all weak roots in heap.
static void IterateWeakRoots(ObjectVisitor* v);
+ // Iterates over weak roots that are bound to a given callback.
+ static void IterateWeakRoots(WeakReferenceGuest f,
+ WeakReferenceCallback callback);
+
// Find all weak handles satisfying the callback predicate, mark
// them as pending.
static void IdentifyWeakHandles(WeakSlotCallback f);
#include "v8.h"
#include "heap-profiler.h"
+#include "frames-inl.h"
+#include "global-handles.h"
#include "string-stream.h"
namespace v8 {
}
+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)) {
} else {
SmartPointer<char> s_name(
constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
- accumulator->Add("%s", (*s_name)[0] != '\0' ? *s_name : "(anonymous)");
+ accumulator->Add("%s", GetConstructorName(*s_name));
if (instance_ != NULL) {
accumulator->Add(":%p", static_cast<void*>(instance_));
}
}
+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(
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::RecordJSObjectAllocation(Object* obj) {
+ if (!can_log_ || !FLAG_log_producers) 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
};
+class ProducerHeapProfile : public AllStatic {
+ public:
+ static void Setup();
+ static void RecordJSObjectAllocation(Object* obj);
+ private:
+ static bool can_log_;
+};
+
#endif // ENABLE_LOGGING_AND_PROFILING
} } // namespace v8::internal
TargetSpaceId(map->instance_type()));
if (result->IsFailure()) return result;
HeapObject::cast(result)->set_map(map);
+ ProducerHeapProfile::RecordJSObjectAllocation(result);
return result;
}
JSObject::cast(clone)->set_properties(FixedArray::cast(prop));
}
// Return the new clone.
+ ProducerHeapProfile::RecordJSObjectAllocation(clone);
return clone;
}
LOG(IntEvent("heap-capacity", Capacity()));
LOG(IntEvent("heap-available", Available()));
+ // This should be called only after initial objects have been created.
+ ProducerHeapProfile::Setup();
+
return true;
}
}
+void Logger::HeapSampleJSProducerEvent(const char* constructor,
+ Address* stack) {
+#ifdef ENABLE_LOGGING_AND_PROFILING
+ if (!Log::IsEnabled() || !FLAG_log_gc) return;
+ LogMessageBuilder msg;
+ msg.Append("heap-js-prod-item,%s", constructor);
+ while (*stack != NULL) {
+ msg.Append(",0x%" V8PRIxPTR, *stack++);
+ }
+ msg.Append("\n");
+ msg.WriteToLogFile();
+#endif
+}
+
+
void Logger::DebugTag(const char* call_site_tag) {
#ifdef ENABLE_LOGGING_AND_PROFILING
if (!Log::IsEnabled() || !FLAG_log) return;
int number, int bytes);
static void HeapSampleJSRetainersEvent(const char* constructor,
const char* event);
+ static void HeapSampleJSProducerEvent(const char* constructor,
+ Address* stack);
static void HeapSampleStats(const char* space, const char* kind,
int capacity, int used);
'tick': { parsers: [this.createAddressParser('code'),
this.createAddressParser('stack'), parseInt, 'var-args'],
processor: this.processTick, backrefs: true },
+ 'heap-sample-begin': { parsers: [null, null, parseInt],
+ processor: this.processHeapSampleBegin },
+ 'heap-sample-end': { parsers: [null, null],
+ processor: this.processHeapSampleEnd },
+ 'heap-js-prod-item': { parsers: [null, 'var-args'],
+ processor: this.processJSProducer, backrefs: true },
+ // Ignored events.
'profiler': null,
+ 'heap-sample-stats': null,
+ 'heap-sample-item': null,
+ 'heap-js-cons-item': null,
+ 'heap-js-ret-item': null,
// Obsolete row types.
'code-allocate': null,
'begin-code-region': null,
// Count each tick as a time unit.
this.viewBuilder_ = new devtools.profiler.ViewBuilder(1);
this.lastLogFileName_ = null;
+
+ this.generation_ = 1;
+ this.currentProducerProfile_ = null;
};
inherits(TickProcessor, devtools.profiler.LogReader);
};
+TickProcessor.prototype.processHeapSampleBegin = function(space, state, ticks) {
+ if (space != 'Heap') return;
+ this.currentProducerProfile_ = new devtools.profiler.CallTree();
+};
+
+
+TickProcessor.prototype.processHeapSampleEnd = function(space, state) {
+ if (space != 'Heap' || !this.currentProducerProfile_) return;
+
+ print('Generation ' + this.generation_ + ':');
+ var tree = this.currentProducerProfile_;
+ tree.computeTotalWeights();
+ var producersView = this.viewBuilder_.buildView(tree);
+ // Sort by total time, desc, then by name, desc.
+ producersView.sort(function(rec1, rec2) {
+ return rec2.totalTime - rec1.totalTime ||
+ (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
+ this.printHeavyProfile(producersView.head.children);
+
+ this.currentProducerProfile_ = null;
+ this.generation_++;
+};
+
+
+TickProcessor.prototype.processJSProducer = function(constructor, stack) {
+ if (!this.currentProducerProfile_) return;
+ if (stack.length == 0) return;
+ var first = stack.shift();
+ var processedStack =
+ this.profile_.resolveAndFilterFuncs_(this.processStack(first, stack));
+ processedStack.unshift(constructor);
+ this.currentProducerProfile_.addPath(processedStack);
+};
+
+
TickProcessor.prototype.printStatistics = function() {
print('Statistical profiling result from ' + this.lastLogFileName_ +
', (' + this.ticks_.total +