diff --git a/runtime/vm/benchmark_test.h b/runtime/vm/benchmark_test.h index edc9bfce882..d3569b04453 100644 --- a/runtime/vm/benchmark_test.h +++ b/runtime/vm/benchmark_test.h @@ -11,6 +11,7 @@ #include "vm/globals.h" #include "vm/heap.h" #include "vm/isolate.h" +#include "vm/malloc_hooks.h" #include "vm/object.h" #include "vm/zone.h" @@ -35,6 +36,9 @@ extern const uint8_t* core_isolate_snapshot_instructions; static void Dart_BenchmarkHelper##name(Benchmark* benchmark, \ Thread* thread); \ void Dart_Benchmark##name(Benchmark* benchmark) { \ + bool __stack_trace_collection_enabled__ = \ + MallocHooks::stack_trace_collection_enabled(); \ + MallocHooks::set_stack_trace_collection_enabled(false); \ FLAG_old_gen_growth_space_ratio = 100; \ BenchmarkIsolateScope __isolate__(benchmark); \ Thread* __thread__ = Thread::Current(); \ @@ -42,6 +46,8 @@ extern const uint8_t* core_isolate_snapshot_instructions; StackZone __zone__(__thread__); \ HandleScope __hs__(__thread__); \ Dart_BenchmarkHelper##name(benchmark, __thread__); \ + MallocHooks::set_stack_trace_collection_enabled( \ + __stack_trace_collection_enabled__); \ } \ static void Dart_BenchmarkHelper##name(Benchmark* benchmark, Thread* thread) diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc index 07cdcdc1fd4..83b1055b308 100644 --- a/runtime/vm/dart.cc +++ b/runtime/vm/dart.cc @@ -152,7 +152,6 @@ char* Dart::InitOnce(const uint8_t* vm_isolate_snapshot, start_time_micros_ = OS::GetCurrentMonotonicMicros(); VirtualMemory::InitOnce(); OSThread::InitOnce(); - MallocHooks::InitOnce(); if (FLAG_support_timeline) { Timeline::InitOnce(); } @@ -233,6 +232,13 @@ char* Dart::InitOnce(const uint8_t* vm_isolate_snapshot, return strdup("Precompiled runtime requires a precompiled snapshot"); #else StubCode::InitOnce(); + // MallocHooks can't be initialized until StubCode has been since stack + // trace generation relies on stub methods that are generated in + // StubCode::InitOnce(). + // TODO(bkonyi) Split initialization for stack trace collection from the + // initialization for the actual malloc hooks to increase accuracy of + // memory consumption statistics. + MallocHooks::InitOnce(); #endif } else { return strdup("Invalid vm isolate snapshot seen"); @@ -271,6 +277,13 @@ char* Dart::InitOnce(const uint8_t* vm_isolate_snapshot, #else vm_snapshot_kind_ = Snapshot::kNone; StubCode::InitOnce(); + // MallocHooks can't be initialized until StubCode has been since stack + // trace generation relies on stub methods that are generated in + // StubCode::InitOnce(). + // TODO(bkonyi) Split initialization for stack trace collection from the + // initialization for the actual malloc hooks to increase accuracy of + // memory consumption statistics. + MallocHooks::InitOnce(); Symbols::InitOnce(vm_isolate_); #endif } diff --git a/runtime/vm/hash_map.h b/runtime/vm/hash_map.h index 6526acaf5c6..c9eb941c9a1 100644 --- a/runtime/vm/hash_map.h +++ b/runtime/vm/hash_map.h @@ -27,7 +27,7 @@ class BaseDirectChainedHashMap : public B { BaseDirectChainedHashMap(const BaseDirectChainedHashMap& other); - ~BaseDirectChainedHashMap() { + virtual ~BaseDirectChainedHashMap() { allocator_->template Free(array_, array_size_); allocator_->template Free(lists_, lists_size_); } @@ -42,7 +42,7 @@ class BaseDirectChainedHashMap : public B { bool IsEmpty() const { return count_ == 0; } - void Clear() { + virtual void Clear() { if (!IsEmpty()) { count_ = 0; InitArray(array_, array_size_); diff --git a/runtime/vm/malloc_hooks.cc b/runtime/vm/malloc_hooks.cc index a85d22e85f3..7fb2238923b 100644 --- a/runtime/vm/malloc_hooks.cc +++ b/runtime/vm/malloc_hooks.cc @@ -4,7 +4,8 @@ #include "platform/globals.h" -#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) +#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) && \ + !defined(TARGET_ARCH_DBC) && !defined(TARGET_OS_FUCHSIA) #include "vm/malloc_hooks.h" @@ -14,92 +15,33 @@ #include "vm/hash_map.h" #include "vm/json_stream.h" #include "vm/os_thread.h" +#include "vm/profiler.h" namespace dart { -// A locker-type class similar to MutexLocker which tracks which thread -// currently holds the lock. We use this instead of MutexLocker and -// mutex->IsOwnedByCurrentThread() since IsOwnedByCurrentThread() is only -// enabled for debug mode. -class MallocLocker : public ValueObject { - public: - explicit MallocLocker(Mutex* mutex, ThreadId* owner) - : mutex_(mutex), owner_(owner) { - ASSERT(owner != NULL); - mutex_->Lock(); - ASSERT(*owner_ == OSThread::kInvalidThreadId); - *owner_ = OSThread::GetCurrentThreadId(); - } - - virtual ~MallocLocker() { - ASSERT(*owner_ == OSThread::GetCurrentThreadId()); - *owner_ = OSThread::kInvalidThreadId; - mutex_->Unlock(); - } - - private: - Mutex* mutex_; - ThreadId* owner_; -}; - - -// Custom key/value trait specifically for address/size pairs. Unlike -// RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. -class AddressKeyValueTrait { - public: - typedef const void* Key; - typedef intptr_t Value; - - struct Pair { - Key key; - Value value; - Pair() : key(NULL), value(-1) {} - Pair(const Key key, const Value& value) : key(key), value(value) {} - Pair(const Pair& other) : key(other.key), value(other.value) {} - }; - - static Key KeyOf(Pair kv) { return kv.key; } - static Value ValueOf(Pair kv) { return kv.value; } - static intptr_t Hashcode(Key key) { return reinterpret_cast(key); } - static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } -}; - - -// Map class that will be used to store mappings between ptr -> allocation size. -class AddressMap : public MallocDirectChainedHashMap { - public: - typedef AddressKeyValueTrait::Key Key; - typedef AddressKeyValueTrait::Value Value; - typedef AddressKeyValueTrait::Pair Pair; - - inline void Insert(const Key& key, const Value& value) { - Pair pair(key, value); - MallocDirectChainedHashMap::Insert(pair); - } - - inline bool Lookup(const Key& key, Value* value) { - ASSERT(value != NULL); - Pair* pair = MallocDirectChainedHashMap::Lookup(key); - if (pair == NULL) { - return false; - } else { - *value = pair->value; - return true; - } - } -}; - +class AddressMap; +// MallocHooksState contains all of the state related to the configuration of +// the malloc hooks, allocation information, and locks. class MallocHooksState : public AllStatic { public: static void RecordAllocHook(const void* ptr, size_t size); static void RecordFreeHook(const void* ptr); - static bool Active() { return active_; } - static void Init() { - address_map_ = new AddressMap(); - active_ = true; - original_pid_ = OS::ProcessId(); + static bool Active() { + ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); + return active_; + } + static void Init(); + + static bool ProfilingEnabled() { return (OSThread::TryCurrent() != NULL); } + + static bool stack_trace_collection_enabled() { + return stack_trace_collection_enabled_; + } + + static void set_stack_trace_collection_enabled(bool enabled) { + stack_trace_collection_enabled_ = enabled; } static bool IsOriginalProcess() { @@ -139,36 +81,146 @@ class MallocHooksState : public AllStatic { static AddressMap* address_map() { return address_map_; } - static void ResetStats() { - ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); - allocation_count_ = 0; - heap_allocated_memory_in_bytes_ = 0; - address_map_->Clear(); - } - - static void TearDown() { - ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); - active_ = false; - original_pid_ = kInvalidPid; - ResetStats(); - delete address_map_; - } + static void ResetStats(); + static void TearDown(); private: - static bool active_; - static intptr_t original_pid_; static Mutex* malloc_hook_mutex_; static ThreadId malloc_hook_mutex_owner_; + + // Variables protected by malloc_hook_mutex_. + static bool active_; + static bool stack_trace_collection_enabled_; static intptr_t allocation_count_; static intptr_t heap_allocated_memory_in_bytes_; static AddressMap* address_map_; + // End protected variables. + static intptr_t original_pid_; static const intptr_t kInvalidPid = -1; }; +// A locker-type class similar to MutexLocker which tracks which thread +// currently holds the lock. We use this instead of MutexLocker and +// mutex->IsOwnedByCurrentThread() since IsOwnedByCurrentThread() is only +// enabled for debug mode. +class MallocLocker : public ValueObject { + public: + explicit MallocLocker(Mutex* mutex, ThreadId* owner) + : mutex_(mutex), owner_(owner) { + ASSERT(owner != NULL); + mutex_->Lock(); + ASSERT(*owner_ == OSThread::kInvalidThreadId); + *owner_ = OSThread::GetCurrentThreadId(); + } + + virtual ~MallocLocker() { + ASSERT(*owner_ == OSThread::GetCurrentThreadId()); + *owner_ = OSThread::kInvalidThreadId; + mutex_->Unlock(); + } + + private: + Mutex* mutex_; + ThreadId* owner_; +}; + +// AllocationInfo contains all information related to a given allocation +// including: +// -Allocation size in bytes +// -Stack trace corresponding to the location of allocation, if applicable +class AllocationInfo { + public: + explicit AllocationInfo(intptr_t allocation_size) + : sample_(NULL), allocation_size_(allocation_size) { + // Stack trace collection is disabled when we are in the process of creating + // the first OSThread in order to prevent deadlocks. + if (MallocHooksState::ProfilingEnabled() && + MallocHooksState::stack_trace_collection_enabled()) { + sample_ = Profiler::SampleNativeAllocation(kSkipCount); + } + } + + Sample* sample() const { return sample_; } + intptr_t allocation_size() const { return allocation_size_; } + + private: + Sample* sample_; + intptr_t allocation_size_; + + // The number of frames that are generated by the malloc hooks and collection + // of the stack trace. These frames are ignored when collecting the stack + // trace for a memory allocation. If this number is incorrect, some tests in + // malloc_hook_tests.cc might fail, particularily + // StackTraceMallocHookLengthTest. If this value is updated, please make sure + // that the MallocHooks test cases pass on all platforms. + static const intptr_t kSkipCount = 5; +}; + + +// Custom key/value trait specifically for address/size pairs. Unlike +// RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. +class AddressKeyValueTrait : public AllStatic { + public: + typedef const void* Key; + typedef AllocationInfo* Value; + + struct Pair { + Key key; + Value value; + Pair() : key(NULL), value(NULL) {} + Pair(const Key key, const Value& value) : key(key), value(value) {} + Pair(const Pair& other) : key(other.key), value(other.value) {} + }; + + static Key KeyOf(Pair kv) { return kv.key; } + static Value ValueOf(Pair kv) { return kv.value; } + static intptr_t Hashcode(Key key) { return reinterpret_cast(key); } + static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } +}; + + +// Map class that will be used to store mappings between ptr -> allocation size. +class AddressMap : public MallocDirectChainedHashMap { + public: + typedef AddressKeyValueTrait::Key Key; + typedef AddressKeyValueTrait::Value Value; + typedef AddressKeyValueTrait::Pair Pair; + + virtual ~AddressMap() { Clear(); } + + void Insert(const Key& key, const Value& value) { + Pair pair(key, value); + MallocDirectChainedHashMap::Insert(pair); + } + + bool Lookup(const Key& key, Value* value) { + ASSERT(value != NULL); + Pair* pair = MallocDirectChainedHashMap::Lookup(key); + if (pair == NULL) { + return false; + } else { + *value = pair->value; + return true; + } + } + + void Clear() { + Iterator iter = GetIterator(); + Pair* result = iter.Next(); + while (result != NULL) { + delete result->value; + result->value = NULL; + result = iter.Next(); + } + MallocDirectChainedHashMap::Clear(); + } +}; + // MallocHooks state / locks. bool MallocHooksState::active_ = false; +bool MallocHooksState::stack_trace_collection_enabled_ = false; intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); ThreadId MallocHooksState::malloc_hook_mutex_owner_ = @@ -180,6 +232,36 @@ intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; AddressMap* MallocHooksState::address_map_ = NULL; +void MallocHooksState::Init() { + address_map_ = new AddressMap(); + active_ = true; +#if defined(DEBUG) + stack_trace_collection_enabled_ = true; +#else + stack_trace_collection_enabled_ = false; +#endif // defined(DEBUG) + original_pid_ = OS::ProcessId(); +} + + +void MallocHooksState::ResetStats() { + ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); + allocation_count_ = 0; + heap_allocated_memory_in_bytes_ = 0; + address_map_->Clear(); +} + + +void MallocHooksState::TearDown() { + ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); + active_ = false; + original_pid_ = kInvalidPid; + ResetStats(); + delete address_map_; + address_map_ = NULL; +} + + void MallocHooks::InitOnce() { if (!FLAG_enable_malloc_hooks) { return; @@ -218,6 +300,25 @@ void MallocHooks::TearDown() { } +bool MallocHooks::ProfilingEnabled() { + return MallocHooksState::ProfilingEnabled(); +} + + +bool MallocHooks::stack_trace_collection_enabled() { + MallocLocker ml(MallocHooksState::malloc_hook_mutex(), + MallocHooksState::malloc_hook_mutex_owner()); + return MallocHooksState::stack_trace_collection_enabled(); +} + + +void MallocHooks::set_stack_trace_collection_enabled(bool enabled) { + MallocLocker ml(MallocHooksState::malloc_hook_mutex(), + MallocHooksState::malloc_hook_mutex_owner()); + MallocHooksState::set_stack_trace_collection_enabled(enabled); +} + + void MallocHooks::ResetStats() { if (!FLAG_enable_malloc_hooks) { return; @@ -234,7 +335,9 @@ bool MallocHooks::Active() { if (!FLAG_enable_malloc_hooks) { return false; } - ASSERT(MallocHooksState::malloc_hook_mutex()->IsOwnedByCurrentThread()); + MallocLocker ml(MallocHooksState::malloc_hook_mutex(), + MallocHooksState::malloc_hook_mutex_owner()); + return MallocHooksState::Active(); } @@ -252,7 +355,7 @@ void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { { MallocLocker ml(MallocHooksState::malloc_hook_mutex(), MallocHooksState::malloc_hook_mutex_owner()); - if (Active()) { + if (MallocHooksState::Active()) { allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); allocation_count = MallocHooksState::allocation_count(); add_usage = true; @@ -285,6 +388,23 @@ intptr_t MallocHooks::heap_allocated_memory_in_bytes() { } +Sample* MallocHooks::GetSample(const void* ptr) { + MallocLocker ml(MallocHooksState::malloc_hook_mutex(), + MallocHooksState::malloc_hook_mutex_owner()); + + ASSERT(MallocHooksState::Active()); + + if (ptr != NULL) { + AllocationInfo* allocation_info = NULL; + if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { + ASSERT(allocation_info != NULL); + return allocation_info->sample(); + } + } + return NULL; +} + + void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { if (MallocHooksState::IsLockHeldByCurrentThread() || !MallocHooksState::IsOriginalProcess()) { @@ -296,7 +416,7 @@ void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { // Now that we hold the lock, check to make sure everything is still active. if ((ptr != NULL) && MallocHooksState::Active()) { MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); - MallocHooksState::address_map()->Insert(ptr, size); + MallocHooksState::address_map()->Insert(ptr, new AllocationInfo(size)); } } @@ -311,14 +431,17 @@ void MallocHooksState::RecordFreeHook(const void* ptr) { MallocHooksState::malloc_hook_mutex_owner()); // Now that we hold the lock, check to make sure everything is still active. if ((ptr != NULL) && MallocHooksState::Active()) { - intptr_t size = 0; - if (MallocHooksState::address_map()->Lookup(ptr, &size)) { - MallocHooksState::DecrementHeapAllocatedMemoryInBytes(size); + AllocationInfo* allocation_info = NULL; + if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { + MallocHooksState::DecrementHeapAllocatedMemoryInBytes( + allocation_info->allocation_size()); MallocHooksState::address_map()->Remove(ptr); + delete allocation_info; } } } } // namespace dart -#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) +#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) && + // !defined(TARGET_ARCH_DBC) && !defined(TARGET_OS_FUCHSIA) diff --git a/runtime/vm/malloc_hooks.h b/runtime/vm/malloc_hooks.h index 78132c756e1..64e423dfed9 100644 --- a/runtime/vm/malloc_hooks.h +++ b/runtime/vm/malloc_hooks.h @@ -11,14 +11,19 @@ namespace dart { class JSONObject; +class Sample; class MallocHooks : public AllStatic { public: static void InitOnce(); static void TearDown(); + static bool ProfilingEnabled(); + static bool stack_trace_collection_enabled(); + static void set_stack_trace_collection_enabled(bool enabled); static void ResetStats(); static bool Active(); static void PrintToJSONObject(JSONObject* jsobj); + static Sample* GetSample(const void* ptr); static intptr_t allocation_count(); static intptr_t heap_allocated_memory_in_bytes(); diff --git a/runtime/vm/malloc_hooks_test.cc b/runtime/vm/malloc_hooks_test.cc index 791e2694aa9..26a56478ae9 100644 --- a/runtime/vm/malloc_hooks_test.cc +++ b/runtime/vm/malloc_hooks_test.cc @@ -4,13 +4,15 @@ #include "platform/globals.h" -#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) +#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) && \ + !defined(TARGET_ARCH_DBC) && !defined(TARGET_OS_FUCHSIA) #include "platform/assert.h" -#include "vm/class_finalizer.h" #include "vm/globals.h" #include "vm/malloc_hooks.h" -#include "vm/symbols.h" +#include "vm/os.h" +#include "vm/profiler.h" +#include "vm/profiler_service.h" #include "vm/unit_test.h" namespace dart { @@ -64,7 +66,7 @@ UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) { EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes()); const intptr_t buffer_size = 10; - volatile char* buffer = new char[buffer_size]; + char* buffer = new char[buffer_size]; MallocHookTestBufferInitializer(buffer, buffer_size); EXPECT_EQ(1L, MallocHooks::allocation_count()); @@ -85,6 +87,123 @@ UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) { FLAG_enable_malloc_hooks = enable_malloc_hooks_saved; } + +VM_UNIT_TEST_CASE(StackTraceMallocHookSimpleTest) { + bool enable_malloc_hooks_saved = FLAG_enable_malloc_hooks; + FLAG_enable_malloc_hooks = true; + + MallocHooks::InitOnce(); + MallocHooks::ResetStats(); + + bool enable_stack_traces_saved = + MallocHooks::stack_trace_collection_enabled(); + MallocHooks::set_stack_trace_collection_enabled(true); + + char* var = static_cast(malloc(16 * sizeof(char))); + Sample* sample = MallocHooks::GetSample(var); + EXPECT(sample != NULL); + + free(var); + sample = MallocHooks::GetSample(var); + EXPECT(sample == NULL); + MallocHooks::TearDown(); + MallocHooks::set_stack_trace_collection_enabled(enable_stack_traces_saved); + FLAG_enable_malloc_hooks = enable_malloc_hooks_saved; +} + + +static char* DART_NOINLINE StackTraceLengthHelper(uintptr_t* end_address) { + char* var = static_cast(malloc(16 * sizeof(char))); + *end_address = OS::GetProgramCounter(); + return var; +} + + +VM_UNIT_TEST_CASE(StackTraceMallocHookLengthTest) { + bool enable_malloc_hooks_saved = FLAG_enable_malloc_hooks; + FLAG_enable_malloc_hooks = true; + + uintptr_t test_start_address = + reinterpret_cast(Dart_TestStackTraceMallocHookLengthTest); + uintptr_t helper_start_address = + reinterpret_cast(StackTraceLengthHelper); + uintptr_t helper_end_address = 0; + + MallocHooks::InitOnce(); + MallocHooks::ResetStats(); + + bool enable_stack_traces_saved = + MallocHooks::stack_trace_collection_enabled(); + MallocHooks::set_stack_trace_collection_enabled(true); + + char* var = StackTraceLengthHelper(&helper_end_address); + Sample* sample = MallocHooks::GetSample(var); + EXPECT(sample != NULL); + uintptr_t test_end_address = OS::GetProgramCounter(); + + // Ensure that all stack frames are where we expect them to be in the sample. + // If they aren't, the kSkipCount constant in malloc_hooks.cc is likely + // incorrect. + uword address = sample->At(0); + bool first_result = + (helper_start_address <= address) && (helper_end_address >= address); + EXPECT(first_result); + address = sample->At(1); + bool second_result = + (test_start_address <= address) && (test_end_address >= address); + EXPECT(second_result); + + if (!(first_result && second_result)) { + OS::PrintErr( + "If this test is failing, it's likely that the value set for" + "the number of frames to skip in malloc_hooks.cc is " + "incorrect for this configuration/platform. This value can be" + " found in malloc_hooks.cc in the AllocationInfo class, and " + "is stored in the kSkipCount constant."); + } + + free(var); + MallocHooks::TearDown(); + MallocHooks::set_stack_trace_collection_enabled(enable_stack_traces_saved); + FLAG_enable_malloc_hooks = enable_malloc_hooks_saved; +} + + +ISOLATE_UNIT_TEST_CASE(StackTraceMallocHookSimpleJSONTest) { + bool enable_malloc_hooks_saved = FLAG_enable_malloc_hooks; + FLAG_enable_malloc_hooks = true; + + MallocHooks::InitOnce(); + MallocHooks::ResetStats(); + + bool enable_stack_traces_saved = + MallocHooks::stack_trace_collection_enabled(); + MallocHooks::set_stack_trace_collection_enabled(true); + + ClearProfileVisitor cpv(Isolate::Current()); + Profiler::sample_buffer()->VisitSamples(&cpv); + + char* var = static_cast(malloc(16 * sizeof(char))); + JSONStream js; + ProfilerService::PrintNativeAllocationJSON(&js, Profile::kNoTags, -1, -1); + const char* json = js.ToCString(); + + // Check that all the stack frames from the current down to main are actually + // present in the profile. This is just a simple sanity check to make sure + // that the ProfileTrie has a representation of the stack trace collected when + // var is allocated. More intense testing is already done in profiler_test.cc. + EXPECT_SUBSTRING("\"dart::Dart_TestStackTraceMallocHookSimpleJSONTest()\"", + json); + EXPECT_SUBSTRING("\"dart::TestCase::Run()\"", json); + EXPECT_SUBSTRING("\"dart::TestCaseBase::RunTest()\"", json); + EXPECT_SUBSTRING("\"main\"", json); + + free(var); + MallocHooks::TearDown(); + MallocHooks::set_stack_trace_collection_enabled(enable_stack_traces_saved); + FLAG_enable_malloc_hooks = enable_malloc_hooks_saved; +} + }; // namespace dart #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) diff --git a/runtime/vm/malloc_hooks_unsupported.cc b/runtime/vm/malloc_hooks_unsupported.cc index 733e3ded74e..1f8b75a45dd 100644 --- a/runtime/vm/malloc_hooks_unsupported.cc +++ b/runtime/vm/malloc_hooks_unsupported.cc @@ -4,7 +4,8 @@ #include "platform/globals.h" -#if !defined(DART_USE_TCMALLOC) || defined(PRODUCT) +#if !defined(DART_USE_TCMALLOC) || defined(PRODUCT) || \ + defined(TARGET_ARCH_DBC) || defined(TARGET_OS_FUCHSIA) #include "vm/malloc_hooks.h" @@ -20,6 +21,21 @@ void MallocHooks::TearDown() { } +bool MallocHooks::ProfilingEnabled() { + return false; +} + + +bool MallocHooks::stack_trace_collection_enabled() { + return false; +} + + +void MallocHooks::set_stack_trace_collection_enabled(bool enabled) { + // Do nothing. +} + + void MallocHooks::ResetStats() { // Do nothing. } @@ -46,4 +62,5 @@ intptr_t MallocHooks::heap_allocated_memory_in_bytes() { } // namespace dart -#endif // defined(DART_USE_TCMALLOC) || defined(PRODUCT) +#endif // !defined(DART_USE_TCMALLOC) || defined(PRODUCT) || + // defined(TARGET_ARCH_DBC) || defined(TARGET_OS_FUCHSIA) diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc index c435602c24a..fb4b621d610 100644 --- a/runtime/vm/object_test.cc +++ b/runtime/vm/object_test.cc @@ -10,6 +10,7 @@ #include "vm/dart_entry.h" #include "vm/debugger.h" #include "vm/isolate.h" +#include "vm/malloc_hooks.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/simulator.h" @@ -2709,6 +2710,9 @@ ISOLATE_UNIT_TEST_CASE(Code) { // Test for immutability of generated instructions. The test crashes with a // segmentation fault when writing into it. ISOLATE_UNIT_TEST_CASE(CodeImmutability) { + bool stack_trace_collection_enabled = + MallocHooks::stack_trace_collection_enabled(); + MallocHooks::set_stack_trace_collection_enabled(false); extern void GenerateIncrement(Assembler * assembler); Assembler _assembler_; GenerateIncrement(&_assembler_); @@ -2726,6 +2730,8 @@ ISOLATE_UNIT_TEST_CASE(CodeImmutability) { // TODO(regis, fschneider): Should this be FATAL() instead? OS::DebugBreak(); } + MallocHooks::set_stack_trace_collection_enabled( + stack_trace_collection_enabled); } diff --git a/runtime/vm/os.h b/runtime/vm/os.h index b90b06da823..9f1de5cb4bf 100644 --- a/runtime/vm/os.h +++ b/runtime/vm/os.h @@ -95,6 +95,9 @@ class OS { // Debug break. static void DebugBreak(); + // Returns the current program counter. + static uintptr_t GetProgramCounter(); + // Not all platform support strndup. static char* StrNDup(const char* s, intptr_t n); static intptr_t StrNLen(const char* s, intptr_t n); diff --git a/runtime/vm/os_android.cc b/runtime/vm/os_android.cc index 1ce9650dea0..f3673ee4999 100644 --- a/runtime/vm/os_android.cc +++ b/runtime/vm/os_android.cc @@ -276,6 +276,12 @@ void OS::DebugBreak() { } +uintptr_t DART_NOINLINE OS::GetProgramCounter() { + return reinterpret_cast( + __builtin_extract_return_addr(__builtin_return_address(0))); +} + + char* OS::StrNDup(const char* s, intptr_t n) { return strndup(s, n); } diff --git a/runtime/vm/os_fuchsia.cc b/runtime/vm/os_fuchsia.cc index 9c850ed4dc3..1a96aaa3433 100644 --- a/runtime/vm/os_fuchsia.cc +++ b/runtime/vm/os_fuchsia.cc @@ -177,6 +177,12 @@ void OS::DebugBreak() { } +uintptr_t DART_NOINLINE OS::GetProgramCounter() { + return reinterpret_cast( + __builtin_extract_return_addr(__builtin_return_address(0))); +} + + char* OS::StrNDup(const char* s, intptr_t n) { return strndup(s, n); } diff --git a/runtime/vm/os_linux.cc b/runtime/vm/os_linux.cc index 163f436ea63..e73996bc7fb 100644 --- a/runtime/vm/os_linux.cc +++ b/runtime/vm/os_linux.cc @@ -287,6 +287,12 @@ void OS::DebugBreak() { } +uintptr_t DART_NOINLINE OS::GetProgramCounter() { + return reinterpret_cast( + __builtin_extract_return_addr(__builtin_return_address(0))); +} + + char* OS::StrNDup(const char* s, intptr_t n) { return strndup(s, n); } diff --git a/runtime/vm/os_macos.cc b/runtime/vm/os_macos.cc index 7038dd55a29..efeb76c81ca 100644 --- a/runtime/vm/os_macos.cc +++ b/runtime/vm/os_macos.cc @@ -266,6 +266,12 @@ void OS::DebugBreak() { } +uintptr_t DART_NOINLINE OS::GetProgramCounter() { + return reinterpret_cast( + __builtin_extract_return_addr(__builtin_return_address(0))); +} + + char* OS::StrNDup(const char* s, intptr_t n) { // strndup has only been added to Mac OS X in 10.7. We are supplying // our own copy here if needed. diff --git a/runtime/vm/os_thread.h b/runtime/vm/os_thread.h index a3693af4308..3130b25ef44 100644 --- a/runtime/vm/os_thread.h +++ b/runtime/vm/os_thread.h @@ -116,7 +116,7 @@ class OSThread : public BaseThread { bool ThreadInterruptsEnabled(); // The currently executing thread, or NULL if not yet initialized. - static OSThread* Current() { + static OSThread* TryCurrent() { BaseThread* thread = GetCurrentTLS(); OSThread* os_thread = NULL; if (thread != NULL) { @@ -126,7 +126,15 @@ class OSThread : public BaseThread { Thread* vm_thread = reinterpret_cast(thread); os_thread = GetOSThreadFromThread(vm_thread); } - } else { + } + return os_thread; + } + + // The currently executing thread. If there is no currently executing thread, + // a new OSThread is created and returned. + static OSThread* Current() { + OSThread* os_thread = TryCurrent(); + if (os_thread == NULL) { os_thread = CreateAndSetUnknownThread(); } return os_thread; diff --git a/runtime/vm/os_win.cc b/runtime/vm/os_win.cc index 87d2247dc01..0c5bb0e3550 100644 --- a/runtime/vm/os_win.cc +++ b/runtime/vm/os_win.cc @@ -265,6 +265,11 @@ void OS::DebugBreak() { } +DART_NOINLINE uintptr_t OS::GetProgramCounter() { + return reinterpret_cast(_ReturnAddress()); +} + + char* OS::StrNDup(const char* s, intptr_t n) { intptr_t len = strlen(s); if ((n < 0) || (len < 0)) { diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc index e637be31f49..7110d082ae2 100644 --- a/runtime/vm/profiler.cc +++ b/runtime/vm/profiler.cc @@ -168,7 +168,7 @@ Sample* SampleBuffer::ReserveSampleAndLink(Sample* previous) { ASSERT(previous != NULL); intptr_t next_index = ReserveSampleSlot(); Sample* next = At(next_index); - next->Init(previous->isolate(), previous->timestamp(), previous->tid()); + next->Init(previous->port(), previous->timestamp(), previous->tid()); next->set_head_sample(false); // Mark that previous continues at next. previous->SetContinuationIndex(next_index); @@ -321,7 +321,7 @@ bool SampleFilter::TaskFilterSample(Sample* sample) { ClearProfileVisitor::ClearProfileVisitor(Isolate* isolate) - : SampleVisitor(isolate) {} + : SampleVisitor(isolate->main_port()) {} void ClearProfileVisitor::VisitSample(Sample* sample) { @@ -357,15 +357,17 @@ static void DumpStackFrame(intptr_t frame_index, uword pc) { class ProfilerStackWalker : public ValueObject { public: - ProfilerStackWalker(Isolate* isolate, + ProfilerStackWalker(Dart_Port port_id, Sample* head_sample, - SampleBuffer* sample_buffer) - : isolate_(isolate), + SampleBuffer* sample_buffer, + intptr_t skip_count = 0) + : port_id_(port_id), sample_(head_sample), sample_buffer_(sample_buffer), + skip_count_(skip_count), + frames_skipped_(0), frame_index_(0), total_frames_(0) { - ASSERT(isolate_ != NULL); if (sample_ == NULL) { ASSERT(sample_buffer_ == NULL); } else { @@ -375,6 +377,11 @@ class ProfilerStackWalker : public ValueObject { } bool Append(uword pc) { + if (frames_skipped_ < skip_count_) { + frames_skipped_++; + return true; + } + if (sample_ == NULL) { DumpStackFrame(frame_index_, pc); frame_index_++; @@ -404,9 +411,11 @@ class ProfilerStackWalker : public ValueObject { } protected: - Isolate* isolate_; + Dart_Port port_id_; Sample* sample_; SampleBuffer* sample_buffer_; + intptr_t skip_count_; + intptr_t frames_skipped_; intptr_t frame_index_; intptr_t total_frames_; }; @@ -424,8 +433,14 @@ class ProfilerDartStackWalker : public ProfilerStackWalker { uword fp, uword sp, bool exited_dart_code, - bool allocation_sample) - : ProfilerStackWalker(thread->isolate(), sample, sample_buffer), + bool allocation_sample, + intptr_t skip_count = 0) + : ProfilerStackWalker((thread->isolate() != NULL) + ? thread->isolate()->main_port() + : ILLEGAL_PORT, + sample, + sample_buffer, + skip_count), pc_(reinterpret_cast(pc)), fp_(reinterpret_cast(fp)), sp_(reinterpret_cast(sp)), @@ -597,15 +612,16 @@ class ProfilerDartStackWalker : public ProfilerStackWalker { // class ProfilerNativeStackWalker : public ProfilerStackWalker { public: - ProfilerNativeStackWalker(Isolate* isolate, + ProfilerNativeStackWalker(Dart_Port port_id, Sample* sample, SampleBuffer* sample_buffer, uword stack_lower, uword stack_upper, uword pc, uword fp, - uword sp) - : ProfilerStackWalker(isolate, sample, sample_buffer), + uword sp, + intptr_t skip_count = 0) + : ProfilerStackWalker(port_id, sample, sample_buffer, skip_count), stack_upper_(stack_upper), original_pc_(pc), original_fp_(fp), @@ -614,6 +630,7 @@ class ProfilerNativeStackWalker : public ProfilerStackWalker { void walk() { const uword kMaxStep = VirtualMemory::PageSize(); + Append(original_pc_); uword* pc = reinterpret_cast(original_pc_); @@ -796,6 +813,29 @@ static void CollectSample(Isolate* isolate, } +static bool ValidateThreadStackBounds(uintptr_t fp, + uintptr_t sp, + uword stack_lower, + uword stack_upper) { + if (stack_lower >= stack_upper) { + // Stack boundary is invalid. + return false; + } + + if ((sp < stack_lower) || (sp >= stack_upper)) { + // Stack pointer is outside thread's stack boundary. + return false; + } + + if ((fp < stack_lower) || (fp >= stack_upper)) { + // Frame pointer is outside threads's stack boundary. + return false; + } + + return true; +} + + // Get |isolate|'s stack boundary and verify that |sp| and |fp| are within // it. If |get_os_thread_bounds| is true then if |isolate| stackbounds are // not available we fallback to using underlying OS thread bounds. This only @@ -807,8 +847,12 @@ static bool GetAndValidateThreadStackBounds(Thread* thread, uword* stack_lower, uword* stack_upper, bool get_os_thread_bounds = false) { - ASSERT(thread != NULL); - OSThread* os_thread = thread->os_thread(); + OSThread* os_thread = NULL; + if (thread != NULL) { + os_thread = thread->os_thread(); + } else { + os_thread = OSThread::Current(); + } ASSERT(os_thread != NULL); ASSERT(stack_lower != NULL); ASSERT(stack_upper != NULL); @@ -844,22 +888,7 @@ static bool GetAndValidateThreadStackBounds(Thread* thread, *stack_lower = sp; } - if (*stack_lower >= *stack_upper) { - // Stack boundary is invalid. - return false; - } - - if ((sp < *stack_lower) || (sp >= *stack_upper)) { - // Stack pointer is outside thread's stack boundary. - return false; - } - - if ((fp < *stack_lower) || (fp >= *stack_upper)) { - // Frame pointer is outside threads's stack boundary. - return false; - } - - return true; + return ValidateThreadStackBounds(fp, sp, *stack_lower, *stack_upper); } @@ -887,7 +916,7 @@ static Sample* SetupSample(Thread* thread, Isolate* isolate = thread->isolate(); ASSERT(sample_buffer != NULL); Sample* sample = sample_buffer->ReserveSample(); - sample->Init(isolate, OS::GetCurrentMonotonicMicros(), tid); + sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), tid); uword vm_tag = thread->vm_tag(); #if defined(USING_SIMULATOR) && !defined(TARGET_ARCH_DBC) // When running in the simulator, the runtime entry function address @@ -905,6 +934,23 @@ static Sample* SetupSample(Thread* thread, } +static Sample* SetupSampleNative(SampleBuffer* sample_buffer, ThreadId tid) { + Sample* sample = sample_buffer->ReserveSample(); + sample->Init(ILLEGAL_PORT, OS::GetCurrentMonotonicMicros(), tid); + sample->set_is_native_allocation_sample(true); + + Thread* thread = Thread::Current(); + + // TODO(bkonyi) Any samples created while a current thread doesn't exist are + // ignored by the NativeAllocationSampleFilter since the default task is + // kUnknownTask. Is this what we want to do? + if (thread != NULL) { + sample->set_thread_task(thread->task_kind()); + } + return sample; +} + + static bool CheckIsolate(Isolate* isolate) { if ((isolate == NULL) || (Dart::vm_isolate() == NULL)) { // No isolate. @@ -914,18 +960,6 @@ static bool CheckIsolate(Isolate* isolate) { } -#if defined(TARGET_OS_WINDOWS) -__declspec(noinline) static uintptr_t GetProgramCounter() { - return reinterpret_cast(_ReturnAddress()); -} -#else -static uintptr_t __attribute__((noinline)) GetProgramCounter() { - return reinterpret_cast( - __builtin_extract_return_addr(__builtin_return_address(0))); -} -#endif - - void Profiler::DumpStackTrace(void* context) { #if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) ucontext_t* ucontext = reinterpret_cast(context); @@ -944,7 +978,7 @@ void Profiler::DumpStackTrace(void* context) { void Profiler::DumpStackTrace() { uintptr_t sp = Thread::GetCurrentStackPointer(); uintptr_t fp = 0; - uintptr_t pc = GetProgramCounter(); + uintptr_t pc = OS::GetProgramCounter(); COPY_FP_REGISTER(fp); @@ -992,7 +1026,8 @@ void Profiler::DumpStackTrace(uword sp, uword fp, uword pc) { } ProfilerNativeStackWalker native_stack_walker( - isolate, NULL, NULL, stack_lower, stack_upper, pc, fp, sp); + (isolate != NULL) ? isolate->main_port() : ILLEGAL_PORT, NULL, NULL, + stack_lower, stack_upper, pc, fp, sp); native_stack_walker.walk(); OS::PrintErr("-- End of DumpStackTrace\n"); } @@ -1017,7 +1052,7 @@ void Profiler::SampleAllocation(Thread* thread, intptr_t cid) { uintptr_t sp = Thread::GetCurrentStackPointer(); uintptr_t fp = 0; - uintptr_t pc = GetProgramCounter(); + uintptr_t pc = OS::GetProgramCounter(); COPY_FP_REGISTER(fp); @@ -1039,7 +1074,8 @@ void Profiler::SampleAllocation(Thread* thread, intptr_t cid) { if (FLAG_profile_vm) { ProfilerNativeStackWalker native_stack_walker( - isolate, sample, sample_buffer, stack_lower, stack_upper, pc, fp, sp); + (isolate != NULL) ? isolate->main_port() : ILLEGAL_PORT, sample, + sample_buffer, stack_lower, stack_upper, pc, fp, sp); native_stack_walker.walk(); } else if (exited_dart_code) { ProfilerDartStackWalker dart_exit_stack_walker( @@ -1048,7 +1084,7 @@ void Profiler::SampleAllocation(Thread* thread, intptr_t cid) { dart_exit_stack_walker.walk(); } else { // Fall back. - uintptr_t pc = GetProgramCounter(); + uintptr_t pc = OS::GetProgramCounter(); Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id()); sample->SetAllocationCid(cid); sample->SetAt(0, pc); @@ -1056,6 +1092,44 @@ void Profiler::SampleAllocation(Thread* thread, intptr_t cid) { } +Sample* Profiler::SampleNativeAllocation(intptr_t skip_count) { + SampleBuffer* sample_buffer = Profiler::sample_buffer(); + if (sample_buffer == NULL) { + return NULL; + } + + uintptr_t sp = Thread::GetCurrentStackPointer(); + uintptr_t fp = 0; + uintptr_t pc = OS::GetProgramCounter(); + + COPY_FP_REGISTER(fp); + + uword stack_lower = 0; + uword stack_upper = 0; + if (!InitialRegisterCheck(pc, fp, sp)) { + AtomicOperations::IncrementInt64By( + &counters_.failure_native_allocation_sample, 1); + return NULL; + } + + if (!(OSThread::GetCurrentStackBounds(&stack_lower, &stack_upper) && + ValidateThreadStackBounds(fp, sp, stack_lower, stack_upper))) { + // Could not get stack boundary. + AtomicOperations::IncrementInt64By( + &counters_.failure_native_allocation_sample, 1); + return NULL; + } + + OSThread* os_thread = OSThread::Current(); + Sample* sample = SetupSampleNative(sample_buffer, os_thread->trace_id()); + ProfilerNativeStackWalker native_stack_walker( + ILLEGAL_PORT, sample, sample_buffer, stack_lower, stack_upper, pc, fp, sp, + skip_count); + native_stack_walker.walk(); + return sample; +} + + void Profiler::SampleThreadSingleFrame(Thread* thread, uintptr_t pc) { ASSERT(thread != NULL); OSThread* os_thread = thread->os_thread(); @@ -1181,7 +1255,8 @@ void Profiler::SampleThread(Thread* thread, } ProfilerNativeStackWalker native_stack_walker( - isolate, sample, sample_buffer, stack_lower, stack_upper, pc, fp, sp); + (isolate != NULL) ? isolate->main_port() : ILLEGAL_PORT, sample, + sample_buffer, stack_lower, stack_upper, pc, fp, sp); const bool exited_dart_code = thread->HasExitedDartCode(); ProfilerDartStackWalker dart_stack_walker(thread, sample, sample_buffer, stack_lower, stack_upper, pc, fp, @@ -1341,7 +1416,9 @@ ProcessedSampleBuffer* SampleBuffer::BuildProcessedSampleBuffer( // An inner sample in a chain of samples. continue; } - if (sample->isolate() != filter->isolate()) { + // If we're requesting all the native allocation samples, we don't care + // whether or not we're in the same isolate as the sample. + if (sample->port() != filter->port()) { // Another isolate. continue; } @@ -1420,7 +1497,7 @@ Sample* SampleBuffer::Next(Sample* sample) { // Sanity check. ASSERT(sample != next_sample); // Detect invalid chaining. - if (sample->isolate() != next_sample->isolate()) { + if (sample->port() != next_sample->port()) { return NULL; } if (sample->timestamp() != next_sample->timestamp()) { diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h index ee87a31d954..9f967cf9bea 100644 --- a/runtime/vm/profiler.h +++ b/runtime/vm/profiler.h @@ -41,6 +41,8 @@ struct ProfilerCounters { int64_t stack_walker_dart_exit; int64_t stack_walker_dart; int64_t stack_walker_none; + // Count of failed checks: + int64_t failure_native_allocation_sample; }; @@ -58,6 +60,7 @@ class Profiler : public AllStatic { static void DumpStackTrace(); static void SampleAllocation(Thread* thread, intptr_t cid); + static Sample* SampleNativeAllocation(intptr_t skip_count); // SampleThread is called from inside the signal handler and hence it is very // critical that the implementation of SampleThread does not do any of the @@ -91,7 +94,7 @@ class Profiler : public AllStatic { class SampleVisitor : public ValueObject { public: - explicit SampleVisitor(Isolate* isolate) : isolate_(isolate), visited_(0) {} + explicit SampleVisitor(Dart_Port port) : port_(port), visited_(0) {} virtual ~SampleVisitor() {} virtual void VisitSample(Sample* sample) = 0; @@ -100,10 +103,10 @@ class SampleVisitor : public ValueObject { void IncrementVisited() { visited_++; } - Isolate* isolate() const { return isolate_; } + Dart_Port port() const { return port_; } private: - Isolate* isolate_; + Dart_Port port_; intptr_t visited_; DISALLOW_IMPLICIT_CONSTRUCTORS(SampleVisitor); @@ -112,11 +115,11 @@ class SampleVisitor : public ValueObject { class SampleFilter : public ValueObject { public: - SampleFilter(Isolate* isolate, + SampleFilter(Dart_Port port, intptr_t thread_task_mask, int64_t time_origin_micros, int64_t time_extent_micros) - : isolate_(isolate), + : port_(port), thread_task_mask_(thread_task_mask), time_origin_micros_(time_origin_micros), time_extent_micros_(time_extent_micros) { @@ -130,7 +133,7 @@ class SampleFilter : public ValueObject { // Return |true| if |sample| passes the filter. virtual bool FilterSample(Sample* sample) { return true; } - Isolate* isolate() const { return isolate_; } + Dart_Port port() const { return port_; } // Returns |true| if |sample| passes the time filter. bool TimeFilterSample(Sample* sample); @@ -139,7 +142,7 @@ class SampleFilter : public ValueObject { bool TaskFilterSample(Sample* sample); private: - Isolate* isolate_; + Dart_Port port_; intptr_t thread_task_mask_; int64_t time_origin_micros_; int64_t time_extent_micros_; @@ -157,21 +160,20 @@ class ClearProfileVisitor : public SampleVisitor { // Each Sample holds a stack trace from an isolate. class Sample { public: - void Init(Isolate* isolate, int64_t timestamp, ThreadId tid) { + void Init(Dart_Port port, int64_t timestamp, ThreadId tid) { Clear(); timestamp_ = timestamp; tid_ = tid; - isolate_ = isolate; + port_ = port; } - // Isolate sample was taken from. - Isolate* isolate() const { return isolate_; } + Dart_Port port() const { return port_; } // Thread sample was taken on. ThreadId tid() const { return tid_; } void Clear() { - isolate_ = NULL; + port_ = ILLEGAL_PORT; pc_marker_ = 0; for (intptr_t i = 0; i < kStackBufferSizeInWords; i++) { stack_buffer_[i] = 0; @@ -268,6 +270,15 @@ class Sample { state_ = ClassAllocationSampleBit::update(allocation_sample, state_); } + bool is_native_allocation_sample() const { + return NativeAllocationSampleBit::decode(state_); + } + + void set_is_native_allocation_sample(bool native_allocation_sample) { + state_ = + NativeAllocationSampleBit::update(native_allocation_sample, state_); + } + Thread::TaskKind thread_task() const { return ThreadTaskBit::decode(state_); } void set_thread_task(Thread::TaskKind task) { @@ -331,7 +342,8 @@ class Sample { kClassAllocationSampleBit = 6, kContinuationSampleBit = 7, kThreadTaskBit = 8, // 5 bits. - kNextFreeBit = 13, + kNativeAllocationSampleBit = 13, + kNextFreeBit = 14, }; class HeadSampleBit : public BitField {}; class LeafFrameIsDart : public BitField { @@ -348,10 +360,12 @@ class Sample { : public BitField {}; class ThreadTaskBit : public BitField {}; + class NativeAllocationSampleBit + : public BitField {}; int64_t timestamp_; ThreadId tid_; - Isolate* isolate_; + Dart_Port port_; uword pc_marker_; uword stack_buffer_[kStackBufferSizeInWords]; uword vm_tag_; @@ -474,7 +488,7 @@ class SampleBuffer { // Bad sample. continue; } - if (sample->isolate() != visitor->isolate()) { + if (sample->port() != visitor->port()) { // Another isolate. continue; } diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc index 5290e0697d0..8b9bee9489e 100644 --- a/runtime/vm/profiler_service.cc +++ b/runtime/vm/profiler_service.cc @@ -2670,16 +2670,19 @@ void ProfilerService::PrintJSONImpl(Thread* thread, class NoAllocationSampleFilter : public SampleFilter { public: - NoAllocationSampleFilter(Isolate* isolate, + NoAllocationSampleFilter(Dart_Port port, intptr_t thread_task_mask, int64_t time_origin_micros, int64_t time_extent_micros) - : SampleFilter(isolate, + : SampleFilter(port, thread_task_mask, time_origin_micros, time_extent_micros) {} - bool FilterSample(Sample* sample) { return !sample->is_allocation_sample(); } + bool FilterSample(Sample* sample) { + return !sample->is_allocation_sample() && + !sample->is_native_allocation_sample(); + } }; @@ -2690,7 +2693,7 @@ void ProfilerService::PrintJSON(JSONStream* stream, int64_t time_extent_micros) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); - NoAllocationSampleFilter filter(isolate, Thread::kMutatorTask, + NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask, time_origin_micros, time_extent_micros); const bool as_timeline = false; PrintJSONImpl(thread, stream, tag_order, extra_tags, &filter, as_timeline); @@ -2699,12 +2702,12 @@ void ProfilerService::PrintJSON(JSONStream* stream, class ClassAllocationSampleFilter : public SampleFilter { public: - ClassAllocationSampleFilter(Isolate* isolate, + ClassAllocationSampleFilter(Dart_Port port, const Class& cls, intptr_t thread_task_mask, int64_t time_origin_micros, int64_t time_extent_micros) - : SampleFilter(isolate, + : SampleFilter(port, thread_task_mask, time_origin_micros, time_extent_micros), @@ -2722,6 +2725,21 @@ class ClassAllocationSampleFilter : public SampleFilter { }; +class NativeAllocationSampleFilter : public SampleFilter { + public: + NativeAllocationSampleFilter(intptr_t thread_task_mask, + int64_t time_origin_micros, + int64_t time_extent_micros) + : SampleFilter(ILLEGAL_PORT, + thread_task_mask, + time_origin_micros, + time_extent_micros) {} + bool FilterSample(Sample* sample) { + return sample->is_native_allocation_sample(); + } +}; + + void ProfilerService::PrintAllocationJSON(JSONStream* stream, Profile::TagOrder tag_order, const Class& cls, @@ -2729,8 +2747,24 @@ void ProfilerService::PrintAllocationJSON(JSONStream* stream, int64_t time_extent_micros) { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); - ClassAllocationSampleFilter filter(isolate, cls, Thread::kMutatorTask, - time_origin_micros, time_extent_micros); + ClassAllocationSampleFilter filter(isolate->main_port(), cls, + Thread::kMutatorTask, time_origin_micros, + time_extent_micros); + const bool as_timeline = false; + PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, as_timeline); +} + + +void ProfilerService::PrintNativeAllocationJSON(JSONStream* stream, + Profile::TagOrder tag_order, + int64_t time_origin_micros, + int64_t time_extent_micros) { + Thread* thread = Thread::Current(); + const intptr_t thread_task_mask = Thread::kMutatorTask | + Thread::kCompilerTask | + Thread::kSweeperTask | Thread::kMarkerTask; + NativeAllocationSampleFilter filter(thread_task_mask, time_origin_micros, + time_extent_micros); const bool as_timeline = false; PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, as_timeline); } @@ -2745,8 +2779,8 @@ void ProfilerService::PrintTimelineJSON(JSONStream* stream, const intptr_t thread_task_mask = Thread::kMutatorTask | Thread::kCompilerTask | Thread::kSweeperTask | Thread::kMarkerTask; - NoAllocationSampleFilter filter(isolate, thread_task_mask, time_origin_micros, - time_extent_micros); + NoAllocationSampleFilter filter(isolate->main_port(), thread_task_mask, + time_origin_micros, time_extent_micros); const bool as_timeline = true; PrintJSONImpl(thread, stream, tag_order, kNoExtraTags, &filter, as_timeline); } diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h index 7251432f651..d9205b3b7a1 100644 --- a/runtime/vm/profiler_service.h +++ b/runtime/vm/profiler_service.h @@ -419,6 +419,11 @@ class ProfilerService : public AllStatic { int64_t time_origin_micros, int64_t time_extent_micros); + static void PrintNativeAllocationJSON(JSONStream* stream, + Profile::TagOrder tag_order, + int64_t time_origin_micros, + int64_t time_extent_micros); + static void PrintTimelineJSON(JSONStream* stream, Profile::TagOrder tag_order, int64_t time_origin_micros, diff --git a/runtime/vm/profiler_test.cc b/runtime/vm/profiler_test.cc index bcb32618de5..08d3980cb74 100644 --- a/runtime/vm/profiler_test.cc +++ b/runtime/vm/profiler_test.cc @@ -68,12 +68,12 @@ class MaxProfileDepthScope : public ValueObject { class ProfileSampleBufferTestHelper { public: - static intptr_t IterateCount(const Isolate* isolate, + static intptr_t IterateCount(const Dart_Port port, const SampleBuffer& sample_buffer) { intptr_t c = 0; for (intptr_t i = 0; i < sample_buffer.capacity(); i++) { Sample* sample = sample_buffer.At(i); - if (sample->isolate() != isolate) { + if (sample->port() != port) { continue; } c++; @@ -82,12 +82,12 @@ class ProfileSampleBufferTestHelper { } - static intptr_t IterateSumPC(const Isolate* isolate, + static intptr_t IterateSumPC(const Dart_Port port, const SampleBuffer& sample_buffer) { intptr_t c = 0; for (intptr_t i = 0; i < sample_buffer.capacity(); i++) { Sample* sample = sample_buffer.At(i); - if (sample->isolate() != isolate) { + if (sample->port() != port) { continue; } c += sample->At(0); @@ -99,7 +99,7 @@ class ProfileSampleBufferTestHelper { TEST_CASE(Profiler_SampleBufferWrapTest) { SampleBuffer* sample_buffer = new SampleBuffer(3); - Isolate* i = reinterpret_cast(0x1); + Dart_Port i = 123; EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); Sample* s; s = sample_buffer->ReserveSample(); @@ -124,7 +124,7 @@ TEST_CASE(Profiler_SampleBufferWrapTest) { TEST_CASE(Profiler_SampleBufferIterateTest) { SampleBuffer* sample_buffer = new SampleBuffer(3); - Isolate* i = reinterpret_cast(0x1); + Dart_Port i = 123; EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); Sample* s; s = sample_buffer->ReserveSample(); @@ -147,7 +147,7 @@ TEST_CASE(Profiler_AllocationSampleTest) { Isolate* isolate = Isolate::Current(); SampleBuffer* sample_buffer = new SampleBuffer(3); Sample* sample = sample_buffer->ReserveSample(); - sample->Init(isolate, 0, 0); + sample->Init(isolate->main_port(), 0, 0); sample->set_metadata(99); sample->set_is_allocation_sample(true); EXPECT_EQ(99, sample->allocation_cid()); @@ -173,11 +173,11 @@ static RawFunction* GetFunction(const Library& lib, const char* name) { class AllocationFilter : public SampleFilter { public: - AllocationFilter(Isolate* isolate, + AllocationFilter(Dart_Port port, intptr_t cid, int64_t time_origin_micros = -1, int64_t time_extent_micros = -1) - : SampleFilter(isolate, + : SampleFilter(port, Thread::kMutatorTask, time_origin_micros, time_extent_micros), @@ -239,7 +239,8 @@ TEST_CASE(Profiler_TrivialRecordAllocation) { HANDLESCOPE(thread); Profile profile(isolate); // Filter for the class in the time range. - AllocationFilter filter(isolate, class_a.id(), before_allocations_micros, + AllocationFilter filter(isolate->main_port(), class_a.id(), + before_allocations_micros, allocation_extent_micros); profile.Build(thread, &filter, Profile::kNoTags); // We should have 1 allocation sample. @@ -306,8 +307,8 @@ TEST_CASE(Profiler_TrivialRecordAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id(), Dart_TimelineGetMicros(), - 16000); + AllocationFilter filter(isolate->main_port(), class_a.id(), + Dart_TimelineGetMicros(), 16000); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples because none occured within // the specified time range. @@ -350,7 +351,7 @@ TEST_CASE(Profiler_ToggleRecordAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -368,7 +369,7 @@ TEST_CASE(Profiler_ToggleRecordAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -439,7 +440,7 @@ TEST_CASE(Profiler_ToggleRecordAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -480,7 +481,7 @@ TEST_CASE(Profiler_CodeTicks) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -503,7 +504,7 @@ TEST_CASE(Profiler_CodeTicks) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have three allocation samples. EXPECT_EQ(3, profile.sample_count()); @@ -583,7 +584,7 @@ TEST_CASE(Profiler_FunctionTicks) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -606,7 +607,7 @@ TEST_CASE(Profiler_FunctionTicks) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have three allocation samples. EXPECT_EQ(3, profile.sample_count()); @@ -677,7 +678,7 @@ TEST_CASE(Profiler_IntrinsicAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, double_class.id()); + AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -691,7 +692,7 @@ TEST_CASE(Profiler_IntrinsicAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, double_class.id()); + AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -717,7 +718,7 @@ TEST_CASE(Profiler_IntrinsicAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, double_class.id()); + AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -747,7 +748,7 @@ TEST_CASE(Profiler_ArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, array_class.id()); + AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -761,7 +762,7 @@ TEST_CASE(Profiler_ArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, array_class.id()); + AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -789,7 +790,7 @@ TEST_CASE(Profiler_ArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, array_class.id()); + AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -813,7 +814,7 @@ TEST_CASE(Profiler_ArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, array_class.id()); + AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -861,7 +862,7 @@ TEST_CASE(Profiler_ContextAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, context_class.id()); + AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -875,7 +876,7 @@ TEST_CASE(Profiler_ContextAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, context_class.id()); + AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -899,7 +900,7 @@ TEST_CASE(Profiler_ContextAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, context_class.id()); + AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -942,7 +943,7 @@ TEST_CASE(Profiler_ClosureAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, closure_class.id()); + AllocationFilter filter(isolate->main_port(), closure_class.id()); filter.set_enable_vm_ticks(true); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. @@ -970,7 +971,7 @@ TEST_CASE(Profiler_ClosureAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, closure_class.id()); + AllocationFilter filter(isolate->main_port(), closure_class.id()); filter.set_enable_vm_ticks(true); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. @@ -1004,7 +1005,7 @@ TEST_CASE(Profiler_TypedArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, float32_list_class.id()); + AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -1018,7 +1019,7 @@ TEST_CASE(Profiler_TypedArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, float32_list_class.id()); + AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1042,7 +1043,7 @@ TEST_CASE(Profiler_TypedArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, float32_list_class.id()); + AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1056,7 +1057,7 @@ TEST_CASE(Profiler_TypedArrayAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, float32_list_class.id()); + AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); @@ -1088,7 +1089,7 @@ TEST_CASE(Profiler_StringAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -1102,7 +1103,7 @@ TEST_CASE(Profiler_StringAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1128,7 +1129,7 @@ TEST_CASE(Profiler_StringAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1142,7 +1143,7 @@ TEST_CASE(Profiler_StringAllocation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); @@ -1175,7 +1176,7 @@ TEST_CASE(Profiler_StringInterpolation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -1189,7 +1190,7 @@ TEST_CASE(Profiler_StringInterpolation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1217,7 +1218,7 @@ TEST_CASE(Profiler_StringInterpolation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1231,7 +1232,7 @@ TEST_CASE(Profiler_StringInterpolation) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, one_byte_string_class.id()); + AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); @@ -1291,7 +1292,7 @@ TEST_CASE(Profiler_FunctionInline) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -1310,7 +1311,7 @@ TEST_CASE(Profiler_FunctionInline) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have 50,000 allocation samples. EXPECT_EQ(50000, profile.sample_count()); @@ -1428,7 +1429,7 @@ TEST_CASE(Profiler_FunctionInline) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags, ProfilerService::kCodeTransitionTagsBit); // We should have 50,000 allocation samples. @@ -1601,7 +1602,7 @@ TEST_CASE(Profiler_InliningIntervalBoundry) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); @@ -1619,7 +1620,7 @@ TEST_CASE(Profiler_InliningIntervalBoundry) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); EXPECT_EQ(1, profile.sample_count()); ProfileTrieWalker walker(&profile); @@ -1718,7 +1719,7 @@ TEST_CASE(Profiler_ChainedSamples) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have 1 allocation sample. EXPECT_EQ(1, profile.sample_count()); @@ -1820,7 +1821,7 @@ TEST_CASE(Profiler_BasicSourcePosition) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -1914,7 +1915,7 @@ TEST_CASE(Profiler_BasicSourcePositionOptimized) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -2001,7 +2002,7 @@ TEST_CASE(Profiler_SourcePosition) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -2126,7 +2127,7 @@ TEST_CASE(Profiler_SourcePositionOptimized) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -2234,7 +2235,7 @@ TEST_CASE(Profiler_BinaryOperatorSourcePosition) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -2368,7 +2369,7 @@ TEST_CASE(Profiler_BinaryOperatorSourcePositionOptimized) { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); - AllocationFilter filter(isolate, class_a.id()); + AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profile::kNoTags); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); @@ -2427,7 +2428,7 @@ static void InsertFakeSample(SampleBuffer* sample_buffer, uword* pc_offsets) { Isolate* isolate = Isolate::Current(); Sample* sample = sample_buffer->ReserveSample(); ASSERT(sample != NULL); - sample->Init(isolate, OS::GetCurrentMonotonicMicros(), + sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), OSThread::Current()->trace_id()); sample->set_thread_task(Thread::kMutatorTask); diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 2e96b66bf84..6b8afb7ad89 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -3235,6 +3235,28 @@ static bool GetAllocationSamples(Thread* thread, JSONStream* js) { } +static const MethodParameter* get_native_allocation_samples_params[] = { + NO_ISOLATE_PARAMETER, + new EnumParameter("tags", true, tags_enum_names), + new Int64Parameter("timeOriginMicros", false), + new Int64Parameter("timeExtentMicros", false), + NULL, +}; + + +static bool GetNativeAllocationSamples(Thread* thread, JSONStream* js) { + Profile::TagOrder tag_order = + EnumMapper(js->LookupParam("tags"), tags_enum_names, tags_enum_values); + int64_t time_origin_micros = + Int64Parameter::Parse(js->LookupParam("timeOriginMicros")); + int64_t time_extent_micros = + Int64Parameter::Parse(js->LookupParam("timeExtentMicros")); + ProfilerService::PrintNativeAllocationJSON(js, tag_order, time_origin_micros, + time_extent_micros); + return true; +} + + static const MethodParameter* clear_cpu_profile_params[] = { RUNNABLE_ISOLATE_PARAMETER, NULL, }; @@ -4057,6 +4079,8 @@ static const ServiceMethodDescriptor service_methods_[] = { get_allocation_profile_params }, { "_getAllocationSamples", GetAllocationSamples, get_allocation_samples_params }, + { "_getNativeAllocationSamples", GetNativeAllocationSamples, + get_native_allocation_samples_params }, { "getClassList", GetClassList, get_class_list_params }, { "_getCpuProfile", GetCpuProfile, diff --git a/runtime/vm/snapshot_test.cc b/runtime/vm/snapshot_test.cc index a910b545897..41953ecee96 100644 --- a/runtime/vm/snapshot_test.cc +++ b/runtime/vm/snapshot_test.cc @@ -12,6 +12,7 @@ #include "vm/dart_api_message.h" #include "vm/dart_api_state.h" #include "vm/flags.h" +#include "vm/malloc_hooks.h" #include "vm/snapshot.h" #include "vm/symbols.h" #include "vm/unicode.h" @@ -1122,6 +1123,11 @@ static void IterateScripts(const Library& lib) { } ISOLATE_UNIT_TEST_CASE(GenerateSource) { + // Disable stack trace collection for this test as it results in a timeout. + bool stack_trace_collection_enabled = + MallocHooks::stack_trace_collection_enabled(); + MallocHooks::set_stack_trace_collection_enabled(false); + Zone* zone = thread->zone(); Isolate* isolate = thread->isolate(); const GrowableObjectArray& libs = @@ -1135,6 +1141,9 @@ ISOLATE_UNIT_TEST_CASE(GenerateSource) { OS::Print("Generating source for library: %s\n", uri.ToCString()); IterateScripts(lib); } + + MallocHooks::set_stack_trace_collection_enabled( + stack_trace_collection_enabled); } diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc index e156203e503..1508ae7347a 100644 --- a/runtime/vm/source_report.cc +++ b/runtime/vm/source_report.cc @@ -56,8 +56,8 @@ void SourceReport::Init(Thread* thread, ClearScriptTable(); if (IsReportRequested(kProfile)) { // Build the profile. - SampleFilter samplesForIsolate(thread_->isolate(), Thread::kMutatorTask, -1, - -1); + SampleFilter samplesForIsolate(thread_->isolate()->main_port(), + Thread::kMutatorTask, -1, -1); profile_.Build(thread, &samplesForIsolate, Profile::kNoTags); } } diff --git a/tests/language/disassemble_test.dart b/tests/language/disassemble_test.dart index cbea9036c9e..ae42e8f9947 100644 --- a/tests/language/disassemble_test.dart +++ b/tests/language/disassemble_test.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. // Dart test program for testing isolate communication with // typed objects. -// VMOptions=--disassemble --no-background-compilation -// VMOptions=--disassemble --print-variable-descriptors --no-background-compilation +// VMOptions=--disassemble --no-background-compilation --enable-malloc-hooks=false +// VMOptions=--disassemble --print-variable-descriptors --no-background-compilation --enable-malloc-hooks=false // Tests proper object recognition in disassembler.