// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/assert.h" #include "vm/dart_api_impl.h" #include "vm/dart_api_state.h" #include "vm/globals.h" #include "vm/profiler.h" #include "vm/profiler_service.h" #include "vm/source_report.h" #include "vm/symbols.h" #include "vm/unit_test.h" namespace dart { #ifndef PRODUCT DECLARE_FLAG(bool, profile_vm); DECLARE_FLAG(bool, profile_vm_allocation); DECLARE_FLAG(int, max_profile_depth); DECLARE_FLAG(int, optimization_counter_threshold); // Some tests are written assuming native stack trace profiling is disabled. class DisableNativeProfileScope : public ValueObject { public: DisableNativeProfileScope() : FLAG_profile_vm_(FLAG_profile_vm), FLAG_profile_vm_allocation_(FLAG_profile_vm_allocation) { FLAG_profile_vm = false; FLAG_profile_vm_allocation = false; } ~DisableNativeProfileScope() { FLAG_profile_vm = FLAG_profile_vm_; FLAG_profile_vm_allocation = FLAG_profile_vm_allocation_; } private: const bool FLAG_profile_vm_; const bool FLAG_profile_vm_allocation_; }; class DisableBackgroundCompilationScope : public ValueObject { public: DisableBackgroundCompilationScope() : FLAG_background_compilation_(FLAG_background_compilation) { FLAG_background_compilation = false; } ~DisableBackgroundCompilationScope() { FLAG_background_compilation = FLAG_background_compilation_; } private: const bool FLAG_background_compilation_; }; // Temporarily adjust the maximum profile depth. class MaxProfileDepthScope : public ValueObject { public: explicit MaxProfileDepthScope(intptr_t new_max_depth) : FLAG_max_profile_depth_(FLAG_max_profile_depth) { Profiler::SetSampleDepth(new_max_depth); } ~MaxProfileDepthScope() { Profiler::SetSampleDepth(FLAG_max_profile_depth_); } private: const intptr_t FLAG_max_profile_depth_; }; class ProfileSampleBufferTestHelper { public: 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->port() != port) { continue; } c++; } return c; } 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->port() != port) { continue; } c += sample->At(0); } return c; } }; TEST_CASE(Profiler_SampleBufferWrapTest) { SampleBuffer* sample_buffer = new SampleBuffer(3); Dart_Port i = 123; EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); Sample* s; s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); s->SetAt(0, 2); EXPECT_EQ(2, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); s->SetAt(0, 4); EXPECT_EQ(6, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); s->SetAt(0, 6); EXPECT_EQ(12, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); s->SetAt(0, 8); EXPECT_EQ(18, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer)); delete sample_buffer; } TEST_CASE(Profiler_SampleBufferIterateTest) { SampleBuffer* sample_buffer = new SampleBuffer(3); Dart_Port i = 123; EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); Sample* s; s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); EXPECT_EQ(1, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); EXPECT_EQ(2, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); EXPECT_EQ(3, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); s = sample_buffer->ReserveSample(); s->Init(i, 0, 0); EXPECT_EQ(3, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer)); delete sample_buffer; } TEST_CASE(Profiler_AllocationSampleTest) { Isolate* isolate = Isolate::Current(); SampleBuffer* sample_buffer = new SampleBuffer(3); Sample* sample = sample_buffer->ReserveSample(); sample->Init(isolate->main_port(), 0, 0); sample->set_metadata(99); sample->set_is_allocation_sample(true); EXPECT_EQ(99, sample->allocation_cid()); delete sample_buffer; } static LibraryPtr LoadTestScript(const char* script) { Dart_Handle api_lib; { TransitionVMToNative transition(Thread::Current()); api_lib = TestCase::LoadTestScript(script, NULL); EXPECT_VALID(api_lib); } Library& lib = Library::Handle(); lib ^= Api::UnwrapHandle(api_lib); return lib.ptr(); } static ClassPtr GetClass(const Library& lib, const char* name) { Thread* thread = Thread::Current(); const Class& cls = Class::Handle( lib.LookupClassAllowPrivate(String::Handle(Symbols::New(thread, name)))); EXPECT(!cls.IsNull()); // No ambiguity error expected. return cls.ptr(); } static FunctionPtr GetFunction(const Library& lib, const char* name) { Thread* thread = Thread::Current(); const Function& func = Function::Handle(lib.LookupFunctionAllowPrivate( String::Handle(Symbols::New(thread, name)))); EXPECT(!func.IsNull()); // No ambiguity error expected. return func.ptr(); } static void Invoke(const Library& lib, const char* name, intptr_t argc = 0, Dart_Handle* argv = NULL) { Thread* thread = Thread::Current(); Dart_Handle api_lib = Api::NewHandle(thread, lib.ptr()); TransitionVMToNative transition(thread); Dart_Handle result = Dart_Invoke(api_lib, NewString(name), argc, argv); EXPECT_VALID(result); } class AllocationFilter : public SampleFilter { public: AllocationFilter(Dart_Port port, intptr_t cid, int64_t time_origin_micros = -1, int64_t time_extent_micros = -1) : SampleFilter(port, Thread::kMutatorTask, time_origin_micros, time_extent_micros), cid_(cid), enable_vm_ticks_(false) {} bool FilterSample(Sample* sample) { if (!enable_vm_ticks_ && (sample->vm_tag() == VMTag::kVMTagId)) { // We don't want to see embedder ticks in the test. return false; } return sample->is_allocation_sample() && (sample->allocation_cid() == cid_); } void set_enable_vm_ticks(bool enable) { enable_vm_ticks_ = enable; } private: intptr_t cid_; bool enable_vm_ticks_; }; static void EnableProfiler() { if (!FLAG_profiler) { FLAG_profiler = true; Profiler::Init(); } } class ProfileStackWalker { public: explicit ProfileStackWalker(Profile* profile, bool as_func = false) : profile_(profile), as_functions_(as_func), index_(0), sample_(profile->SampleAt(0)) { ClearInliningData(); } bool Down() { if (as_functions_) { return UpdateFunctionIndex(); } else { ++index_; return (index_ < sample_->length()); } } const char* CurrentName() { if (as_functions_) { ProfileFunction* func = GetFunction(); EXPECT(func != NULL); return func->Name(); } else { ProfileCode* code = GetCode(); EXPECT(code != NULL); return code->name(); } } const char* CurrentToken() { if (!as_functions_) { return nullptr; } ProfileFunction* func = GetFunction(); const Function& function = *(func->function()); if (function.IsNull()) { // No function. return nullptr; } Zone* zone = Thread::Current()->zone(); const Script& script = Script::Handle(zone, function.script()); if (script.IsNull()) { // No script. return nullptr; } ProfileFunctionSourcePosition pfsp(TokenPosition::kNoSource); if (!func->GetSinglePosition(&pfsp)) { // Not exactly one source position. return nullptr; } const TokenPosition& token_pos = pfsp.token_pos(); intptr_t line, column; if (script.GetTokenLocation(token_pos, &line, &column)) { const intptr_t token_len = script.GetTokenLength(token_pos); const auto& str = String::Handle( zone, script.GetSnippet(line, column, line, column + token_len)); if (!str.IsNull()) return str.ToCString(); } // Couldn't get line/number information. return nullptr; } intptr_t CurrentInclusiveTicks() { if (as_functions_) { ProfileFunction* func = GetFunction(); EXPECT(func != NULL); return func->inclusive_ticks(); } else { ProfileCode* code = GetCode(); ASSERT(code != NULL); return code->inclusive_ticks(); } } intptr_t CurrentExclusiveTicks() { if (as_functions_) { ProfileFunction* func = GetFunction(); EXPECT(func != NULL); return func->exclusive_ticks(); } else { ProfileCode* code = GetCode(); ASSERT(code != NULL); return code->exclusive_ticks(); } } const char* VMTagName() { return VMTag::TagName(sample_->vm_tag()); } private: ProfileCode* GetCode() { uword pc = sample_->At(index_); int64_t timestamp = sample_->timestamp(); return profile_->GetCodeFromPC(pc, timestamp); } static const intptr_t kInvalidInlinedIndex = -1; bool UpdateFunctionIndex() { if (inlined_index_ != kInvalidInlinedIndex) { if (inlined_index_ - 1 >= 0) { --inlined_index_; return true; } ClearInliningData(); } ++index_; return (index_ < sample_->length()); } void ClearInliningData() { inlined_index_ = kInvalidInlinedIndex; inlined_functions_ = NULL; inlined_token_positions_ = NULL; } ProfileFunction* GetFunction() { // Check to see if we're currently processing inlined functions. If so, // return the next inlined function. ProfileFunction* function = GetInlinedFunction(); if (function != NULL) { return function; } const uword pc = sample_->At(index_); ProfileCode* profile_code = profile_->GetCodeFromPC(pc, sample_->timestamp()); ASSERT(profile_code != NULL); function = profile_code->function(); ASSERT(function != NULL); TokenPosition token_position = TokenPosition::kNoSource; Code& code = Code::ZoneHandle(); if (profile_code->code().IsCode()) { code ^= profile_code->code().ptr(); inlined_functions_cache_.Get(pc, code, sample_, index_, &inlined_functions_, &inlined_token_positions_, &token_position); } if (code.IsNull() || (inlined_functions_ == NULL) || (inlined_functions_->length() <= 1)) { ClearInliningData(); // No inlined functions. return function; } ASSERT(code.is_optimized()); inlined_index_ = inlined_functions_->length() - 1; function = GetInlinedFunction(); ASSERT(function != NULL); return function; } ProfileFunction* GetInlinedFunction() { if ((inlined_index_ != kInvalidInlinedIndex) && (inlined_index_ < inlined_functions_->length())) { return profile_->FindFunction(*(*inlined_functions_)[inlined_index_]); } return NULL; } Profile* profile_; bool as_functions_; intptr_t index_; ProcessedSample* sample_; ProfileCodeInlinedFunctionsCache inlined_functions_cache_; GrowableArray* inlined_functions_; GrowableArray* inlined_token_positions_; intptr_t inlined_index_; }; ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " return B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const int64_t before_allocations_micros = Dart_TimelineGetMicros(); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); class_a.SetTraceAllocation(true); Invoke(root_library, "main"); const int64_t after_allocations_micros = Dart_TimelineGetMicros(); const int64_t allocation_extent_micros = after_allocations_micros - before_allocations_micros; { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); // Filter for the class in the time range. AllocationFilter filter(isolate->main_port(), class_a.id(), before_allocations_micros, allocation_extent_micros); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have 1 allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); // Move down from the root. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); EXPECT(!walker.Down()); } // Query with a time filter where no allocations occurred. { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id(), Dart_TimelineGetMicros(), 16000); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples because none occured within // the specified time range. EXPECT_EQ(0, profile.sample_count()); } } #if defined(DART_USE_TCMALLOC) && defined(HOST_OS_LINUX) && defined(DEBUG) && \ defined(HOST_ARCH_X64) DART_NOINLINE static void NativeAllocationSampleHelper(char** result) { ASSERT(result != NULL); *result = static_cast(malloc(sizeof(char) * 1024)); } ISOLATE_UNIT_TEST_CASE(Profiler_NativeAllocation) { bool enable_malloc_hooks_saved = FLAG_profiler_native_memory; FLAG_profiler_native_memory = true; EnableProfiler(); MallocHooks::Init(); MallocHooks::ResetStats(); bool stack_trace_collection_enabled = MallocHooks::stack_trace_collection_enabled(); MallocHooks::set_stack_trace_collection_enabled(true); char* result = NULL; const int64_t before_allocations_micros = Dart_TimelineGetMicros(); NativeAllocationSampleHelper(&result); // Disable stack allocation stack trace collection to avoid muddying up // results. MallocHooks::set_stack_trace_collection_enabled(false); const int64_t after_allocations_micros = Dart_TimelineGetMicros(); const int64_t allocation_extent_micros = after_allocations_micros - before_allocations_micros; // Walk the trie and do a sanity check of the allocation values associated // with each node. { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); // Filter for the class in the time range. NativeAllocationSampleFilter filter(before_allocations_micros, allocation_extent_micros); profile.Build(thread, &filter, Profiler::allocation_sample_buffer()); // We should have 1 allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); // Move down from the root. EXPECT_SUBSTRING("[Native]", walker.CurrentName()); EXPECT_EQ(1024ul, profile.SampleAt(0)->native_allocation_size_bytes()); EXPECT(walker.Down()); EXPECT_STREQ("dart::Dart_TestProfiler_NativeAllocation()", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("dart::TestCase::Run()", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("dart::TestCaseBase::RunTest()", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("dart::TestCaseBase::RunAll()", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_SUBSTRING("[Native]", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT(!walker.Down()); } MallocHooks::set_stack_trace_collection_enabled(true); free(result); MallocHooks::set_stack_trace_collection_enabled(false); // Check to see that the native allocation sample associated with the memory // freed above is marked as free and is no longer reported. { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); // Filter for the class in the time range. NativeAllocationSampleFilter filter(before_allocations_micros, allocation_extent_micros); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have 0 allocation samples since we freed the memory. EXPECT_EQ(0, profile.sample_count()); } // Query with a time filter where no allocations occurred. { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); NativeAllocationSampleFilter filter(Dart_TimelineGetMicros(), 16000); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples because none occured within // the specified time range. EXPECT_EQ(0, profile.sample_count()); } MallocHooks::set_stack_trace_collection_enabled( stack_trace_collection_enabled); FLAG_profiler_native_memory = enable_malloc_hooks_saved; } #endif // defined(DART_USE_TCMALLOC) && defined(HOST_OS_LINUX) && \ // defined(DEBUG) && defined(HOST_ARCH_X64) ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " return B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); EXPECT(!walker.Down()); } // Turn off allocation tracing for A. class_a.SetTraceAllocation(false); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " return B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate three times. Invoke(root_library, "main"); Invoke(root_library, "main"); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have three allocation samples. EXPECT_EQ(3, profile.sample_count()); ProfileStackWalker walker(&profile); // Move down from the root. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(3, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " return B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate three times. Invoke(root_library, "main"); Invoke(root_library, "main"); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have three allocation samples. EXPECT_EQ(3, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(3, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(3, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "double foo(double a, double b) => a + b;"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& double_class = Class::Handle(isolate->group()->object_store()->double_class()); EXPECT(!double_class.IsNull()); Dart_Handle args[2]; { TransitionVMToNative transition(thread); args[0] = Dart_NewDouble(1.0); args[1] = Dart_NewDouble(2.0); } Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } double_class.SetTraceAllocation(true); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("Double_add", walker.VMTagName()); EXPECT_STREQ("[Unoptimized] double._add", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] double.+", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } double_class.SetTraceAllocation(false); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), double_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "List foo() => List.filled(4, null);\n" "List bar() => List.filled(0, null, growable: true);\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& array_class = Class::Handle(isolate->group()->object_store()->array_class()); EXPECT(!array_class.IsNull()); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } array_class.SetTraceAllocation(true); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateArray", walker.VMTagName()); EXPECT_STREQ("[Stub] AllocateArray", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] new _List", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } array_class.SetTraceAllocation(false); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } // Clear the samples. ProfilerService::ClearSamples(); // Compile bar (many List objects allocated). Invoke(root_library, "bar"); // Enable again. array_class.SetTraceAllocation(true); // Run bar. Invoke(root_library, "bar"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), array_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples, since empty // growable lists use a shared backing. EXPECT_EQ(0, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "var msg1 = 'a';\n" "foo() {\n" " var msg = msg1 + msg1;\n" " return (x) { return '$msg + $msg'; }(msg);\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& context_class = Class::Handle(Object::context_class()); EXPECT(!context_class.IsNull()); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } context_class.SetTraceAllocation(true); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateContext", walker.VMTagName()); EXPECT_STREQ("[Stub] AllocateContext", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } context_class.SetTraceAllocation(false); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), context_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "var msg1 = 'a';\n" "\n" "foo() {\n" " var msg = msg1 + msg1;\n" " var msg2 = msg + msg;\n" " return (x, y, z, w) { return '$x + $y + $z'; }(msg, msg2, msg, msg);\n" "}\n" "bar() {\n" " var msg = msg1 + msg1;\n" " var msg2 = msg + msg;\n" " return (x, y) { return '$x + $y'; }(msg, msg2);\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& closure_class = Class::Handle(IsolateGroup::Current()->object_store()->closure_class()); EXPECT(!closure_class.IsNull()); closure_class.SetTraceAllocation(true); // Invoke "foo" which during compilation, triggers a closure allocation. Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), closure_class.id()); filter.set_enable_vm_ticks(true); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_SUBSTRING("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate _Closure", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT(walker.Down()); EXPECT_SUBSTRING("foo", walker.CurrentName()); EXPECT(!walker.Down()); } // Disable allocation tracing for Closure. closure_class.SetTraceAllocation(false); // Invoke "bar" which during compilation, triggers a closure allocation. Invoke(root_library, "bar"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), closure_class.id()); filter.set_enable_vm_ticks(true); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "import 'dart:typed_data';\n" "List foo() => new Float32List(4);\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Library& typed_data_library = Library::Handle(isolate->group()->object_store()->typed_data_library()); const Class& float32_list_class = Class::Handle(GetClass(typed_data_library, "_Float32List")); EXPECT(!float32_list_class.IsNull()); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } float32_list_class.SetTraceAllocation(true); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateTypedData", walker.VMTagName()); EXPECT_STREQ("[Stub] AllocateFloat32Array", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] new Float32List", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } float32_list_class.SetTraceAllocation(false); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } float32_list_class.SetTraceAllocation(true); Invoke(root_library, "foo"); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), float32_list_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "String foo(String a, String b) => a + b;"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& one_byte_string_class = Class::Handle(isolate->group()->object_store()->one_byte_string_class()); EXPECT(!one_byte_string_class.IsNull()); Dart_Handle args[2]; { TransitionVMToNative transition(thread); args[0] = NewString("a"); args[1] = NewString("b"); } Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } one_byte_string_class.SetTraceAllocation(true); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("String_concat", walker.VMTagName()); EXPECT_STREQ("[Unoptimized] _StringBase.+", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } one_byte_string_class.SetTraceAllocation(false); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } one_byte_string_class.SetTraceAllocation(true); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "String foo(String a, String b) => '$a | $b';"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); Isolate* isolate = thread->isolate(); const Class& one_byte_string_class = Class::Handle(isolate->group()->object_store()->one_byte_string_class()); EXPECT(!one_byte_string_class.IsNull()); Dart_Handle args[2]; { TransitionVMToNative transition(thread); args[0] = NewString("a"); args[1] = NewString("b"); } Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } one_byte_string_class.SetTraceAllocation(true); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("Internal_allocateOneByteString", walker.VMTagName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] String._allocate", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] String._concatAll", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] _StringBase._interpolate", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] foo", walker.CurrentName()); EXPECT(!walker.Down()); } one_byte_string_class.SetTraceAllocation(false); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should still only have one allocation sample. EXPECT_EQ(1, profile.sample_count()); } one_byte_string_class.SetTraceAllocation(true); Invoke(root_library, "foo", 2, &args[0]); { StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should now have two allocation samples. EXPECT_EQ(2, profile.sample_count()); } } ISOLATE_UNIT_TEST_CASE(Profiler_FunctionInline) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; SetFlagScope sfs(&FLAG_optimization_counter_threshold, 30000); const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static choo(bool alloc) {\n" " if (alloc) return new A();\n" " return alloc && alloc && !alloc;\n" " }\n" " static foo(bool alloc) {\n" " choo(alloc);\n" " }\n" " static boo(bool alloc) {\n" " for (var i = 0; i < 50000; i++) {\n" " foo(alloc);\n" " }\n" " }\n" "}\n" "main() {\n" " B.boo(false);\n" "}\n" "mainA() {\n" " B.boo(true);\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); // Compile "main". Invoke(root_library, "main"); // Compile "mainA". Invoke(root_library, "mainA"); // At this point B.boo should be optimized and inlined B.foo and B.choo. { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate 50,000 instances of A. Invoke(root_library, "mainA"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have 50,000 allocation samples. EXPECT_EQ(50000, profile.sample_count()); { ProfileStackWalker walker(&profile); // We have two code objects: mainA and B.boo. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(50000, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(!walker.Down()); } { ProfileStackWalker walker(&profile, true); // Inline expansion should show us the complete call chain: // mainA -> B.boo -> B.foo -> B.choo. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(50000, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.choo", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.foo", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("mainA", walker.CurrentName()); EXPECT_EQ(50000, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT(!walker.Down()); } } } ISOLATE_UNIT_TEST_CASE(Profiler_InliningIntervalBoundry) { // The PC of frames below the top frame is a call's return address, // which can belong to a different inlining interval than the call. // This test checks the profiler service takes this into account; see // ProfileBuilder::ProcessFrame. EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; SetFlagScope sfs(&FLAG_optimization_counter_threshold, 30000); const char* kScript = "class A {\n" "}\n" "bool alloc = false;" "maybeAlloc() {\n" " try {\n" " if (alloc) new A();\n" " } catch (e) {\n" " }\n" "}\n" "right() => maybeAlloc();\n" "doNothing() {\n" " try {\n" " } catch (e) {\n" " }\n" "}\n" "wrong() => doNothing();\n" "a() {\n" " try {\n" " right();\n" " wrong();\n" " } catch (e) {\n" " }\n" "}\n" "mainNoAlloc() {\n" " for (var i = 0; i < 20000; i++) {\n" " a();\n" " }\n" "}\n" "mainAlloc() {\n" " alloc = true;\n" " a();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); // Compile and optimize. Invoke(root_library, "mainNoAlloc"); Invoke(root_library, "mainAlloc"); // At this point a should be optimized and have inlined both right and wrong, // but not maybeAllocate or doNothing. Function& func = Function::Handle(); func = GetFunction(root_library, "a"); EXPECT(!func.is_inlinable()); EXPECT(func.HasOptimizedCode()); func = GetFunction(root_library, "right"); EXPECT(func.is_inlinable()); func = GetFunction(root_library, "wrong"); EXPECT(func.is_inlinable()); func = GetFunction(root_library, "doNothing"); EXPECT(!func.is_inlinable()); func = GetFunction(root_library, "maybeAlloc"); EXPECT(!func.is_inlinable()); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have no allocation samples. EXPECT_EQ(0, profile.sample_count()); } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); Invoke(root_library, "mainAlloc"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); // Inline expansion should show us the complete call chain: EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT(walker.Down()); EXPECT_STREQ("maybeAlloc", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("right", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("a", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("mainAlloc", walker.CurrentName()); } } ISOLATE_UNIT_TEST_CASE(Profiler_ChainedSamples) { EnableProfiler(); MaxProfileDepthScope mpds(32); DisableNativeProfileScope dnps; // Each sample holds 8 stack frames. // This chain is 20 stack frames deep. const char* kScript = "class A {\n" " var a;\n" " var b;\n" "}\n" "class B {\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "go() => init();\n" "init() => secondInit();\n" "secondInit() => apple();\n" "apple() => banana();\n" "banana() => cantaloupe();\n" "cantaloupe() => dog();\n" "dog() => elephant();\n" "elephant() => fred();\n" "fred() => granola();\n" "granola() => haystack();\n" "haystack() => ice();\n" "ice() => jeep();\n" "jeep() => kindle();\n" "kindle() => lemon();\n" "lemon() => mayo();\n" "mayo() => napkin();\n" "napkin() => orange();\n" "orange() => B.boo();\n" "main() {\n" " return go();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); class_a.SetTraceAllocation(true); Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have 1 allocation sample. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] orange", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] napkin", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] mayo", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] lemon", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] kindle", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] jeep", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] ice", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] haystack", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] granola", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] fred", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] elephant", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] dog", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] cantaloupe", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] banana", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] apple", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] secondInit", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] init", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] go", walker.CurrentName()); EXPECT(walker.Down()); EXPECT_STREQ("[Unoptimized] main", walker.CurrentName()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePosition) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePositionOptimized) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; // Optimize quickly. SetFlagScope sfs(&FLAG_optimization_counter_threshold, 5); const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "main() {\n" " B.boo();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); const Function& main = Function::Handle(GetFunction(root_library, "main")); EXPECT(!main.IsNull()); // Warm up function. while (true) { Invoke(root_library, "main"); const Code& code = Code::Handle(main.CurrentCode()); if (code.is_optimized()) { // Warmed up. break; } } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); // Still optimized. const Code& code = Code::Handle(main.CurrentCode()); EXPECT(code.is_optimized()); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); // Move down from the root. EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_SourcePosition) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:never-inline')\n" " static oats() {\n" " return boo();\n" " }\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "class C {\n" " @pragma('vm:never-inline') bacon() {\n" " return fox();\n" " }\n" " @pragma('vm:prefer-inline') fox() {\n" " return B.oats();\n" " }\n" "}\n" "main() {\n" " new C()..bacon();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("B.oats", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.fox", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("oats", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.bacon", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("fox", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("bacon", walker.CurrentToken()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_SourcePositionOptimized) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; // Optimize quickly. SetFlagScope sfs(&FLAG_optimization_counter_threshold, 5); const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:never-inline')\n" " static oats() {\n" " return boo();\n" " }\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "class C {\n" " @pragma('vm:never-inline') bacon() {\n" " return fox();\n" " }\n" " @pragma('vm:prefer-inline') fox() {\n" " return B.oats();\n" " }\n" "}\n" "main() {\n" " new C()..bacon();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); const Function& main = Function::Handle(GetFunction(root_library, "main")); EXPECT(!main.IsNull()); // Warm up function. while (true) { Invoke(root_library, "main"); const Code& code = Code::Handle(main.CurrentCode()); if (code.is_optimized()) { // Warmed up. break; } } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); // Still optimized. const Code& code = Code::Handle(main.CurrentCode()); EXPECT(code.is_optimized()); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("B.oats", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.fox", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("oats", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.bacon", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("fox", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("bacon", walker.CurrentToken()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePosition) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:never-inline')\n" " static oats() {\n" " return boo();\n" " }\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "class C {\n" " @pragma('vm:never-inline') bacon() {\n" " return this + this;\n" " }\n" " @pragma('vm:prefer-inline') operator+(C other) {\n" " return fox();\n" " }\n" " @pragma('vm:prefer-inline') fox() {\n" " return B.oats();\n" " }\n" "}\n" "main() {\n" " new C()..bacon();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); Invoke(root_library, "main"); // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("B.oats", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.fox", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("oats", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.+", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("fox", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.bacon", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("+", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("bacon", walker.CurrentToken()); EXPECT(!walker.Down()); } } ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePositionOptimized) { EnableProfiler(); DisableNativeProfileScope dnps; DisableBackgroundCompilationScope dbcs; // Optimize quickly. SetFlagScope sfs(&FLAG_optimization_counter_threshold, 5); const char* kScript = "class A {\n" " var a;\n" " var b;\n" " @pragma('vm:never-inline') A() { }\n" "}\n" "class B {\n" " @pragma('vm:never-inline')\n" " static oats() {\n" " return boo();\n" " }\n" " @pragma('vm:prefer-inline')\n" " static boo() {\n" " return new A();\n" " }\n" "}\n" "class C {\n" " @pragma('vm:never-inline') bacon() {\n" " return this + this;\n" " }\n" " @pragma('vm:prefer-inline') operator+(C other) {\n" " return fox();\n" " }\n" " @pragma('vm:prefer-inline') fox() {\n" " return B.oats();\n" " }\n" "}\n" "main() {\n" " new C()..bacon();\n" "}\n"; const Library& root_library = Library::Handle(LoadTestScript(kScript)); const Class& class_a = Class::Handle(GetClass(root_library, "A")); EXPECT(!class_a.IsNull()); const Function& main = Function::Handle(GetFunction(root_library, "main")); EXPECT(!main.IsNull()); // Warm up function. while (true) { Invoke(root_library, "main"); const Code& code = Code::Handle(main.CurrentCode()); if (code.is_optimized()) { // Warmed up. break; } } // Turn on allocation tracing for A. class_a.SetTraceAllocation(true); // Allocate one time. Invoke(root_library, "main"); // Still optimized. const Code& code = Code::Handle(main.CurrentCode()); EXPECT(code.is_optimized()); { Thread* thread = Thread::Current(); Isolate* isolate = thread->isolate(); StackZone zone(thread); HANDLESCOPE(thread); Profile profile(isolate); AllocationFilter filter(isolate->main_port(), class_a.id()); profile.Build(thread, &filter, Profiler::sample_buffer()); // We should have one allocation samples. EXPECT_EQ(1, profile.sample_count()); ProfileStackWalker walker(&profile, true); EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); #if defined(TARGET_ARCH_IA32) // Alloc. stub not impl. for ia32. EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName()); #else EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName()); #endif EXPECT_EQ(1, walker.CurrentExclusiveTicks()); EXPECT(walker.Down()); EXPECT_STREQ("B.boo", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_STREQ("A", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("B.oats", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("boo", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.fox", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("oats", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.+", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("fox", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("C.bacon", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("+", walker.CurrentToken()); EXPECT(walker.Down()); EXPECT_STREQ("main", walker.CurrentName()); EXPECT_EQ(1, walker.CurrentInclusiveTicks()); EXPECT_EQ(0, walker.CurrentExclusiveTicks()); EXPECT_STREQ("bacon", walker.CurrentToken()); EXPECT(!walker.Down()); } } static void InsertFakeSample(SampleBuffer* sample_buffer, uword* pc_offsets) { ASSERT(sample_buffer != NULL); Isolate* isolate = Isolate::Current(); Sample* sample = sample_buffer->ReserveSample(); ASSERT(sample != NULL); sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), OSThread::Current()->trace_id()); sample->set_thread_task(Thread::kMutatorTask); intptr_t i = 0; while (pc_offsets[i] != 0) { // When we collect a real stack trace, all PCs collected aside from the // executing one (i == 0) are actually return addresses. Return addresses // are one byte beyond the call instruction that is executing. The profiler // accounts for this and subtracts one from these addresses when querying // inline and token position ranges. To be consistent with real stack // traces, we add one byte to all PCs except the executing one. // See OffsetForPC in profiler_service.cc for more context. const intptr_t return_address_offset = i > 0 ? 1 : 0; sample->SetAt(i, pc_offsets[i] + return_address_offset); i++; } sample->SetAt(i, 0); } static uword FindPCForTokenPosition(const Code& code, TokenPosition tp) { GrowableArray functions; GrowableArray token_positions; for (uword pc_offset = 0; pc_offset < code.Size(); pc_offset++) { code.GetInlinedFunctionsAtInstruction(pc_offset, &functions, &token_positions); if (token_positions[0] == tp) { return code.PayloadStart() + pc_offset; } } return 0; } ISOLATE_UNIT_TEST_CASE(Profiler_GetSourceReport) { EnableProfiler(); const char* kScript = "int doWork(i) => i * i;\n" "int main() {\n" " int sum = 0;\n" " for (int i = 0; i < 100; i++) {\n" " sum += doWork(i);\n" " }\n" " return sum;\n" "}\n"; // Token position of * in `i * i`. const TokenPosition squarePosition = TokenPosition::Deserialize(19); // Token position of the call to `doWork`. const TokenPosition callPosition = TokenPosition::Deserialize(95); DisableNativeProfileScope dnps; // Disable profiling for this thread. DisableThreadInterruptsScope dtis(Thread::Current()); DisableBackgroundCompilationScope dbcs; SampleBuffer* sample_buffer = Profiler::sample_buffer(); EXPECT(sample_buffer != NULL); const Library& root_library = Library::Handle(LoadTestScript(kScript)); // Invoke main so that it gets compiled. Invoke(root_library, "main"); { // Clear the profile for this isolate. ClearProfileVisitor cpv(Isolate::Current()); sample_buffer->VisitSamples(&cpv); } // Query the code object for main and determine the PC at some token // positions. const Function& main = Function::Handle(GetFunction(root_library, "main")); EXPECT(!main.IsNull()); const Function& do_work = Function::Handle(GetFunction(root_library, "doWork")); EXPECT(!do_work.IsNull()); const Script& script = Script::Handle(main.script()); EXPECT(!script.IsNull()); const Code& main_code = Code::Handle(main.CurrentCode()); EXPECT(!main_code.IsNull()); const Code& do_work_code = Code::Handle(do_work.CurrentCode()); EXPECT(!do_work_code.IsNull()); // Dump code source map. do_work_code.DumpSourcePositions(); main_code.DumpSourcePositions(); // Look up some source token position's pc. uword squarePositionPc = FindPCForTokenPosition(do_work_code, squarePosition); EXPECT(squarePositionPc != 0); uword callPositionPc = FindPCForTokenPosition(main_code, callPosition); EXPECT(callPositionPc != 0); // Look up some classifying token position's pc. uword controlFlowPc = FindPCForTokenPosition(do_work_code, TokenPosition::kControlFlow); EXPECT(controlFlowPc != 0); // Insert fake samples. // Sample 1: // squarePositionPc exclusive. // callPositionPc inclusive. uword sample1[] = {squarePositionPc, // doWork. callPositionPc, // main. 0}; // Sample 2: // squarePositionPc exclusive. uword sample2[] = { squarePositionPc, // doWork. 0, }; // Sample 3: // controlFlowPc exclusive. // callPositionPc inclusive. uword sample3[] = {controlFlowPc, // doWork. callPositionPc, // main. 0}; InsertFakeSample(sample_buffer, &sample1[0]); InsertFakeSample(sample_buffer, &sample2[0]); InsertFakeSample(sample_buffer, &sample3[0]); // Generate source report for main. JSONStream js; { SourceReport sourceReport(SourceReport::kProfile); sourceReport.PrintJSON(&js, script, do_work.token_pos(), main.end_token_pos()); } // Verify positions in do_work. EXPECT_SUBSTRING("\"positions\":[\"ControlFlow\",19]", js.ToCString()); // Verify exclusive ticks in do_work. EXPECT_SUBSTRING("\"exclusiveTicks\":[1,2]", js.ToCString()); // Verify inclusive ticks in do_work. EXPECT_SUBSTRING("\"inclusiveTicks\":[1,2]", js.ToCString()); // Verify positions in main. EXPECT_SUBSTRING("\"positions\":[95]", js.ToCString()); // Verify exclusive ticks in main. EXPECT_SUBSTRING("\"exclusiveTicks\":[0]", js.ToCString()); // Verify inclusive ticks in main. EXPECT_SUBSTRING("\"inclusiveTicks\":[2]", js.ToCString()); } ISOLATE_UNIT_TEST_CASE(Profiler_ProfileCodeTableTest) { Zone* Z = Thread::Current()->zone(); ProfileCodeTable* table = new (Z) ProfileCodeTable(); EXPECT_EQ(table->length(), 0); EXPECT_EQ(table->FindCodeForPC(42), static_cast(NULL)); int64_t timestamp = 0; const AbstractCode null_code(Code::null()); ProfileCode* code1 = new (Z) ProfileCode(ProfileCode::kNativeCode, 50, 60, timestamp, null_code); EXPECT_EQ(table->InsertCode(code1), 0); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(55), code1); EXPECT_EQ(table->FindCodeForPC(59), code1); EXPECT_EQ(table->FindCodeForPC(60), static_cast(NULL)); // Insert below all. ProfileCode* code2 = new (Z) ProfileCode(ProfileCode::kNativeCode, 10, 20, timestamp, null_code); EXPECT_EQ(table->InsertCode(code2), 0); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(19), code2); EXPECT_EQ(table->FindCodeForPC(20), static_cast(NULL)); // Insert above all. ProfileCode* code3 = new (Z) ProfileCode(ProfileCode::kNativeCode, 80, 90, timestamp, null_code); EXPECT_EQ(table->InsertCode(code3), 2); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(80), code3); EXPECT_EQ(table->FindCodeForPC(89), code3); EXPECT_EQ(table->FindCodeForPC(90), static_cast(NULL)); // Insert between. ProfileCode* code4 = new (Z) ProfileCode(ProfileCode::kNativeCode, 65, 75, timestamp, null_code); EXPECT_EQ(table->InsertCode(code4), 2); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(80), code3); EXPECT_EQ(table->FindCodeForPC(65), code4); EXPECT_EQ(table->FindCodeForPC(74), code4); EXPECT_EQ(table->FindCodeForPC(75), static_cast(NULL)); // Insert overlapping left. ProfileCode* code5 = new (Z) ProfileCode(ProfileCode::kNativeCode, 15, 25, timestamp, null_code); EXPECT_EQ(table->InsertCode(code5), 0); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(80), code3); EXPECT_EQ(table->FindCodeForPC(65), code4); EXPECT_EQ(table->FindCodeForPC(15), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(24), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(25), static_cast(NULL)); // Insert overlapping right. ProfileCode* code6 = new (Z) ProfileCode(ProfileCode::kNativeCode, 45, 55, timestamp, null_code); EXPECT_EQ(table->InsertCode(code6), 1); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(80), code3); EXPECT_EQ(table->FindCodeForPC(65), code4); EXPECT_EQ(table->FindCodeForPC(15), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(24), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(45), code1); // Merged right. EXPECT_EQ(table->FindCodeForPC(54), code1); // Merged right. EXPECT_EQ(table->FindCodeForPC(55), code1); // Insert overlapping both. ProfileCode* code7 = new (Z) ProfileCode(ProfileCode::kNativeCode, 20, 50, timestamp, null_code); EXPECT_EQ(table->InsertCode(code7), 0); EXPECT_EQ(table->FindCodeForPC(0), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(100), static_cast(NULL)); EXPECT_EQ(table->FindCodeForPC(50), code1); EXPECT_EQ(table->FindCodeForPC(10), code2); EXPECT_EQ(table->FindCodeForPC(80), code3); EXPECT_EQ(table->FindCodeForPC(65), code4); EXPECT_EQ(table->FindCodeForPC(15), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(24), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(45), code1); // Merged right. EXPECT_EQ(table->FindCodeForPC(54), code1); // Merged right. EXPECT_EQ(table->FindCodeForPC(20), code2); // Merged left. EXPECT_EQ(table->FindCodeForPC(49), code1); // Truncated. EXPECT_EQ(table->FindCodeForPC(50), code1); } #endif // !PRODUCT } // namespace dart