Record foreign dex files loaded by the app in the profile

A foreign dex file is a file which is not owned by the app
(it's not part of its code paths or its private data directory).

When such a dex file is loaded by the app, the runtime will record
a marker in a dedicated profile folder (foreing_dex_profile_path).
The marker is just a file named after the canonical location of the
dex file where '/' is replaced by '@'.

The markers will be used by the system server system server to
decide if the apk should be fully or profile guide compiled.

Bug: 27334750
Bug: 26080105

(cherry picked from commit 86a9ebe4197e963249ffbbaa1830da97ed642fa5)

Change-Id: I8be1fd4d854fa1e23c3c1054c9c083ad7b27317b
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 3e66ce2..4623a4a 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -194,9 +194,11 @@
 }
 
 void Jit::StartProfileSaver(const std::string& filename,
-                            const std::vector<std::string>& code_paths) {
+                            const std::vector<std::string>& code_paths,
+                            const std::string& foreign_dex_profile_path,
+                            const std::string& app_dir) {
   if (save_profiling_info_) {
-    ProfileSaver::Start(filename, code_cache_.get(), code_paths);
+    ProfileSaver::Start(filename, code_cache_.get(), code_paths, foreign_dex_profile_path, app_dir);
   }
 }
 
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 109ca3d..570f683 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -70,7 +70,17 @@
     return instrumentation_cache_.get();
   }
 
-  void StartProfileSaver(const std::string& filename, const std::vector<std::string>& code_paths);
+  // Starts the profile saver if the config options allow profile recording.
+  // The profile will be stored in the specified `filename` and will contain
+  // information collected from the given `code_paths` (a set of dex locations).
+  // The `foreign_dex_profile_path` is the path where the saver will put the
+  // profile markers for loaded dex files which are not owned by the application.
+  // The `app_dir` is the application directory and is used to decide which
+  // dex files belong to the application.
+  void StartProfileSaver(const std::string& filename,
+                         const std::vector<std::string>& code_paths,
+                         const std::string& foreign_dex_profile_path,
+                         const std::string& app_dir);
   void StopProfileSaver();
 
   void DumpForSigQuit(std::ostream& os) {
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index ab26f6f..5abfa6c 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -16,6 +16,10 @@
 
 #include "profile_saver.h"
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
 #include "art_method-inl.h"
 #include "scoped_thread_state_change.h"
 #include "oat_file_manager.h"
@@ -42,14 +46,31 @@
 
 ProfileSaver::ProfileSaver(const std::string& output_filename,
                            jit::JitCodeCache* jit_code_cache,
-                           const std::vector<std::string>& code_paths)
+                           const std::vector<std::string>& code_paths,
+                           const std::string& foreign_dex_profile_path,
+                           const std::string& app_data_dir)
     : jit_code_cache_(jit_code_cache),
