Expose the optimization status of a dex file

Add a method which exposes the optimization status of a dex file based on
its expected runtime behaviour. The method returns the status in an array
[compilation filter, compilation reason].

The method will try to mimic the runtime effect of loading the dex file.
For example, if there is no usable oat file, the compiler filter will be
set to "run-from-apk".

This will enable more accurate performance monitoring of apks.

Test: oat_file_assistant_test
Bug: 73102540
Change-Id: Ida9abef502dcb3fd07e1b0988771fb60e9b2a423
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index 037d1fb..8ce7921 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -49,7 +49,8 @@
                                     CompilerFilter::Filter filter,
                                     bool relocate,
                                     bool pic,
-                                    bool with_alternate_image) {
+                                    bool with_alternate_image,
+                                    const char* compilation_reason) {
   std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA));
   std::string dalvik_cache_tmp = dalvik_cache + ".redirected";
   std::string oat_location = oat_location_in;
@@ -89,6 +90,10 @@
     args.push_back("--boot-image=" + GetImageLocation2());
   }
 
+  if (compilation_reason != nullptr) {
+    args.push_back("--compilation-reason=" + std::string(compilation_reason));
+  }
+
   std::string error_msg;
   ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg;
 
@@ -155,13 +160,15 @@
 
 void DexoptTest::GeneratePicOdexForTest(const std::string& dex_location,
                             const std::string& odex_location,
-                            CompilerFilter::Filter filter) {
+                            CompilerFilter::Filter filter,
+                            const char* compilation_reason) {
   GenerateOatForTest(dex_location,
                      odex_location,
                      filter,
                      /*relocate*/false,
                      /*pic*/true,
-                     /*with_alternate_image*/false);
+                     /*with_alternate_image*/false,
+                     compilation_reason);
 }
 
 void DexoptTest::GenerateOatForTest(const char* dex_location,
diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h
index 5f0eafd..6e8dc09 100644
--- a/runtime/dexopt_test.h
+++ b/runtime/dexopt_test.h
@@ -46,7 +46,8 @@
                           CompilerFilter::Filter filter,
                           bool relocate,
                           bool pic,
-                          bool with_alternate_image);
+                          bool with_alternate_image,
+                          const char* compilation_reason = nullptr);
 
   // Generate a non-PIC odex file for the purposes of test.
   // The generated odex file will be un-relocated.
@@ -56,7 +57,8 @@
 
   void GeneratePicOdexForTest(const std::string& dex_location,
                               const std::string& odex_location,
-                              CompilerFilter::Filter filter);
+                              CompilerFilter::Filter filter,
+                              const char* compilation_reason = nullptr);
 
   // Generate an oat file for the given dex location in its oat location (under
   // the dalvik cache).
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 6ea9a7a..b49209c 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -549,6 +549,55 @@
   return env->NewStringUTF(oat_file_assistant.GetStatusDump().c_str());
 }
 
+// Return an array specifying the optimization status of the given file.
+// The array specification is [compiler_filter, compiler_reason].
+static jobjectArray DexFile_getDexFileOptimizationStatus(JNIEnv* env,
+                                                         jclass,
+                                                         jstring javaFilename,
+                                                         jstring javaInstructionSet) {
+  ScopedUtfChars filename(env, javaFilename);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+
+  ScopedUtfChars instruction_set(env, javaInstructionSet);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+
+  const InstructionSet target_instruction_set = GetInstructionSetFromString(
+      instruction_set.c_str());
+  if (target_instruction_set == InstructionSet::kNone) {
+    ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException"));
+    std::string message(StringPrintf("Instruction set %s is invalid.", instruction_set.c_str()));
+    env->ThrowNew(iae.get(), message.c_str());
+    return nullptr;
+  }
+
+  std::string compilation_filter;
+  std::string compilation_reason;
+  OatFileAssistant::GetOptimizationStatus(
+      filename.c_str(), target_instruction_set, &compilation_filter, &compilation_reason);
+
+  ScopedLocalRef<jstring> j_compilation_filter(env, env->NewStringUTF(compilation_filter.c_str()));
+  if (j_compilation_filter.get() == nullptr) {
+    return nullptr;
+  }
+  ScopedLocalRef<jstring> j_compilation_reason(env, env->NewStringUTF(compilation_reason.c_str()));
+  if (j_compilation_reason.get() == nullptr) {
+    return nullptr;
+  }
+
+  // Now create output array and copy the set into it.
+  jobjectArray result = env->NewObjectArray(2,
+                                            WellKnownClasses::java_lang_String,
+                                            nullptr);
+  env->SetObjectArrayElement(result, 0, j_compilation_filter.get());
+  env->SetObjectArrayElement(result, 1, j_compilation_reason.get());
+
+  return result;
+}
+
 static jint DexFile_getDexOptNeeded(JNIEnv* env,
                                     jclass,
                                     jstring javaFilename,
@@ -801,7 +850,9 @@
                 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
   NATIVE_METHOD(DexFile, getDexFileOutputPaths,
                 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
-  NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J")
+  NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
+  NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,
+                "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;")
 };
 
 void register_dalvik_system_DexFile(JNIEnv* env) {
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 15a5954..0170073 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -1262,4 +1262,27 @@
   }
   return std::unique_ptr<OatFile>();
 }
