odrefresh: add compilation space checks

Bug: 160683548
Test: manual
Change-Id: Ic640da41855477bf3b6524e82efb15f384127d9e
diff --git a/odrefresh/TODO.md b/odrefresh/TODO.md
index 95c4aa1..5676398 100644
--- a/odrefresh/TODO.md
+++ b/odrefresh/TODO.md
@@ -2,21 +2,24 @@
 
 ## TODO (STOPSHIP until done)
 
-1. Add a log file that tracks status of recent compilation.
-2. Implement back off on trying compilation when previous attempt(s) failed.
-3. Free space calculation and only attempting compilation if sufficient space.
-4. Metrics for tracking issues:
+1. Implement back off on trying compilation when previous attempt(s) failed.
+
+## DONE
+
+<strike>
+
+1. Fix dexoptanalyzer so it can analyze boot extensions.
+2. Parse apex-info-list.xml into an apex_info (to make version and location available).
+3. Timeouts for pathological failures.
+4. Add a log file that tracks status of recent compilation.
+5. Metrics for tracking issues:
    - Successful compilation of all artifacts.
    - Time limit exceeded (indicates a pathological issue, e.g. dex2oat bug, device driver bug, etc).
    - Insufficient space for compilation.
    - Compilation failure (boot extensions)
    - Compilation failure (system server)
    - Unexpected error (a setup or clean-up action failed).
-5. Metrics recording for subprocess timeouts.
-6. Decide and implement testing.
+6. Metrics recording for subprocess timeouts.
+7. Free space calculation and only attempting compilation if sufficient space.
 
-## DONE
-
-1. <strike>Fix dexoptanalyzer so it can analyze boot extensions.</strike>
-2. <strike>Parse apex-info-list.xml into an apex_info (to make version and location available).</strike>
-3. <strike>Timeouts for pathological failures.</strike>
+</strike>
\ No newline at end of file
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index c9c870e..a1d4e54 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -610,7 +610,7 @@
       return cleanup_system_server_return(ExitCode::kCompilationRequired);
     }
 
-    // Cache info looks  good, check all compilation artifacts exist.
+    // Cache info looks good, check all compilation artifacts exist.
     auto cleanup_boot_extensions_return = [this](ExitCode exit_code, InstructionSet isa) {
       return RemoveBootExtensionArtifactsFromData(isa) ? exit_code : ExitCode::kCleanupFailed;
     };
@@ -920,17 +920,6 @@
     return exit_code;
   }
 
-  static void ReportSpace() {
-    uint64_t bytes;
-    std::string data_dir = GetArtApexData();
-    if (GetUsedSpace(data_dir, &bytes)) {
-      LOG(INFO) << "Used space " << bytes << " bytes.";
-    }
-    if (GetFreeSpace(data_dir, &bytes)) {
-      LOG(INFO) << "Available space " << bytes << " bytes.";
-    }
-  }
-
   WARN_UNUSED bool CleanApexdataDirectory() const {
     const std::string& apex_data_path = GetArtApexData();
     if (config_.GetDryRun()) {
@@ -1214,16 +1203,39 @@
   void ReportNextBootAnimationProgress(uint32_t current_compilation) const {
     uint32_t number_of_compilations =
         config_.GetBootExtensionIsas().size() + systemserver_compilable_jars_.size();
-    // We arbitrarly show progress until 90%, expecting that our compilations
+    // We arbitrarily show progress until 90%, expecting that our compilations
     // take a large chunk of boot time.
     uint32_t value = (90 * current_compilation) / number_of_compilations;
     android::base::SetProperty("service.bootanim.progress", std::to_string(value));
   }
 
+  WARN_UNUSED bool CheckCompilationSpace() const {
+    // Check the available storage space against an arbitrary threshold because dex2oat does not
+    // report when it runs out of storage space and we do not want to completely fill
+    // the users data partition.
+    //
+    // We do not have a good way of pre-computing the required space for a compilation step, but
+    // typically observe 16MB as the largest size of an AOT artifact. Since there are three
+    // AOT artifacts per compilation step - an image file, executable file, and a verification
+    // data file - the threshold is three times 16MB.
+    static constexpr uint64_t kMinimumSpaceForCompilation = 3 * 16 * 1024 * 1024;
+
+    uint64_t bytes_available;
+    const std::string& art_apex_data_path = GetArtApexData();
+    if (!GetFreeSpace(art_apex_data_path.c_str(), &bytes_available)) {
+      return false;
+    }
+
+    if (bytes_available < kMinimumSpaceForCompilation) {
+      LOG(WARNING) << "Low space for " << QuotePath(art_apex_data_path) << " (" << bytes_available
+                   << " bytes)";
+      return false;
+    }
+
+    return true;
+  }
 
   WARN_UNUSED ExitCode Compile(OdrMetrics& metrics, bool force_compile) const {
-    ReportSpace();  // TODO(oth): Factor available space into compilation logic.
-
     const char* staging_dir = nullptr;
     metrics.SetStage(OdrMetrics::Stage::kPreparation);
     // Clean-up existing files.
@@ -1259,6 +1271,13 @@
         if (!RemoveBootExtensionArtifactsFromData(isa)) {
             return ExitCode::kCleanupFailed;
         }
+
+        if (!CheckCompilationSpace()) {
+          metrics.SetStatus(OdrMetrics::Status::kNoSpace);
+          // Return kOkay so odsign will keep and sign whatever we have been able to compile.
+          return ExitCode::kOkay;
+        }
+
         if (!CompileBootExtensionArtifacts(
                 isa, staging_dir, metrics, &dex2oat_invocation_count, &error_msg)) {
           LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
@@ -1272,6 +1291,13 @@
 
     if (force_compile || !SystemServerArtifactsExistOnData(&error_msg)) {
       metrics.SetStage(OdrMetrics::Stage::kSystemServerClasspath);
+
+      if (!CheckCompilationSpace()) {
+        metrics.SetStatus(OdrMetrics::Status::kNoSpace);
+        // Return kOkay so odsign will keep and sign whatever we have been able to compile.
+        return ExitCode::kOkay;
+      }
+
       if (!CompileSystemServerArtifacts(
               staging_dir, metrics, &dex2oat_invocation_count, &error_msg)) {
         LOG(ERROR) << "Compilation of system_server failed: " << error_msg;