+      foreign_dex_profile_path_(foreign_dex_profile_path),
       code_cache_last_update_time_ns_(0),
       shutting_down_(false),
       first_profile_(true),
       wait_lock_("ProfileSaver wait lock"),
       period_condition_("ProfileSaver period condition", wait_lock_) {
   AddTrackedLocations(output_filename, code_paths);
+  app_data_dir_ = "";
+  if (!app_data_dir.empty()) {
+    // The application directory is used to determine which dex files are owned by app.
+    // Since it could be a symlink (e.g. /data/data instead of /data/user/0), and we
+    // don't have control over how the dex files are actually loaded (symlink or canonical path),
+    // store it's canonical form to be sure we use the same base when comparing.
+    UniqueCPtr<const char[]> app_data_dir_real_path(realpath(app_data_dir.c_str(), nullptr));
+    if (app_data_dir_real_path != nullptr) {
+      app_data_dir_.assign(app_data_dir_real_path.get());
+    } else {
+      LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir_
+          << ". The app dir will not be used to determine which dex files belong to the app";
+    }
+  }
 }
 
 void ProfileSaver::Run() {
@@ -162,7 +183,9 @@
 
 void ProfileSaver::Start(const std::string& output_filename,
                          jit::JitCodeCache* jit_code_cache,
-                         const std::vector<std::string>& code_paths) {
+                         const std::vector<std::string>& code_paths,
+                         const std::string& foreign_dex_profile_path,
+                         const std::string& app_data_dir) {
   DCHECK(Runtime::Current()->UseJit());
   DCHECK(!output_filename.empty());
   DCHECK(jit_code_cache != nullptr);
@@ -181,7 +204,11 @@
   VLOG(profiler) << "Starting profile saver using output file: " << output_filename
       << ". Tracking: " << Join(code_paths, ':');
 
-  instance_ = new ProfileSaver(output_filename, jit_code_cache, code_paths);
+  instance_ = new ProfileSaver(output_filename,
+                               jit_code_cache,
+                               code_paths,
+                               foreign_dex_profile_path,
+                               app_data_dir);
 
   // Create a new thread which does the saving.
   CHECK_PTHREAD_CALL(
@@ -248,4 +275,83 @@
   }
 }
 
+void ProfileSaver::NotifyDexUse(const std::string& dex_location) {
+  std::set<std::string> app_code_paths;
+  std::string foreign_dex_profile_path;
+  std::string app_data_dir;
+  {
+    MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
+    DCHECK(instance_ != nullptr);
+    // Make a copy so that we don't hold the lock while doing I/O.
+    for (const auto& it : instance_->tracked_dex_base_locations_) {
+      app_code_paths.insert(it.second.begin(), it.second.end());
+    }
+    foreign_dex_profile_path = instance_->foreign_dex_profile_path_;
+    app_data_dir = instance_->app_data_dir_;
+  }
+
+  MaybeRecordDexUseInternal(dex_location,
+                            app_code_paths,
+                            foreign_dex_profile_path,
+                            app_data_dir);
+}
+
+void ProfileSaver::MaybeRecordDexUseInternal(
+      const std::string& dex_location,
+      const std::set<std::string>& app_code_paths,
+      const std::string& foreign_dex_profile_path,
+      const std::string& app_data_dir) {
+  if (foreign_dex_profile_path.empty()) {
+    LOG(WARNING) << "Asked to record foreign dex use without a valid profile path ";
+    return;
+  }
+
+  UniqueCPtr<const char[]> dex_location_real_path(realpath(dex_location.c_str(), nullptr));
+  std::string dex_location_real_path_str(dex_location_real_path.get());
+
+  if (dex_location_real_path_str.compare(0, app_data_dir.length(), app_data_dir) == 0) {
+    // The dex location is under the application folder. Nothing to record.
+    return;
+  }
+
+  if (app_code_paths.find(dex_location) != app_code_paths.end()) {
+    // The dex location belongs to the application code paths. Nothing to record.
+    return;
+  }
+  // Do another round of checks with the real paths.
+  // Note that we could cache all the real locations in the saver (since it's an expensive
+  // operation). However we expect that app_code_paths is small (usually 1 element), and
+  // NotifyDexUse is called just a few times in the app lifetime. So we make the compromise
+  // to save some bytes of memory usage.
+  for (const auto& app_code_location : app_code_paths) {
+    UniqueCPtr<const char[]> real_app_code_location(realpath(app_code_location.c_str(), nullptr));
+    std::string real_app_code_location_str(real_app_code_location.get());
+    if (real_app_code_location_str == dex_location_real_path_str) {
+      // The dex location belongs to the application code paths. Nothing to record.
+      return;
+    }
+  }
+
+  // For foreign dex files we record a flag on disk. PackageManager will (potentially) take this
+  // into account when deciding how to optimize the loaded dex file.
+  // The expected flag name is the canonical path of the apk where '/' is substituted to '@'.
+  // (it needs to be kept in sync with
+  // frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java)
+  std::replace(dex_location_real_path_str.begin(), dex_location_real_path_str.end(), '/', '@');
+  std::string flag_path = foreign_dex_profile_path + "/" + dex_location_real_path_str;
+  // No need to give any sort of access to flag_path. The system has enough permissions
+  // to test for its existence.
+  int fd = TEMP_FAILURE_RETRY(open(flag_path.c_str(), O_CREAT | O_EXCL, 0));
+  if (fd != -1) {
+    if (close(fd) != 0) {
+      PLOG(WARNING) << "Could not close file after flagging foreign dex use " << flag_path;
+    }
+  } else {
+    if (errno != EEXIST) {
+      // Another app could have already created the file.
+      PLOG(WARNING) << "Could not create foreign dex use mark " << flag_path;
+    }
+  }
+}
+
 }   // namespace art
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index 21017c1..e7eab95 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -30,7 +30,9 @@
   // If the saver is already running it adds (output_filename, code_paths) to its tracked locations.
   static void Start(const std::string& output_filename,
                     jit::JitCodeCache* jit_code_cache,
-                    const std::vector<std::string>& code_paths)
+                    const std::vector<std::string>& code_paths,
+                    const std::string& foreign_dex_profile_path,
+                    const std::string& app_data_dir)
       REQUIRES(!Locks::profiler_lock_, !wait_lock_);
 
   // Stops the profile saver thread.
@@ -42,10 +44,14 @@
   // Returns true if the profile saver is started.
   static bool IsStarted() REQUIRES(!Locks::profiler_lock_);
 
+  static void NotifyDexUse(const std::string& dex_location);
+
  private:
   ProfileSaver(const std::string& output_filename,
                jit::JitCodeCache* jit_code_cache,
-               const std::vector<std::string>& code_paths);
+               const std::vector<std::string>& code_paths,
+               const std::string& foreign_dex_profile_path,
+               const std::string& app_data_dir);
 
   // NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock.
   static void* RunProfileSaverThread(void* arg)
@@ -64,6 +70,12 @@
                            const std::vector<std::string>& code_paths)
       REQUIRES(Locks::profiler_lock_);
 
+  static void MaybeRecordDexUseInternal(
+      const std::string& dex_location,
+      const std::set<std::string>& tracked_locations,
+      const std::string& foreign_dex_profile_path,
+      const std::string& app_data_dir);
+
   // The only instance of the saver.
   static ProfileSaver* instance_ GUARDED_BY(Locks::profiler_lock_);
   // Profile saver thread.
