Support multiple instrumentation clients

Changes Instrumentation::ConfigureStubs to support multiple clients
that need different levels of instrumenation. A client is identified
by a string key used to save the desired instrumentation level.

Also adds regression gtest instrumentation_test and some cleanup.

Bug: 19829329
Change-Id: I1fc24a86fcb7cb46d4be806895376c25cc0a0b3c
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
new file mode 100644
index 0000000..5afacb8
--- /dev/null
+++ b/runtime/instrumentation_test.cc
@@ -0,0 +1,791 @@
+/*
+ * 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