blob: 1cb823620797e4cb598d52987df4bd430ee86139 [file] [log] [blame] [edit]
// 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(bool, enable_inlining_annotations);
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 RawLibrary* LoadTestScript(const char* script) {
Dart_Handle api_lib;
{
TransitionVMToNative transition(Thread::Current());
api_lib = TestCase::LoadTestScript(script, NULL);
}
Library& lib = Library::Handle();
lib ^= Api::UnwrapHandle(api_lib);
return lib.raw();
}
static RawClass* 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.raw();
}
static RawFunction* 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.raw();
}
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.raw());
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();
}
}
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(), Profile::kNoTags);
// We should have 1 allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive code: B.boo -> main.
walker.Reset(Profile::kExclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT(!walker.Down());
// Inclusive code: main -> B.boo.
walker.Reset(Profile::kInclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT(!walker.Down());
// Inclusive function: main -> B.boo.
walker.Reset(Profile::kInclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", 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(), Profile::kNoTags);
// 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<char*>(malloc(sizeof(char) * 1024));
}
ISOLATE_UNIT_TEST_CASE(Profiler_NativeAllocation) {
EnableProfiler();
bool enable_malloc_hooks_saved = FLAG_profiler_native_memory;
FLAG_profiler_native_memory = true;
MallocHooks::InitOnce();
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::sample_buffer(), Profile::kNoTags);
// We should have 1 allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive code: NativeAllocationSampleHelper -> main.
walker.Reset(Profile::kExclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_SUBSTRING("[Native]", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 1024);
EXPECT(walker.Down());
EXPECT_STREQ("dart::Dart_TestProfiler_NativeAllocation()",
walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCase::Run()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunTest()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunAll()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(!walker.Down());
// Inclusive code: main -> NativeAllocationSampleHelper.
walker.Reset(Profile::kInclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunAll()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunTest()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCase::Run()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::Dart_TestProfiler_NativeAllocation()",
walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_SUBSTRING("[Native]", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 1024);
EXPECT(!walker.Down());
// Exclusive function: NativeAllocationSampleHelper -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_SUBSTRING("[Native]", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 1024);
EXPECT(walker.Down());
EXPECT_STREQ("dart::Dart_TestProfiler_NativeAllocation()",
walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCase::Run()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunTest()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunAll()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(!walker.Down());
// Inclusive function: main -> NativeAllocationSampleHelper.
walker.Reset(Profile::kInclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunAll()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCaseBase::RunTest()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::TestCase::Run()", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_STREQ("dart::Dart_TestProfiler_NativeAllocation()",
walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 0);
EXPECT(walker.Down());
EXPECT_SUBSTRING("[Native]", walker.CurrentName());
EXPECT_EQ(walker.CurrentInclusiveAllocations(), 1024);
EXPECT_EQ(walker.CurrentExclusiveAllocations(), 1024);
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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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);
MallocHooks::TearDown();
FLAG_profiler_native_memory = enable_malloc_hooks_saved;
}
#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) &&
// !defined(TARGET_ARCH_DBC) && !defined(HOST_OS_FUCHSIA)
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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive code: B.boo -> main.
walker.Reset(Profile::kExclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT(!walker.Down());
// Inclusive code: main -> B.boo.
walker.Reset(Profile::kInclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
// Exclusive function: boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT(!walker.Down());
// Inclusive function: main -> boo.
walker.Reset(Profile::kInclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", 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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have three allocation samples.
EXPECT_EQ(3, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive code: B.boo -> main.
walker.Reset(Profile::kExclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(!walker.Down());
// Inclusive code: main -> B.boo.
walker.Reset(Profile::kInclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] main", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] B.boo", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have three allocation samples.
EXPECT_EQ(3, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(!walker.Down());
// Inclusive function: main -> B.boo.
walker.Reset(Profile::kInclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentNodeTickCount());
EXPECT_EQ(3, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(3, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
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->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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("Double_add", walker.CurrentName());
EXPECT(walker.Down());
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(), Profile::kNoTags);
// 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() => new List(4);\n"
"List bar() => new List();\n";
const Library& root_library = Library::Handle(LoadTestScript(kScript));
Isolate* isolate = thread->isolate();
const Class& array_class =
Class::Handle(isolate->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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateArray", walker.CurrentName());
EXPECT(walker.Down());
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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateContext", walker.CurrentName());
EXPECT(walker.Down());
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(), Profile::kNoTags);
// 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(Isolate::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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_SUBSTRING("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate _Closure", walker.CurrentName());
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(), Profile::kNoTags);
// 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->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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("TypedData_Float32Array_new", 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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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->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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("String_concat", walker.CurrentName());
EXPECT(walker.Down());
#if 1
EXPECT_STREQ("[Unoptimized] _StringBase.+", walker.CurrentName());
EXPECT(walker.Down());
#endif
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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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->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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("OneByteString_allocate", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] _OneByteString._allocate",
walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] _OneByteString._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(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// 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<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);
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_buffer(), Profile::kNoTags);
// 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(), Profile::kNoTags);
// We should have 50,000 allocation samples.
EXPECT_EQ(50000, profile.sample_count());
ProfileTrieWalker walker(&profile);
// We have two code objects: mainA and B.boo.
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(!walker.Down());
// We have two code objects: mainA and B.boo.
walker.Reset(Profile::kInclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
// Inline expansion should show us the complete call chain:
// mainA -> B.boo -> B.foo -> B.choo.
walker.Reset(Profile::kExclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.choo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.foo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("mainA", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(!walker.Down());
// Inline expansion should show us the complete call chain:
// mainA -> B.boo -> B.foo -> B.choo.
walker.Reset(Profile::kInclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("mainA", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.foo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT_EQ(0, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.choo", walker.CurrentName());
EXPECT_EQ(1, walker.SiblingCount());
EXPECT_EQ(50000, walker.CurrentNodeTickCount());
EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
}
// Test code transition tags.
{
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(), Profile::kNoTags,
ProfilerService::kCodeTransitionTagsBit);
// We should have 50,000 allocation samples.
EXPECT_EQ(50000, profile.sample_count());
ProfileTrieWalker walker(&profile);
// We have two code objects: mainA and B.boo.
walker.Reset(Profile::kExclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(!walker.Down());
// We have two code objects: mainA and B.boo.
walker.Reset(Profile::kInclusiveCode);
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
// Inline expansion should show us the complete call chain:
// mainA -> B.boo -> B.foo -> B.choo.
walker.Reset(Profile::kExclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Inline End]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.choo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.foo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Inline Start]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("mainA", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(!walker.Down());
// Inline expansion should show us the complete call chain:
// mainA -> B.boo -> B.foo -> B.choo.
walker.Reset(Profile::kInclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("mainA", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Optimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Inline Start]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.foo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("B.choo", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Inline End]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Unoptimized Code]", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
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<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);
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
profile.Build(thread, &filter, Profiler::sample_buffer(), Profile::kNoTags);
// 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(), Profile::kNoTags);
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Inline expansion should show us the complete call chain:
walker.Reset(Profile::kExclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
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());
// Inline expansion should show us the complete call chain:
walker.Reset(Profile::kInclusiveFunction);
EXPECT(walker.Down());
EXPECT_STREQ("mainAlloc", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("a", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("right", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("maybeAlloc", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(!walker.Down());
}
}
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(), Profile::kNoTags);
// We should have 1 allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
walker.Reset(Profile::kExclusiveCode);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
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 =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @AlwaysInline\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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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;
// We use the AlwaysInline and NeverInline annotations in this test.
SetFlagScope<bool> sfs(&FLAG_enable_inlining_annotations, true);
// Optimize quickly.
SetFlagScope<int> sfs2(&FLAG_optimization_counter_threshold, 5);
const char* kScript =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @AlwaysInline\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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("main", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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 =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @NeverInline\n"
" static oats() {\n"
" return boo();\n"
" }\n"
" @AlwaysInline\n"
" static boo() {\n"
" return new A();\n"
" }\n"
"}\n"
"class C {\n"
" @NeverInline bacon() {\n"
" return fox();\n"
" }\n"
" @AlwaysInline 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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("B.oats", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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;
// We use the AlwaysInline and NeverInline annotations in this test.
SetFlagScope<bool> sfs(&FLAG_enable_inlining_annotations, true);
// Optimize quickly.
SetFlagScope<int> sfs2(&FLAG_optimization_counter_threshold, 5);
const char* kScript =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @NeverInline\n"
" static oats() {\n"
" return boo();\n"
" }\n"
" @AlwaysInline\n"
" static boo() {\n"
" return new A();\n"
" }\n"
"}\n"
"class C {\n"
" @NeverInline bacon() {\n"
" return fox();\n"
" }\n"
" @AlwaysInline 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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("B.oats", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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 =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @NeverInline\n"
" static oats() {\n"
" return boo();\n"
" }\n"
" @AlwaysInline\n"
" static boo() {\n"
" return new A();\n"
" }\n"
"}\n"
"class C {\n"
" @NeverInline bacon() {\n"
" return this + this;\n"
" }\n"
" @AlwaysInline operator+(C other) {\n"
" return fox();\n"
" }\n"
" @AlwaysInline 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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("B.oats", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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;
// We use the AlwaysInline and NeverInline annotations in this test.
SetFlagScope<bool> sfs(&FLAG_enable_inlining_annotations, true);
// Optimize quickly.
SetFlagScope<int> sfs2(&FLAG_optimization_counter_threshold, 5);
const char* kScript =
"const AlwaysInline = 'AlwaysInline';\n"
"const NeverInline = 'NeverInline';\n"
"class A {\n"
" var a;\n"
" var b;\n"
" @NeverInline A() { }\n"
"}\n"
"class B {\n"
" @NeverInline\n"
" static oats() {\n"
" return boo();\n"
" }\n"
" @AlwaysInline\n"
" static boo() {\n"
" return new A();\n"
" }\n"
"}\n"
"class C {\n"
" @NeverInline bacon() {\n"
" return this + this;\n"
" }\n"
" @AlwaysInline operator+(C other) {\n"
" return fox();\n"
" }\n"
" @AlwaysInline 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(), Profile::kNoTags);
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileTrieWalker walker(&profile);
// Exclusive function: B.boo -> main.
walker.Reset(Profile::kExclusiveFunction);
// Move down from the root.
EXPECT(walker.Down());
EXPECT_STREQ("DRT_AllocateObject", walker.CurrentName());
EXPECT(walker.Down());
EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentExclusiveTicks());
EXPECT(walker.Down());
EXPECT_STREQ("B.boo", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
EXPECT_EQ(1, walker.CurrentInclusiveTicks());
EXPECT_STREQ("A", walker.CurrentToken());
EXPECT(walker.Down());
EXPECT_STREQ("B.oats", walker.CurrentName());
EXPECT_EQ(1, walker.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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.CurrentNodeTickCount());
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<const Function*> functions;
GrowableArray<TokenPosition> token_positions;
for (intptr_t 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 =
"doWork(i) => i * i;\n"
"main() {\n"
" var sum = 0;\n"
" for (var 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(15);
// Token position of the call to `doWork`.
const TokenPosition callPosition = TokenPosition(90);
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\",15]", 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\":[90]", 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*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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<ProfileCode*>(NULL));
EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(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