Enable ProfileSaver to have a different delay for the first ever save

The runtime now supports -Xps-first-save-ms, which when configured
may alter the delay for the first ever profile save. Subsequent
saves will proceed based on the existing -Xps-min-save-period-ms.

The first ever save is an approximation, and computed by checking
the profiles size.

Test: gtest & manual
Bug: 185979271
Change-Id: I7119b9d2b8829653046565426090c89f6a619a27
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 779e3e0..10c651b 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -487,17 +487,18 @@
 * -Xps-*
 */
 TEST_F(CmdlineParserTest, ProfileSaverOptions) {
-  ProfileSaverOptions opt = ProfileSaverOptions(true, 1, 2, 3, 4, 5, 6, 7, "abc", true);
+  ProfileSaverOptions opt = ProfileSaverOptions(true, 1, 2, 3, 4, 5, 6, 7, 8, "abc", true);
 
   EXPECT_SINGLE_PARSE_VALUE(opt,
                             "-Xjitsaveprofilinginfo "
                             "-Xps-min-save-period-ms:1 "
-                            "-Xps-save-resolved-classes-delay-ms:2 "
-                            "-Xps-hot-startup-method-samples:3 "
-                            "-Xps-min-methods-to-save:4 "
-                            "-Xps-min-classes-to-save:5 "
-                            "-Xps-min-notification-before-wake:6 "
-                            "-Xps-max-notification-before-wake:7 "
+                            "-Xps-min-first-save-ms:2 "
+                            "-Xps-save-resolved-classes-delay-ms:3 "
+                            "-Xps-hot-startup-method-samples:4 "
+                            "-Xps-min-methods-to-save:5 "
+                            "-Xps-min-classes-to-save:6 "
+                            "-Xps-min-notification-before-wake:7 "
+                            "-Xps-max-notification-before-wake:8 "
                             "-Xps-profile-path:abc "
                             "-Xps-profile-boot-class-path",
                             M::ProfileSaverOpts);
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index 7b38b8e..c506e03 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -788,6 +788,12 @@
              &ProfileSaverOptions::min_save_period_ms_,
              type_parser.Parse(suffix));
     }