@@ -72,6 +84,8 @@
   jit::JitCodeCache* jit_code_cache_;
   SafeMap<std::string, std::set<std::string>> tracked_dex_base_locations_
       GUARDED_BY(Locks::profiler_lock_);
+  std::string foreign_dex_profile_path_;
+  std::string app_data_dir_;
   uint64_t code_cache_last_update_time_ns_;
   bool shutting_down_ GUARDED_BY(Locks::profiler_lock_);
   bool first_profile_ = true;
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index da4a891..f6b2f21 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -566,8 +566,9 @@
 static void VMRuntime_registerAppInfo(JNIEnv* env,
                                       jclass clazz ATTRIBUTE_UNUSED,
                                       jstring profile_file,
-                                      jstring app_dir ATTRIBUTE_UNUSED,  // TODO: remove argument
-                                      jobjectArray code_paths) {
+                                      jstring app_dir,
+                                      jobjectArray code_paths,
+                                      jstring foreign_dex_profile_path) {
   std::vector<std::string> code_paths_vec;
   int code_paths_length = env->GetArrayLength(code_paths);
   for (int i = 0; i < code_paths_length; i++) {
@@ -581,7 +582,22 @@
   std::string profile_file_str(raw_profile_file);
   env->ReleaseStringUTFChars(profile_file, raw_profile_file);
 
-  Runtime::Current()->RegisterAppInfo(code_paths_vec, profile_file_str);
+  std::string foreign_dex_profile_path_str = "";
+  if (foreign_dex_profile_path != nullptr) {
+    const char* raw_foreign_dex_profile_path =
+        env->GetStringUTFChars(foreign_dex_profile_path, nullptr);
+    foreign_dex_profile_path_str.assign(raw_foreign_dex_profile_path);
+    env->ReleaseStringUTFChars(foreign_dex_profile_path, raw_foreign_dex_profile_path);
+  }
+
+  const char* raw_app_dir = env->GetStringUTFChars(app_dir, nullptr);
+  std::string app_dir_str(raw_app_dir);
+  env->ReleaseStringUTFChars(app_dir, raw_app_dir);
+
+  Runtime::Current()->RegisterAppInfo(code_paths_vec,
+                                      profile_file_str,
+                                      foreign_dex_profile_path_str,
+                                      app_dir_str);
 }
 
 static jboolean VMRuntime_isBootClassPathOnDisk(JNIEnv* env, jclass, jstring java_instruction_set) {
@@ -638,7 +654,7 @@
   NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "!()Z"),
   NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"),
   NATIVE_METHOD(VMRuntime, registerAppInfo,
-                "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"),
+                "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V"),
   NATIVE_METHOD(VMRuntime, isBootClassPathOnDisk, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(VMRuntime, getCurrentInstructionSet, "()Ljava/lang/String;"),
 };
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index ea26d58..3e6d0b5 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -442,6 +442,10 @@
           + std::string(dex_location));
     }
   }
+
+  // TODO(calin): Consider optimizing this knowing that is useless to record the
+  // use of fully compiled apks.
+  Runtime::Current()->NotifyDexLoaded(dex_location);
   return dex_files;
 }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 47ef214..c2d2dca 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -120,6 +120,7 @@
 #include "os.h"
 #include "parsed_options.h"
 #include "profiler.h"
+#include "jit/profile_saver.h"
 #include "quick/quick_method_frame_info.h"
 #include "reflection.h"
 #include "runtime_options.h"
@@ -1717,7 +1718,9 @@
 }
 
 void Runtime::RegisterAppInfo(const std::vector<std::string>& code_paths,
-                              const std::string& profile_output_filename) {
+                              const std::string& profile_output_filename,
+                              const std::string& foreign_dex_profile_path,
+                              const std::string& app_dir) {
   if (jit_.get() == nullptr) {
     // We are not JITing. Nothing to do.
     return;
@@ -1740,7 +1743,18 @@
   }
 
   profile_output_filename_ = profile_output_filename;
-  jit_->StartProfileSaver(profile_output_filename, code_paths);
+  jit_->StartProfileSaver(profile_output_filename,
+                          code_paths,
+                          foreign_dex_profile_path,
+                          app_dir);
+}
+
+void Runtime::NotifyDexLoaded(const std::string& dex_location) {
+  VLOG(profiler) << "Notify dex loaded: " << dex_location;
+  // We know that if the ProfileSaver is started then we can record profile information.
+  if (ProfileSaver::IsStarted()) {
+    ProfileSaver::NotifyDexUse(dex_location);
+  }
 }
 
 // Transaction support.
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 83e77d2..8e99f80 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -467,7 +467,10 @@
   }
 
   void RegisterAppInfo(const std::vector<std::string>& code_paths,
-                       const std::string& profile_output_filename);
+                       const std::string& profile_output_filename,
+                       const std::string& foreign_dex_profile_path,
+                       const std::string& app_dir);
+  void NotifyDexLoaded(const std::string& dex_location);
 
   // Transaction support.
   bool IsActiveTransaction() const {