| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "instrumentation.h" |
| |
| #include "common_runtime_test.h" |
| #include "common_throws.h" |
| #include "class_linker-inl.h" |
| #include "dex_file.h" |
| #include "handle_scope-inl.h" |
| #include "jvalue.h" |
| #include "runtime.h" |
| #include "scoped_thread_state_change.h" |
| #include "thread_list.h" |
| #include "thread-inl.h" |
| |
| namespace art { |
| namespace instrumentation { |
| |
| class TestInstrumentationListener FINAL : public instrumentation::InstrumentationListener { |
| public: |
| TestInstrumentationListener() |
| : received_method_enter_event(false), received_method_exit_event(false), |
| received_method_unwind_event(false), received_dex_pc_moved_event(false), |
| received_field_read_event(false), received_field_written_event(false), |
| received_exception_caught_event(false), received_backward_branch_event(false) {} |
| |
| virtual ~TestInstrumentationListener() {} |
| |
| void MethodEntered(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t dex_pc ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_method_enter_event = true; |
| } |
| |
| void MethodExited(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t dex_pc ATTRIBUTE_UNUSED, |
| const JValue& return_value ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_method_exit_event = true; |
| } |
| |
| void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t dex_pc ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_method_unwind_event = true; |
| } |
| |
| void DexPcMoved(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t new_dex_pc ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_dex_pc_moved_event = true; |
| } |
| |
| void FieldRead(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t dex_pc ATTRIBUTE_UNUSED, |
| ArtField* field ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_field_read_event = true; |
| } |
| |
| void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object* this_object ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| uint32_t dex_pc ATTRIBUTE_UNUSED, |
| ArtField* field ATTRIBUTE_UNUSED, |
| const JValue& field_value ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_field_written_event = true; |
| } |
| |
| void ExceptionCaught(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Throwable* exception_object ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_exception_caught_event = true; |
| } |
| |
| void BackwardBranch(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::ArtMethod* method ATTRIBUTE_UNUSED, |
| int32_t dex_pc_offset ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| received_backward_branch_event = true; |
| } |
| |
| void Reset() { |
| received_method_enter_event = false; |
| received_method_exit_event = false; |
| received_method_unwind_event = false; |
| received_dex_pc_moved_event = false; |
| received_field_read_event = false; |
| received_field_written_event = false; |
| received_exception_caught_event = false; |
| received_backward_branch_event = false; |
| } |
| |
| bool received_method_enter_event; |
| bool received_method_exit_event; |
| bool received_method_unwind_event; |
| bool received_dex_pc_moved_event; |
| bool received_field_read_event; |
| bool received_field_written_event; |
| bool received_exception_caught_event; |
| bool received_backward_branch_event; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestInstrumentationListener); |
| }; |
| |
| class InstrumentationTest : public CommonRuntimeTest { |
| public: |
| // Unique keys used to test Instrumentation::ConfigureStubs. |
| static constexpr const char* kClientOneKey = "TestClient1"; |
| static constexpr const char* kClientTwoKey = "TestClient2"; |
| |
| void CheckConfigureStubs(const char* key, Instrumentation::InstrumentationLevel level) { |
| ScopedObjectAccess soa(Thread::Current()); |
| instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); |
| { |
| soa.Self()->TransitionFromRunnableToSuspended(kSuspended); |
| Runtime* runtime = Runtime::Current(); |
| runtime->GetThreadList()->SuspendAll("Instrumentation::ConfigureStubs"); |
| instr->ConfigureStubs(key, level); |
| runtime->GetThreadList()->ResumeAll(); |
| soa.Self()->TransitionFromSuspendedToRunnable(); |
| } |
| } |
| |
| Instrumentation::InstrumentationLevel GetCurrentInstrumentationLevel() { |
| return Runtime::Current()->GetInstrumentation()->GetCurrentInstrumentationLevel(); |
| } |
| |
| size_t GetInstrumentationUserCount() { |
| ScopedObjectAccess soa(Thread::Current()); |
| return Runtime::Current()->GetInstrumentation()->requested_instrumentation_levels_.size(); |
| } |
| |
| void TestEvent(uint32_t instrumentation_event) { |
| ScopedObjectAccess soa(Thread::Current()); |
| instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); |
| TestInstrumentationListener listener; |
| { |
| soa.Self()->TransitionFromRunnableToSuspended(kSuspended); |
| Runtime* runtime = Runtime::Current(); |
| runtime->GetThreadList()->SuspendAll("Add instrumentation listener"); |
| instr->AddListener(&listener, instrumentation_event); |
| runtime->GetThreadList()->ResumeAll(); |
| soa.Self()->TransitionFromSuspendedToRunnable(); |
| } |
| |
| mirror::ArtMethod* const event_method = nullptr; |
| mirror::Object* const event_obj = nullptr; |
| const uint32_t event_dex_pc = 0; |
| |
| // Check the listener is registered and is notified of the event. |
| EXPECT_TRUE(HasEventListener(instr, instrumentation_event)); |
| EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); |
| ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc); |
| EXPECT_TRUE(DidListenerReceiveEvent(listener, instrumentation_event)); |
| |
| listener.Reset(); |
| { |
| soa.Self()->TransitionFromRunnableToSuspended(kSuspended); |
| Runtime* runtime = Runtime::Current(); |
| runtime->GetThreadList()->SuspendAll("Remove instrumentation listener"); |
| instr->RemoveListener(&listener, instrumentation_event); |
| runtime->GetThreadList()->ResumeAll(); |
| soa.Self()->TransitionFromSuspendedToRunnable(); |
| } |
| |
| // Check the listener is not registered and is not notified of the event. |
| EXPECT_FALSE(HasEventListener(instr, instrumentation_event)); |
| EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); |
| ReportEvent(instr, instrumentation_event, soa.Self(), event_method, event_obj, event_dex_pc); |
| EXPECT_FALSE(DidListenerReceiveEvent(listener, instrumentation_event)); |
| } |
| |
| void DeoptimizeMethod(Thread* self, Handle<mirror::ArtMethod> method, |
| bool enable_deoptimization) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("Single method deoptimization"); |
| if (enable_deoptimization) { |
| instrumentation->EnableDeoptimization(); |
| } |
| instrumentation->Deoptimize(method.Get()); |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| void UndeoptimizeMethod(Thread* self, Handle<mirror::ArtMethod> method, |
| const char* key, bool disable_deoptimization) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("Single method undeoptimization"); |
| instrumentation->Undeoptimize(method.Get()); |
| if (disable_deoptimization) { |
| instrumentation->DisableDeoptimization(key); |
| } |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| void DeoptimizeEverything(Thread* self, const char* key, bool enable_deoptimization) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("Full deoptimization"); |
| if (enable_deoptimization) { |
| instrumentation->EnableDeoptimization(); |
| } |
| instrumentation->DeoptimizeEverything(key); |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| void UndeoptimizeEverything(Thread* self, const char* key, bool disable_deoptimization) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("Full undeoptimization"); |
| instrumentation->UndeoptimizeEverything(key); |
| if (disable_deoptimization) { |
| instrumentation->DisableDeoptimization(key); |
| } |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| void EnableMethodTracing(Thread* self, const char* key, bool needs_interpreter) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("EnableMethodTracing"); |
| instrumentation->EnableMethodTracing(key, needs_interpreter); |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| void DisableMethodTracing(Thread* self, const char* key) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Runtime* runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation(); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| runtime->GetThreadList()->SuspendAll("EnableMethodTracing"); |
| instrumentation->DisableMethodTracing(key); |
| runtime->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| } |
| |
| private: |
| static bool HasEventListener(const instrumentation::Instrumentation* instr, uint32_t event_type) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| switch (event_type) { |
| case instrumentation::Instrumentation::kMethodEntered: |
| return instr->HasMethodEntryListeners(); |
| case instrumentation::Instrumentation::kMethodExited: |
| return instr->HasMethodExitListeners(); |
| case instrumentation::Instrumentation::kMethodUnwind: |
| return instr->HasMethodUnwindListeners(); |
| case instrumentation::Instrumentation::kDexPcMoved: |
| return instr->HasDexPcListeners(); |
| case instrumentation::Instrumentation::kFieldRead: |
| return instr->HasFieldReadListeners(); |
| case instrumentation::Instrumentation::kFieldWritten: |
| return instr->HasFieldWriteListeners(); |
| case instrumentation::Instrumentation::kExceptionCaught: |
| return instr->HasExceptionCaughtListeners(); |
| case instrumentation::Instrumentation::kBackwardBranch: |
| return instr->HasBackwardBranchListeners(); |
| default: |
| LOG(FATAL) << "Unknown instrumentation event " << event_type; |
| UNREACHABLE(); |
| } |
| } |
| |
| static void ReportEvent(const instrumentation::Instrumentation* instr, uint32_t event_type, |
| Thread* self, mirror::ArtMethod* method, mirror::Object* obj, |
| uint32_t dex_pc) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| switch (event_type) { |
| case instrumentation::Instrumentation::kMethodEntered: |
| instr->MethodEnterEvent(self, obj, method, dex_pc); |
| break; |
| case instrumentation::Instrumentation::kMethodExited: { |
| JValue value; |
| instr->MethodExitEvent(self, obj, method, dex_pc, value); |
| break; |
| } |
| case instrumentation::Instrumentation::kMethodUnwind: |
| instr->MethodUnwindEvent(self, obj, method, dex_pc); |
| break; |
| case instrumentation::Instrumentation::kDexPcMoved: |
| instr->DexPcMovedEvent(self, obj, method, dex_pc); |
| break; |
| case instrumentation::Instrumentation::kFieldRead: |
| instr->FieldReadEvent(self, obj, method, dex_pc, nullptr); |
| break; |
| case instrumentation::Instrumentation::kFieldWritten: { |
| JValue value; |
| instr->FieldWriteEvent(self, obj, method, dex_pc, nullptr, value); |
| break; |
| } |
| case instrumentation::Instrumentation::kExceptionCaught: { |
| ThrowArithmeticExceptionDivideByZero(); |
| mirror::Throwable* event_exception = self->GetException(); |
| instr->ExceptionCaughtEvent(self, event_exception); |
| self->ClearException(); |
| break; |
| } |
| case instrumentation::Instrumentation::kBackwardBranch: |
| instr->BackwardBranch(self, method, dex_pc); |
| break; |
| default: |
| LOG(FATAL) << "Unknown instrumentation event " << event_type; |
| UNREACHABLE(); |
| } |
| } |
| |
| static bool DidListenerReceiveEvent(const TestInstrumentationListener& listener, |
| uint32_t event_type) { |
| switch (event_type) { |
| case instrumentation::Instrumentation::kMethodEntered: |
| return listener.received_method_enter_event; |
| case instrumentation::Instrumentation::kMethodExited: |
| return listener.received_method_exit_event; |
| case instrumentation::Instrumentation::kMethodUnwind: |
| return listener.received_method_unwind_event; |
| case instrumentation::Instrumentation::kDexPcMoved: |
| return listener.received_dex_pc_moved_event; |
| case instrumentation::Instrumentation::kFieldRead: |
| return listener.received_field_read_event; |
| case instrumentation::Instrumentation::kFieldWritten: |
| return listener.received_field_written_event; |
| case instrumentation::Instrumentation::kExceptionCaught: |
| return listener.received_exception_caught_event; |
| case instrumentation::Instrumentation::kBackwardBranch: |
| return listener.received_backward_branch_event; |
| default: |
| LOG(FATAL) << "Unknown instrumentation event " << event_type; |
| UNREACHABLE(); |
| } |
| } |
| }; |
| |
| TEST_F(InstrumentationTest, NoInstrumentation) { |
| ScopedObjectAccess soa(Thread::Current()); |
| instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation(); |
| ASSERT_NE(instr, nullptr); |
| |
| EXPECT_FALSE(instr->AreExitStubsInstalled()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_FALSE(instr->IsActive()); |
| EXPECT_FALSE(instr->ShouldNotifyMethodEnterExitEvents()); |
| |
| // Test interpreter table is the default one. |
| EXPECT_EQ(instrumentation::kMainHandlerTable, instr->GetInterpreterHandlerTable()); |
| |
| // Check there is no registered listener. |
| EXPECT_FALSE(instr->HasDexPcListeners()); |
| EXPECT_FALSE(instr->HasExceptionCaughtListeners()); |
| EXPECT_FALSE(instr->HasFieldReadListeners()); |
| EXPECT_FALSE(instr->HasFieldWriteListeners()); |
| EXPECT_FALSE(instr->HasMethodEntryListeners()); |
| EXPECT_FALSE(instr->HasMethodExitListeners()); |
| EXPECT_FALSE(instr->IsActive()); |
| } |
| |
| // Test instrumentation listeners for each event. |
| TEST_F(InstrumentationTest, MethodEntryEvent) { |
| TestEvent(instrumentation::Instrumentation::kMethodEntered); |
| } |
| |
| TEST_F(InstrumentationTest, MethodExitEvent) { |
| TestEvent(instrumentation::Instrumentation::kMethodExited); |
| } |
| |
| TEST_F(InstrumentationTest, MethodUnwindEvent) { |
| TestEvent(instrumentation::Instrumentation::kMethodUnwind); |
| } |
| |
| TEST_F(InstrumentationTest, DexPcMovedEvent) { |
| TestEvent(instrumentation::Instrumentation::kDexPcMoved); |
| } |
| |
| TEST_F(InstrumentationTest, FieldReadEvent) { |
| TestEvent(instrumentation::Instrumentation::kFieldRead); |
| } |
| |
| TEST_F(InstrumentationTest, FieldWriteEvent) { |
| TestEvent(instrumentation::Instrumentation::kFieldWritten); |
| } |
| |
| TEST_F(InstrumentationTest, ExceptionCaughtEvent) { |
| TestEvent(instrumentation::Instrumentation::kExceptionCaught); |
| } |
| |
| TEST_F(InstrumentationTest, BackwardBranchEvent) { |
| TestEvent(instrumentation::Instrumentation::kBackwardBranch); |
| } |
| |
| TEST_F(InstrumentationTest, DeoptimizeDirectMethod) { |
| ScopedObjectAccess soa(Thread::Current()); |
| jobject class_loader = LoadDex("Instrumentation"); |
| Runtime* const runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); |
| ClassLinker* class_linker = runtime->GetClassLinker(); |
| StackHandleScope<2> hs(soa.Self()); |
| Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader))); |
| mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); |
| ASSERT_TRUE(klass != nullptr); |
| Handle<mirror::ArtMethod> method_to_deoptimize( |
| hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V"))); |
| ASSERT_TRUE(method_to_deoptimize.Get() != nullptr); |
| |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| DeoptimizeMethod(soa.Self(), method_to_deoptimize, true); |
| |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| constexpr const char* instrumentation_key = "DeoptimizeDirectMethod"; |
| UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true); |
| |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| } |
| |
| TEST_F(InstrumentationTest, FullDeoptimization) { |
| ScopedObjectAccess soa(Thread::Current()); |
| Runtime* const runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| |
| constexpr const char* instrumentation_key = "FullDeoptimization"; |
| DeoptimizeEverything(soa.Self(), instrumentation_key, true); |
| |
| EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| |
| UndeoptimizeEverything(soa.Self(), instrumentation_key, true); |
| |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| } |
| |
| TEST_F(InstrumentationTest, MixedDeoptimization) { |
| ScopedObjectAccess soa(Thread::Current()); |
| jobject class_loader = LoadDex("Instrumentation"); |
| Runtime* const runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); |
| ClassLinker* class_linker = runtime->GetClassLinker(); |
| StackHandleScope<2> hs(soa.Self()); |
| Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader))); |
| mirror::Class* klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); |
| ASSERT_TRUE(klass != nullptr); |
| Handle<mirror::ArtMethod> method_to_deoptimize( |
| hs.NewHandle(klass->FindDeclaredDirectMethod("instanceMethod", "()V"))); |
| ASSERT_TRUE(method_to_deoptimize.Get() != nullptr); |
| |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| DeoptimizeMethod(soa.Self(), method_to_deoptimize, true); |
| // Deoptimizing a method does not change instrumentation level. |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| constexpr const char* instrumentation_key = "MixedDeoptimization"; |
| DeoptimizeEverything(soa.Self(), instrumentation_key, false); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| UndeoptimizeEverything(soa.Self(), instrumentation_key, false); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| EXPECT_TRUE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| |
| UndeoptimizeMethod(soa.Self(), method_to_deoptimize, instrumentation_key, true); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_FALSE(instr->IsDeoptimized(method_to_deoptimize.Get())); |
| } |
| |
| TEST_F(InstrumentationTest, MethodTracing_Interpreter) { |
| ScopedObjectAccess soa(Thread::Current()); |
| Runtime* const runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| |
| constexpr const char* instrumentation_key = "MethodTracing"; |
| EnableMethodTracing(soa.Self(), instrumentation_key, true); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| |
| DisableMethodTracing(soa.Self(), instrumentation_key); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| } |
| |
| TEST_F(InstrumentationTest, MethodTracing_InstrumentationEntryExitStubs) { |
| ScopedObjectAccess soa(Thread::Current()); |
| Runtime* const runtime = Runtime::Current(); |
| instrumentation::Instrumentation* instr = runtime->GetInstrumentation(); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| |
| constexpr const char* instrumentation_key = "MethodTracing"; |
| EnableMethodTracing(soa.Self(), instrumentation_key, false); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| EXPECT_TRUE(instr->AreExitStubsInstalled()); |
| |
| DisableMethodTracing(soa.Self(), instrumentation_key); |
| EXPECT_EQ(Instrumentation::InstrumentationLevel::kInstrumentNothing, |
| GetCurrentInstrumentationLevel()); |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); |
| } |
| |
| // We use a macro to print the line number where the test is failing. |
| #define CHECK_INSTRUMENTATION(_level, _user_count) \ |
| do { \ |
| Instrumentation* const instr = Runtime::Current()->GetInstrumentation(); \ |
| bool interpreter = \ |
| (_level == Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); \ |
| EXPECT_EQ(_level, GetCurrentInstrumentationLevel()); \ |
| EXPECT_EQ(_user_count, GetInstrumentationUserCount()); \ |
| if (instr->IsForcedInterpretOnly()) { \ |
| EXPECT_TRUE(instr->InterpretOnly()); \ |
| } else if (interpreter) { \ |
| EXPECT_TRUE(instr->InterpretOnly()); \ |
| } else { \ |
| EXPECT_FALSE(instr->InterpretOnly()); \ |
| } \ |
| if (interpreter) { \ |
| EXPECT_TRUE(instr->AreAllMethodsDeoptimized()); \ |
| } else { \ |
| EXPECT_FALSE(instr->AreAllMethodsDeoptimized()); \ |
| } \ |
| } while (false) |
| |
| TEST_F(InstrumentationTest, ConfigureStubs_Nothing) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Check no-op. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubs) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Check we can switch to instrumentation stubs |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Check we can disable instrumentation. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, ConfigureStubs_Interpreter) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Check we can switch to interpreter |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Check we can disable instrumentation. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, ConfigureStubs_InstrumentationStubsToInterpreter) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with instrumentation stubs. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Configure stubs with interpreter. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Check we can disable instrumentation. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, ConfigureStubs_InterpreterToInstrumentationStubs) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with interpreter. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Configure stubs with instrumentation stubs. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Check we can disable instrumentation. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, |
| ConfigureStubs_InstrumentationStubsToInterpreterToInstrumentationStubs) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with instrumentation stubs. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Configure stubs with interpreter. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Configure stubs with instrumentation stubs again. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Check we can disable instrumentation. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, MultiConfigureStubs_Nothing) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Check kInstrumentNothing with two clients. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubs) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with instrumentation stubs for 1st client. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Configure stubs with instrumentation stubs for 2nd client. |
| CheckConfigureStubs(kClientTwoKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 2U); |
| |
| // 1st client requests instrumentation deactivation but 2nd client still needs |
| // instrumentation stubs. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // 2nd client requests instrumentation deactivation |
| CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, MultiConfigureStubs_Interpreter) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with interpreter for 1st client. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Configure stubs with interpreter for 2nd client. |
| CheckConfigureStubs(kClientTwoKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); |
| |
| // 1st client requests instrumentation deactivation but 2nd client still needs interpreter. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // 2nd client requests instrumentation deactivation |
| CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, MultiConfigureStubs_InstrumentationStubsThenInterpreter) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with instrumentation stubs for 1st client. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // Configure stubs with interpreter for 2nd client. |
| CheckConfigureStubs(kClientTwoKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); |
| |
| // 1st client requests instrumentation deactivation but 2nd client still needs interpreter. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // 2nd client requests instrumentation deactivation |
| CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| TEST_F(InstrumentationTest, MultiConfigureStubs_InterpreterThenInstrumentationStubs) { |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| |
| // Configure stubs with interpreter for 1st client. |
| CheckConfigureStubs(kClientOneKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); |
| |
| // Configure stubs with instrumentation stubs for 2nd client. |
| CheckConfigureStubs(kClientTwoKey, |
| Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); |
| |
| // 1st client requests instrumentation deactivation but 2nd client still needs |
| // instrumentation stubs. |
| CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, |
| 1U); |
| |
| // 2nd client requests instrumentation deactivation |
| CheckConfigureStubs(kClientTwoKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); |
| CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentNothing, 0U); |
| } |
| |
| } // namespace instrumentation |
| } // namespace art |