| // 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 "platform/thread_sanitizer.h" | |
| #include "vm/dart_api_impl.h" | |
| #include "vm/dart_api_state.h" | |
| #include "vm/flags.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); | |
| static void ExpectUnoptimizedOrBytecode(dart::Expect expect, | |
| const char* expected, | |
| const char* got) { | |
| #if defined(TESTING) | |
| TextBuffer buffer(1024); | |
| if (FLAG_interpreter) { | |
| buffer.AddString("[Bytecode] "); | |
| } else { | |
| buffer.AddString("[Unoptimized] "); | |
| } | |
| buffer.Printf("%s", expected); | |
| expect.StringEquals(buffer.buffer(), got); | |
| #endif | |
| } | |
| #define EXPECT_UNOPTIMIZED_OR_BYTECODE(expected, got) \ | |
| ExpectUnoptimizedOrBytecode(dart::Expect(__FILE__, __LINE__), (expected), \ | |
| (got)) | |
| // SampleVisitor ignores samples with timestamp == 0. | |
| const int64_t kValidTimeStamp = 1; | |
| // SampleVisitor ignores samples with pc == 0. | |
| const uword kValidPc = 0xFF; | |
| NO_SANITIZE_THREAD | |
| static void SetIgnoreRace(bool* flag, bool value) { | |
| *flag = value; | |
| } | |
| // 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) { | |
| // Ignore race: we generally don't try to ensure the profiler is disabled | |
| // when changing setting for unit tests. On Linux/Android, there is no way | |
| // to wait for outstanding SIGPROFs. | |
| SetIgnoreRace(&FLAG_profile_vm, false); | |
| SetIgnoreRace(&FLAG_profile_vm_allocation, false); | |
| } | |
| ~DisableNativeProfileScope() { | |
| SetIgnoreRace(&FLAG_profile_vm, FLAG_profile_vm_); | |
| SetIgnoreRace(&FLAG_profile_vm_allocation, FLAG_profile_vm_allocation_); | |
| } | |
| private: | |
| const bool FLAG_profile_vm_; | |
| const bool FLAG_profile_vm_allocation_; | |
| }; | |
| // Temporarily adjust the maximum profile depth. | |
| class MaxProfileDepthScope : public ValueObject { | |
| public: | |
| explicit MaxProfileDepthScope(intptr_t new_max_depth) | |
| : saved_config_(Profiler::CurrentConfig()) { | |
| auto new_config = saved_config_; | |
| new_config.max_depth = new_max_depth; | |
| Profiler::SetConfig(new_config); | |
| } | |
| ~MaxProfileDepthScope() { Profiler::SetConfig(saved_config_); } | |
| private: | |
| const Profiler::Config saved_config_; | |
| }; | |
| class ProfileSampleBufferTestHelper : public SampleVisitor { | |
| public: | |
| explicit ProfileSampleBufferTestHelper(Dart_Port port) | |
| : SampleVisitor(port) {} | |
| void VisitSample(Sample* sample) { sum_ += sample->At(0); } | |
| void Reset() { | |
| sum_ = 0; | |
| SampleVisitor::Reset(); | |
| } | |
| intptr_t sum() const { return sum_; } | |
| private: | |
| intptr_t sum_ = 0; | |
| }; | |
| static void VisitSamples(SampleBlockBuffer* buffer, SampleVisitor* visitor) { | |
| visitor->Reset(); | |
| buffer->VisitSamples(visitor); | |
| } | |
| class SampleBlockBufferOverrideScope { | |
| public: | |
| explicit SampleBlockBufferOverrideScope(SampleBlockBuffer* buffer) | |
| : override_(buffer) { | |
| orig_ = Profiler::sample_block_buffer(); | |
| Profiler::set_sample_block_buffer(override_); | |
| } | |
| ~SampleBlockBufferOverrideScope() { | |
| Profiler::set_sample_block_buffer(orig_); | |
| delete override_; | |
| } | |
| private: | |
| SampleBlockBuffer* orig_; | |
| SampleBlockBuffer* override_; | |
| }; | |
| TEST_CASE(Profiler_SampleBufferWrapTest) { | |
| Isolate* isolate = Isolate::Current(); | |
| SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1)); | |
| SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer(); | |
| Dart_Port i = 123; | |
| ProfileSampleBufferTestHelper visitor(i); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(0, visitor.sum()); | |
| Sample* s; | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, 2); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(2, visitor.sum()); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, 4); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(6, visitor.sum()); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, 6); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(12, visitor.sum()); | |
| // Mark the completed blocks as free so they can be re-used. | |
| sample_buffer->FreeCompletedBlocks(); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, 8); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(18, visitor.sum()); | |
| // The overridden sample buffer will be freed before isolate shutdown. | |
| isolate->set_current_sample_block(nullptr); | |
| } | |
| TEST_CASE(Profiler_SampleBufferIterateTest) { | |
| Isolate* isolate = Isolate::Current(); | |
| SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1)); | |
| SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer(); | |
| Dart_Port i = 123; | |
| ProfileSampleBufferTestHelper visitor(i); | |
| sample_buffer->VisitSamples(&visitor); | |
| EXPECT_EQ(0, visitor.visited()); | |
| Sample* s; | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, kValidPc); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(1, visitor.visited()); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, kValidPc); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(2, visitor.visited()); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, kValidPc); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(3, visitor.visited()); | |
| s = sample_buffer->ReserveCPUSample(isolate); | |
| EXPECT_NOTNULL(s); | |
| s->Init(i, kValidTimeStamp, 0); | |
| s->SetAt(0, kValidPc); | |
| VisitSamples(sample_buffer, &visitor); | |
| EXPECT_EQ(3, visitor.visited()); | |
| // The overridden sample buffer will be freed before isolate shutdown. | |
| isolate->set_current_sample_block(nullptr); | |
| } | |
| TEST_CASE(Profiler_AllocationSampleTest) { | |
| Isolate* isolate = Isolate::Current(); | |
| SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(1, 1); | |
| Sample* sample = sample_buffer->ReserveAllocationSample(isolate); | |
| sample->Init(isolate->main_port(), 0, 0); | |
| sample->set_metadata(99); | |
| sample->set_is_allocation_sample(true); | |
| EXPECT_EQ(99, sample->allocation_cid()); | |
| isolate->set_current_allocation_sample_block(nullptr); | |
| delete sample_buffer; | |
| } | |
| static LibraryPtr LoadTestScript(const char* script) { | |
| Dart_Handle api_lib; | |
| { | |
| TransitionVMToNative transition(Thread::Current()); | |
| api_lib = TestCase::LoadTestScript(script, nullptr); | |
| 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 = nullptr) { | |
| Thread* thread = Thread::Current(); | |
| Dart_Handle api_lib = Api::NewHandle(thread, lib.ptr()); | |
| TransitionVMToNative transition(thread); | |
| { | |
| SetFlagScope<bool> sfs(&FLAG_verify_entry_points, false); | |
| 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_; | |
| }; | |
| 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 != nullptr); | |
| return func->Name(); | |
| } else { | |
| ProfileCode* code = GetCode(); | |
| EXPECT(code != nullptr); | |
| 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 != nullptr); | |
| return func->inclusive_ticks(); | |
| } else { | |
| ProfileCode* code = GetCode(); | |
| ASSERT(code != nullptr); | |
| return code->inclusive_ticks(); | |
| } | |
| } | |
| intptr_t CurrentExclusiveTicks() { | |
| if (as_functions_) { | |
| ProfileFunction* func = GetFunction(); | |
| EXPECT(func != nullptr); | |
| return func->exclusive_ticks(); | |
| } else { | |
| ProfileCode* code = GetCode(); | |
| ASSERT(code != nullptr); | |
| 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 constexpr 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_ = nullptr; | |
| inlined_token_positions_ = nullptr; | |
| } | |
| ProfileFunction* GetFunction() { | |
| // Check to see if we're currently processing inlined functions. If so, | |
| // return the next inlined function. | |
| ProfileFunction* function = GetInlinedFunction(); | |
| if (function != nullptr) { | |
| return function; | |
| } | |
| const uword pc = sample_->At(index_); | |
| ProfileCode* profile_code = | |
| profile_->GetCodeFromPC(pc, sample_->timestamp()); | |
| ASSERT(profile_code != nullptr); | |
| function = profile_code->function(); | |
| ASSERT(function != nullptr); | |
| 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_ == nullptr) || | |
| (inlined_functions_->length() <= 1)) { | |
| ClearInliningData(); | |
| // No inlined functions. | |
| return function; | |
| } | |
| ASSERT(code.is_optimized()); | |
| inlined_index_ = inlined_functions_->length() - 1; | |
| function = GetInlinedFunction(); | |
| ASSERT(function != nullptr); | |
| return function; | |
| } | |
| ProfileFunction* GetInlinedFunction() { | |
| if ((inlined_index_ != kInvalidInlinedIndex) && | |
| (inlined_index_ < inlined_functions_->length())) { | |
| return profile_->FindFunction(*(*inlined_functions_)[inlined_index_]); | |
| } | |
| return nullptr; | |
| } | |
| Profile* profile_; | |
| bool as_functions_; | |
| intptr_t index_; | |
| ProcessedSample* sample_; | |
| ProfileCodeInlinedFunctionsCache inlined_functions_cache_; | |
| GrowableArray<const Function*>* inlined_functions_; | |
| GrowableArray<TokenPosition>* inlined_token_positions_; | |
| intptr_t inlined_index_; | |
| }; | |
| ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| // 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, isolate, &filter, Profiler::sample_block_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 (!FLAG_interpreter) { | |
| #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_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id(), | |
| Dart_TimelineGetMicros(), 16000); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have no allocation samples because none occurred within | |
| // the specified time range. | |
| EXPECT_EQ(0, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_NullSampleBuffer) { | |
| Isolate* isolate = thread->isolate(); | |
| SampleFilter filter(isolate->main_port(), Thread::kMutatorTask, -1, -1); | |
| Profile profile; | |
| profile.Build(thread, isolate, &filter, nullptr); | |
| EXPECT_EQ(0, profile.sample_count()); | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should still only have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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 (!FLAG_interpreter) { | |
| #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_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); | |
| EXPECT_EQ(3, walker.CurrentInclusiveTicks()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); | |
| EXPECT_EQ(3, walker.CurrentInclusiveTicks()); | |
| EXPECT_EQ(0, walker.CurrentExclusiveTicks()); | |
| EXPECT(!walker.Down()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have three allocation samples. | |
| EXPECT_EQ(3, profile.sample_count()); | |
| ProfileStackWalker walker(&profile, true); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), double_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), double_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile); | |
| if (FLAG_interpreter) { | |
| // No intrinsic in the bytecode version. | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| } else { | |
| 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_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| double_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo", 2, &args[0]); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), double_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should still only have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), array_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have no allocation samples. | |
| EXPECT_EQ(0, profile.sample_count()); | |
| } | |
| array_class.SetTraceAllocation(true); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), array_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| array_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), array_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), array_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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) { | |
| Dart_StartProfiling(); | |
| 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(IsolateGroup::Current()->class_table()->At(kContextCid)); | |
| EXPECT(!context_class.IsNull()); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), context_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have no allocation samples. | |
| EXPECT_EQ(0, profile.sample_count()); | |
| } | |
| context_class.SetTraceAllocation(true); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), context_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile); | |
| EXPECT_STREQ("DRT_AllocateContext", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| EXPECT_STREQ("[Stub] AllocateContext", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| } | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| context_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), context_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should still only have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), closure_class.id()); | |
| filter.set_enable_vm_ticks(true); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile); | |
| EXPECT_SUBSTRING("DRT_AllocateClosure", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| EXPECT_STREQ("[Stub] AllocateClosure1", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| } | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), closure_class.id()); | |
| filter.set_enable_vm_ticks(true); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should still only have one allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), float32_list_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), float32_list_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| float32_list_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo"); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), float32_list_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), float32_list_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should now have two allocation samples. | |
| EXPECT_EQ(2, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| one_byte_string_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo", 2, &args[0]); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should now have two allocation samples. | |
| EXPECT_EQ(2, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| one_byte_string_class.SetTraceAllocation(false); | |
| Invoke(root_library, "foo", 2, &args[0]); | |
| { | |
| StackZone zone(thread); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), one_byte_string_class.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should now have two allocation samples. | |
| EXPECT_EQ(2, profile.sample_count()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_FunctionInline) { | |
| Dart_StartProfiling(); | |
| DisableNativeProfileScope dnps; | |
| DisableBackgroundCompilationScope dbcs; | |
| SetFlagScope<int> 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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_InliningIntervalBoundary) { | |
| // 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. | |
| Dart_StartProfiling(); | |
| DisableNativeProfileScope dnps; | |
| DisableBackgroundCompilationScope dbcs; | |
| SetFlagScope<int> 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have 1 allocation sample. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("orange", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("napkin", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("mayo", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("lemon", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("kindle", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("jeep", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("ice", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("haystack", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("granola", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("fred", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("elephant", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("dog", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("cantaloupe", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("banana", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("apple", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("secondInit", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("init", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("go", walker.CurrentName()); | |
| EXPECT(walker.Down()); | |
| EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName()); | |
| EXPECT(!walker.Down()); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePosition) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation samples. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile, true); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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) { | |
| Dart_StartProfiling(); | |
| 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()); | |
| const Function& main = Function::Handle(GetFunction(root_library, "main")); | |
| EXPECT(!main.IsNull()); | |
| // Optimize quickly. | |
| SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5); | |
| // 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation samples. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile, true); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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) { | |
| Dart_StartProfiling(); | |
| 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()); | |
| const Function& main = Function::Handle(GetFunction(root_library, "main")); | |
| EXPECT(!main.IsNull()); | |
| // Optimize quickly. | |
| SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5); | |
| // 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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) { | |
| Dart_StartProfiling(); | |
| 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We should have one allocation samples. | |
| EXPECT_EQ(1, profile.sample_count()); | |
| ProfileStackWalker walker(&profile, true); | |
| EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName()); | |
| if (!FLAG_interpreter) { | |
| #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) { | |
| Dart_StartProfiling(); | |
| 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()); | |
| const Function& main = Function::Handle(GetFunction(root_library, "main")); | |
| EXPECT(!main.IsNull()); | |
| // Optimize quickly. | |
| SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5); | |
| // 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); | |
| Profile profile; | |
| AllocationFilter filter(isolate->main_port(), class_a.id()); | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_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(uword* pc_offsets) { | |
| Isolate* isolate = Isolate::Current(); | |
| ASSERT(Profiler::sample_block_buffer() != nullptr); | |
| Sample* sample = Profiler::sample_block_buffer()->ReserveCPUSample(isolate); | |
| ASSERT(sample != nullptr); | |
| 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<const Function*> functions; | |
| GrowableArray<TokenPosition> 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) { | |
| Dart_StartProfiling(); | |
| 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; | |
| SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer(); | |
| ASSERT(sample_block_buffer != nullptr); | |
| 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_block_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(&sample1[0]); | |
| InsertFakeSample(&sample2[0]); | |
| InsertFakeSample(&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<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| // 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| // 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| // 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| // 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| // 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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<ProfileCode*>(nullptr)); | |
| EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr)); | |
| 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); | |
| } | |
| // Try to hit any races in related to setting TLS and Isolate::mutator_thread_. | |
| // https://github.com/flutter/flutter/issues/134548 | |
| ISOLATE_UNIT_TEST_CASE(Profiler_EnterExitIsolate) { | |
| Profiler::SetConfig({ | |
| .enabled = true, | |
| .period_us = 50, | |
| }); | |
| const char* kScript = "main() => null;\n"; | |
| const Library& root_library = Library::Handle(LoadTestScript(kScript)); | |
| Isolate* isolate = Isolate::Current(); | |
| for (intptr_t i = 0; i < 100000; i++) { | |
| Thread::ExitIsolate(); | |
| Thread::EnterIsolate(isolate); | |
| Invoke(root_library, "main"); | |
| } | |
| } | |
| // Poke a lot at OSThread::thread_interrupt_disabled_, which will be read by the | |
| // sampling thread. | |
| // https://github.com/dart-lang/sdk/issues/62332 | |
| ISOLATE_UNIT_TEST_CASE(Profiler_ThreadEnableDisableProfiler) { | |
| Profiler::SetConfig({ | |
| .enabled = true, | |
| .period_us = 50, | |
| }); | |
| for (intptr_t i = 0; i < 100000; i++) { | |
| Dart_ThreadDisableProfiling(); | |
| Dart_ThreadEnableProfiling(); | |
| } | |
| } | |
| ISOLATE_UNIT_TEST_CASE(Profiler_UpdateRunningState) { | |
| Isolate* isolate = Isolate::Current(); | |
| SampleFilter filter(isolate->main_port(), Thread::kMutatorTask, -1, -1); | |
| Profiler::SetConfig({ | |
| .enabled = true, | |
| .period_us = 50, | |
| }); | |
| const char* kScript = R"( | |
| void main() { | |
| final sw = Stopwatch()..start(); | |
| while (sw.elapsedMilliseconds < 1000) { | |
| } | |
| })"; | |
| const Library& root_library = Library::Handle(LoadTestScript(kScript)); | |
| // Test that profiler collects some samples while main is running if | |
| // it is enabled. | |
| Invoke(root_library, "main"); | |
| { | |
| Profile profile; | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We expect to have collected at least some samples. | |
| EXPECT_LT(0, profile.sample_count()); | |
| } | |
| // Switch profiler off and clear the sample block buffer to discard all | |
| // collected samples. | |
| Dart_StopProfiling(); | |
| delete Profiler::sample_block_buffer(); | |
| Profiler::set_sample_block_buffer(new SampleBlockBuffer()); | |
| // Test that no samples are collected if profiler is disabled. | |
| Invoke(root_library, "main"); | |
| { | |
| Profile profile; | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We expect to have collected no samples. | |
| EXPECT_EQ(0, profile.sample_count()); | |
| } | |
| // Enable profiler again. | |
| Dart_StartProfiling(); | |
| // Test again that samples are collected. | |
| Invoke(root_library, "main"); | |
| { | |
| Profile profile; | |
| profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer()); | |
| // We expect to have collected at least some samples. | |
| EXPECT_LT(0, profile.sample_count()); | |
| } | |
| } | |
| #endif // !PRODUCT | |
| } // namespace dart |