+
+// TODO(calin): we could provide a more refined status here
+// (e.g. run from uncompressed apk, run with vdex but not oat etc). It will allow us to
+// track more experiments but adds extra complexity.
+void OatFileAssistant::GetOptimizationStatus(
+    const std::string& filename,
+    InstructionSet isa,
+    std::string* out_compilation_filter,
+    std::string* out_compilation_reason) {
+  // Try to load the oat file as we would do at runtime.
+  OatFileAssistant oat_file_assistant(filename.c_str(), isa, true /* load_executable */);
+  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
+
+  if (oat_file == nullptr) {
+    *out_compilation_filter = "run-from-apk";
+    *out_compilation_reason = "unknown";
+  } else  {
+    *out_compilation_filter = CompilerFilter::NameOfFilter(oat_file->GetCompilerFilter());
+    const char* reason = oat_file->GetCompilationReason();
+    *out_compilation_reason = reason == nullptr ? "unknown" : reason;
+  }
+}
+
 }  // namespace art
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index a614030..a184807 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -226,6 +226,20 @@
   // dex file. The returned description is for debugging purposes only.
   std::string GetStatusDump();
 
+  // Computes the optimization status of the given dex file. The result is
+  // returned via the two output parameters.
+  //   - out_compilation_filter: the level of optimizations (compiler filter)
+  //   - out_compilation_reason: the optimization reason. The reason might
+  //        be "unknown" if the compiler artifacts were not annotated during optimizations.
+  //
+  // This method will try to mimic the runtime effect of loading the dex file.
+  // For example, if there is no usable oat file, the compiler filter will be set
+  // to "run-from-apk".
+  static void GetOptimizationStatus(const std::string& filename,
+                                    InstructionSet isa,
+                                    std::string* out_compilation_filter,
+                                    std::string* out_compilation_reason);
+
   // Open and returns an image space associated with the oat file.
   static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file);
 
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 50f5e7a..72f7d02 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -43,7 +43,27 @@
 static const std::string kSpecialSharedLibrary = "&";  // NOLINT [runtime/string] [4]
 static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr;
 
-class OatFileAssistantTest : public DexoptTest {};
+class OatFileAssistantTest : public DexoptTest {
+ public:
+  void VerifyOptimizationStatus(const std::string& file,
+                                const std::string& expected_filter,
+                                const std::string& expected_reason) {
+    std::string compilation_filter;
+    std::string compilation_reason;
+    OatFileAssistant::GetOptimizationStatus(
+        file, kRuntimeISA, &compilation_filter, &compilation_reason);
+
+    ASSERT_EQ(expected_filter, compilation_filter);
+    ASSERT_EQ(expected_reason, compilation_reason);
+  }
+
+  void VerifyOptimizationStatus(const std::string& file,
+                                CompilerFilter::Filter expected_filter,
+                                const std::string& expected_reason) {
+      VerifyOptimizationStatus(
+          file, CompilerFilter::NameOfFilter(expected_filter), expected_reason);
+  }
+};
 
 class OatFileAssistantNoDex2OatTest : public DexoptTest {
  public:
@@ -107,6 +127,8 @@
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
   EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles());
+
+  VerifyOptimizationStatus(dex_location, "run-from-apk", "unknown");
 }
 
 // Case: We have no DEX file and no OAT file.
@@ -136,7 +158,7 @@
   std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
   std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
   Copy(GetDexSrc1(), dex_location);
-  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
 
   // For the use of oat location by making the dex parent not writable.
   OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
@@ -154,6 +176,8 @@
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
   EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles());
+
+  VerifyOptimizationStatus(dex_location, CompilerFilter::kSpeed, "install");
 }
 
 // Case: We have a DEX file and a PIC ODEX file, but no OAT file. We load the dex
@@ -221,6 +245,8 @@
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
   EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles());
+
+  VerifyOptimizationStatus(dex_location, CompilerFilter::kSpeed, "unknown");
 }
 
 // Case: Passing valid file descriptors of updated odex/vdex filesalong with
@@ -381,6 +407,8 @@
   // Make sure we don't crash in this case when we dump the status. We don't
   // care what the actual dumped value is.
   oat_file_assistant.GetStatusDump();
+
+  VerifyOptimizationStatus(dex_location, "run-from-apk", "unknown");
 }
 
 // Case: We have a DEX file and empty VDEX and ODEX files.