+    if (android::base::StartsWith(option, "min-first-save-ms:")) {
+      CmdlineType<unsigned int> type_parser;
+      return ParseInto(existing,
+             &ProfileSaverOptions::min_first_save_ms_,
+             type_parser.Parse(suffix));
+    }
     if (android::base::StartsWith(option, "save-resolved-classes-delay-ms:")) {
       CmdlineType<unsigned int> type_parser;
       return ParseInto(existing,
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index ff711fa..263a9ad 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -20,6 +20,7 @@
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include "android-base/strings.h"
 
@@ -180,7 +181,11 @@
       // We might have been woken up by a huge number of notifications to guarantee saving.
       // If we didn't meet the minimum saving period go back to sleep (only if missed by
       // a reasonable margin).
-      uint64_t min_save_period_ns = MsToNs(options_.GetMinSavePeriodMs());
+      bool check_for_first_save = options_.GetMinFirstSaveMs() !=
+          ProfileSaverOptions::kMinFirstSaveMsNotSet;
+      uint64_t min_save_period_ns = MsToNs(check_for_first_save && IsFirstSave()
+          ? options_.GetMinFirstSaveMs()
+          : options_.GetMinSavePeriodMs());
       while (min_save_period_ns * 0.9 > sleep_time) {
         {
           MutexLock mu(self, wait_lock_);
@@ -215,6 +220,48 @@
   }
 }
 
+// TODO(b/185979271): include reference profiles in the test.
+// The current profiles are cleared after bg-dexopt so this test will currently
+// return True after every bg-dexopt call.
+bool ProfileSaver::IsFirstSave() {
+  // Resolve any new registered locations.
+  ResolveTrackedLocations();
+  Thread* self = Thread::Current();
+  SafeMap<std::string, std::set<std::string>> tracked_locations;
+  {
+    // Make a copy so that we don't hold the lock while doing I/O.
+    MutexLock mu(self, *Locks::profiler_lock_);
+    tracked_locations = tracked_dex_base_locations_;
+  }
+
+  for (const auto& it : tracked_locations) {
+    if (ShuttingDown(self)) {
+      return false;
+    }
+    const std::set<std::string>& locations = it.second;
+
+    // Check if any profile is non empty. If so, then this is not the first save.
+    for (const auto& location : locations) {
+      struct stat stat_buffer;
+      if (stat(location.c_str(), &stat_buffer) != 0) {
+        if (VLOG_IS_ON(profiler)) {
+          PLOG(WARNING) << "Failed to stat profile location for IsFirstUse: " << location;
+        }
+        continue;
+      }
+      if (stat_buffer.st_size > 0) {
+        return false;
+      } else {
+        VLOG(profiler) << "Profile location is empty: " << location;
+      }
+    }
+  }
+
+  // All locations are empty. Assume this is the first use.
+  VLOG(profiler) << "All profile locations are empty. This is considered to be first save";
+  return true;
+}
+
 void ProfileSaver::NotifyJitActivity() {
   MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
   if (instance_ == nullptr || instance_->shutting_down_) {
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index 036e717..dc44f24 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -134,6 +134,12 @@
   // to just a few hundreds entries in the ProfileCompilationInfo objects.
   SafeMap<std::string, ProfileCompilationInfo*> profile_cache_ GUARDED_BY(Locks::profiler_lock_);
 
+  // Whether or not this is the first ever profile save.
+  // Note this is an approximation and is not 100% precise. It relies on checking
+  // whether or not the profiles are empty which is not a precise indication
+  // of being the first save (they could have been cleared in the meantime).
+  bool IsFirstSave() REQUIRES(!Locks::profiler_lock_);
+
   // Save period condition support.
   Mutex wait_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
   ConditionVariable period_condition_ GUARDED_BY(wait_lock_);
diff --git a/runtime/jit/profile_saver_options.h b/runtime/jit/profile_saver_options.h
index 1cff713..7492054 100644
--- a/runtime/jit/profile_saver_options.h
+++ b/runtime/jit/profile_saver_options.h
@@ -21,6 +21,9 @@
 struct ProfileSaverOptions {
  public:
   static constexpr uint32_t kMinSavePeriodMs = 40 * 1000;  // 40 seconds
+  // Default value for the min save period on first use, indicating that the
+  // period is not configured.
+  static constexpr uint32_t kMinFirstSaveMsNotSet = 0;
   static constexpr uint32_t kSaveResolvedClassesDelayMs = 5 * 1000;  // 5 seconds
   // Minimum number of JIT samples during launch to mark a method as hot in the profile.
   static constexpr uint32_t kHotStartupMethodSamples = 1;
@@ -34,6 +37,7 @@
   ProfileSaverOptions() :
     enabled_(false),
     min_save_period_ms_(kMinSavePeriodMs),
+    min_first_save_ms_(kMinFirstSaveMsNotSet),
     save_resolved_classes_delay_ms_(kSaveResolvedClassesDelayMs),
     hot_startup_method_samples_(kHotStartupMethodSamplesNotSet),
     min_methods_to_save_(kMinMethodsToSave),
@@ -48,6 +52,7 @@
   ProfileSaverOptions(
       bool enabled,
       uint32_t min_save_period_ms,
+      uint32_t min_first_save_ms,
       uint32_t save_resolved_classes_delay_ms,
       uint32_t hot_startup_method_samples,
       uint32_t min_methods_to_save,
@@ -60,6 +65,7 @@
       bool wait_for_jit_notifications_to_save = true)
   : enabled_(enabled),
     min_save_period_ms_(min_save_period_ms),
+    min_first_save_ms_(min_first_save_ms),
     save_resolved_classes_delay_ms_(save_resolved_classes_delay_ms),
     hot_startup_method_samples_(hot_startup_method_samples),
     min_methods_to_save_(min_methods_to_save),
@@ -81,6 +87,9 @@
   uint32_t GetMinSavePeriodMs() const {
     return min_save_period_ms_;
   }
+  uint32_t GetMinFirstSaveMs() const {
+    return min_first_save_ms_;
+  }
   uint32_t GetSaveResolvedClassesDelayMs() const {
     return save_resolved_classes_delay_ms_;
   }
@@ -122,6 +131,7 @@
   friend std::ostream & operator<<(std::ostream &os, const ProfileSaverOptions& pso) {
     os << "enabled_" << pso.enabled_
         << ", min_save_period_ms_" << pso.min_save_period_ms_
+        << ", min_first_save_ms_" << pso.min_first_save_ms_
         << ", save_resolved_classes_delay_ms_" << pso.save_resolved_classes_delay_ms_
         << ", hot_startup_method_samples_" << pso.hot_startup_method_samples_
         << ", min_methods_to_save_" << pso.min_methods_to_save_
@@ -136,6 +146,7 @@
 
   bool enabled_;
   uint32_t min_save_period_ms_;
+  uint32_t min_first_save_ms_;
   uint32_t save_resolved_classes_delay_ms_;
   // Do not access hot_startup_method_samples_ directly for reading since it may be set to the
   // placeholder default.
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index fec2587..471a3fa 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -288,6 +288,7 @@
       // TODO This should be redone.
       .Define({"-Xps-_",
                "-Xps-min-save-period-ms:_",
+               "-Xps-min-first-save-ms:_",
                "-Xps-save-resolved-classes-delayed-ms:_",
                "-Xps-hot-startup-method-samples:_",
                "-Xps-min-methods-to-save:_",