hps: Log a metric for boot time

Log a metric for successful and failed boots, recording the time taken.

BUG=b:194251526
TEST=env FEATURES=test emerge-amd64-generic hpsd
TEST=hps boot ; chrome://histograms/HPS

Change-Id: I95e94dcf69fdf3a6ec65a18852183990cf46eb94
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/3397559
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: Jakub Młynarczyk <jakubm@chromium.org>
Tested-by: Evan Benn <evanbenn@chromium.org>
Commit-Queue: Evan Benn <evanbenn@chromium.org>
diff --git a/hps/hps_impl.cc b/hps/hps_impl.cc
index 4aedaeb..50687c2 100644
--- a/hps/hps_impl.cc
+++ b/hps/hps_impl.cc
@@ -69,6 +69,7 @@
 
   this->Reboot();
 
+  this->boot_start_time_ = base::TimeTicks::Now();
   // If the boot process sent an update, reboot and try again
   // A full update takes 3 boots, so try 3 times.
   for (int i = 0; i < 3; ++i) {
@@ -224,7 +225,9 @@
               << "ms";
       return true;
     } else {
-      hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kBadMagic);
+      hps_metrics_.SendHpsTurnOnResult(
+          HpsTurnOnResult::kBadMagic,
+          base::TimeTicks::Now() - this->boot_start_time_);
       LOG(FATAL) << base::StringPrintf("Bad magic number 0x%04x",
                                        magic.value());
       return false;
@@ -242,7 +245,9 @@
 // Else return BootResult::kFail.
 hps::HPS_impl::BootResult HPS_impl::CheckStage0() {
   if (!CheckMagic()) {
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kNoResponse);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kNoResponse,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kFail;
   }
 
@@ -254,7 +259,7 @@
   }
 
   if (status.value() & R2::kFault || !(status.value() & R2::kOK)) {
-    this->Fault();
+    this->OnBootFault();
     return BootResult::kFail;
   }
 
@@ -274,7 +279,9 @@
   if (!this->write_protect_off_ && !(status.value() & R2::kStage1Verified)) {
     // Stage1 not verified, so need to update it.
     LOG(INFO) << "Stage1 flash not verified";
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kMcuNotVerified);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kMcuNotVerified,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kUpdate;
   }
 
@@ -298,7 +305,9 @@
     // Versions do not match, need to update.
     LOG(INFO) << "Stage1 version mismatch, module: " << version
               << " expected: " << this->stage1_version_;
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kMcuVersionMismatch);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kMcuVersionMismatch,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kUpdate;
   }
 }
@@ -311,7 +320,9 @@
 // Else return BootResult::kFail.
 hps::HPS_impl::BootResult HPS_impl::CheckStage1() {
   if (!CheckMagic()) {
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kStage1NotStarted);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kStage1NotStarted,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kFail;
   }
 
@@ -323,12 +334,14 @@
   }
 
   if (status.value() & R2::kFault || !(status.value() & R2::kOK)) {
-    this->Fault();
+    this->OnBootFault();
     return BootResult::kFail;
   }
 
   if (!(status.value() & R2::kStage1)) {
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kStage1NotStarted);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kStage1NotStarted,
+        base::TimeTicks::Now() - this->boot_start_time_);
     LOG(FATAL) << "Stage 1 did not start";
     return BootResult::kFail;
   }
@@ -354,7 +367,9 @@
     if (status.value() & R2::kAppl) {
       VLOG(1) << "Application boot after " << timer.Elapsed().InMilliseconds()
               << "ms";
-      hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kSuccess);
+      hps_metrics_.SendHpsTurnOnResult(
+          HpsTurnOnResult::kSuccess,
+          base::TimeTicks::Now() - this->boot_start_time_);
       return BootResult::kOk;
     }
 
@@ -367,17 +382,21 @@
     if (error.value() & RError::kSpiNotVer) {
       VLOG(1) << "SPI verification failed after "
               << timer.Elapsed().InMilliseconds() << "ms";
-      hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kSpiNotVerified);
+      hps_metrics_.SendHpsTurnOnResult(
+          HpsTurnOnResult::kSpiNotVerified,
+          base::TimeTicks::Now() - this->boot_start_time_);
       return BootResult::kUpdate;
     } else if (error.value()) {
-      this->Fault();
+      this->OnBootFault();
       return BootResult::kFail;
     }
 
     Sleep(kApplSleep);
   } while (timer.Elapsed() < kApplTimeout);
 
-  hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kApplNotStarted);
+  hps_metrics_.SendHpsTurnOnResult(
+      HpsTurnOnResult::kApplNotStarted,
+      base::TimeTicks::Now() - this->boot_start_time_);
   LOG(FATAL) << "Application did not start";
   return BootResult::kFail;
 }
@@ -392,9 +411,11 @@
   return true;
 }
 
-// Fault bit seen, attempt to dump status information
-void HPS_impl::Fault() {
-  hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kFault);
+// Fault bit seen during boot, attempt to dump status information and abort.
+// Only call this function in the boot process.
+void HPS_impl::OnBootFault() {
+  hps_metrics_.SendHpsTurnOnResult(
+      HpsTurnOnResult::kFault, base::TimeTicks::Now() - this->boot_start_time_);
   std::optional<uint16_t> errors = this->device_->ReadReg(HpsReg::kError);
   if (!errors) {
     LOG(FATAL) << "Fault: cause unknown";
@@ -412,7 +433,9 @@
     hps_metrics_.SendHpsUpdateDuration(HpsBank::kMcuFlash, timer.Elapsed());
     return BootResult::kUpdate;
   } else {
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kMcuUpdateFailure);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kMcuUpdateFailure,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kFail;
   }
 }
@@ -427,7 +450,9 @@
     hps_metrics_.SendHpsUpdateDuration(HpsBank::kSpiFlash, timer.Elapsed());
     return BootResult::kUpdate;
   } else {
-    hps_metrics_.SendHpsTurnOnResult(HpsTurnOnResult::kSpiUpdateFailure);
+    hps_metrics_.SendHpsTurnOnResult(
+        HpsTurnOnResult::kSpiUpdateFailure,
+        base::TimeTicks::Now() - this->boot_start_time_);
     return BootResult::kFail;
   }
 }
diff --git a/hps/hps_impl.h b/hps/hps_impl.h
index 00817a8..a8fd156 100644
--- a/hps/hps_impl.h
+++ b/hps/hps_impl.h
@@ -72,12 +72,13 @@
   BootResult CheckStage1();
   BootResult CheckApplication();
   bool Reboot();
-  void Fault();
+  void OnBootFault();
   bool WaitForBankReady(uint8_t bank);
   BootResult SendStage1Update();
   BootResult SendApplicationUpdate();
   bool WriteFile(uint8_t bank, const base::FilePath& source);
   std::unique_ptr<DevInterface> device_;
+  base::TimeTicks boot_start_time_;
   HpsMetrics hps_metrics_;
   uint16_t hw_rev_;
   uint32_t stage1_version_;
diff --git a/hps/hps_metrics.cc b/hps/hps_metrics.cc
index 677b976..0f4176b 100644
--- a/hps/hps_metrics.cc
+++ b/hps/hps_metrics.cc
@@ -8,10 +8,39 @@
 
 constexpr int kHpsUpdateMcuMaxDurationMilliSeconds = 60 * 1000;
 constexpr int kHpsUpdateSpiMaxDurationMilliSeconds = 40 * 60 * 1000;
+constexpr int kHpsBootMaxDurationMilliSeconds =
+    kHpsUpdateMcuMaxDurationMilliSeconds +
+    kHpsUpdateSpiMaxDurationMilliSeconds + 10 * 60 * 1000;
 
 HpsMetrics::HpsMetrics() : metrics_lib_(std::make_unique<MetricsLibrary>()) {}
 
-bool HpsMetrics::SendHpsTurnOnResult(HpsTurnOnResult result) {
+bool HpsMetrics::SendHpsTurnOnResult(HpsTurnOnResult result,
+                                     base::TimeDelta duration) {
+  switch (result) {
+    case HpsTurnOnResult::kSuccess:
+      metrics_lib_->SendToUMA(kHpsBootSuccessDuration,
+                              static_cast<int>(duration.InMilliseconds()), 1,
+                              kHpsBootMaxDurationMilliSeconds, 50);
+      break;
+    // The kHpsBoot*Duration is only for terminal boot status, (fail or
+    // succeed). So for the 'send an update' results, do not send the duration.
+    case HpsTurnOnResult::kMcuVersionMismatch:
+    case HpsTurnOnResult::kSpiNotVerified:
+    case HpsTurnOnResult::kMcuNotVerified:
+      break;
+    case HpsTurnOnResult::kStage1NotStarted:
+    case HpsTurnOnResult::kApplNotStarted:
+    case HpsTurnOnResult::kNoResponse:
+    case HpsTurnOnResult::kTimeout:
+    case HpsTurnOnResult::kBadMagic:
+    case HpsTurnOnResult::kFault:
+    case HpsTurnOnResult::kMcuUpdateFailure:
+    case HpsTurnOnResult::kSpiUpdateFailure:
+      metrics_lib_->SendToUMA(kHpsBootFailedDuration,
+                              static_cast<int>(duration.InMilliseconds()), 1,
+                              kHpsBootMaxDurationMilliSeconds, 50);
+      break;
+  }
   return metrics_lib_->SendEnumToUMA(hps::kHpsTurnOnResult, result);
 }
 
diff --git a/hps/hps_metrics.h b/hps/hps_metrics.h
index 7c48e02..564199a 100644
--- a/hps/hps_metrics.h
+++ b/hps/hps_metrics.h
@@ -16,6 +16,9 @@
 namespace hps {
 
 constexpr char kHpsTurnOnResult[] = "ChromeOS.HPS.TurnOn.Result";
+constexpr char kHpsBootFailedDuration[] = "ChromeOS.HPS.TurnOn.Failed.Duration";
+constexpr char kHpsBootSuccessDuration[] =
+    "ChromeOS.HPS.TurnOn.Success.Duration";
 constexpr char kHpsUpdateMcuDuration[] = "ChromeOS.HPS.Update.Mcu.Duration";
 constexpr char kHpsUpdateSpiDuration[] = "ChromeOS.HPS.Update.Spi.Duration";
 
@@ -46,7 +49,7 @@
 
   ~HpsMetrics() = default;
 
-  bool SendHpsTurnOnResult(HpsTurnOnResult result);
+  bool SendHpsTurnOnResult(HpsTurnOnResult result, base::TimeDelta duration);
   bool SendHpsUpdateDuration(HpsBank bank, base::TimeDelta duration);
 
   void SetMetricsLibraryForTesting(
diff --git a/hps/hps_metrics_test.cc b/hps/hps_metrics_test.cc
index 0a450a6..f5abf6a 100644
--- a/hps/hps_metrics_test.cc
+++ b/hps/hps_metrics_test.cc
@@ -49,11 +49,36 @@
   ASSERT_EQ(all_results.size(),
             static_cast<int>(HpsTurnOnResult::kMaxValue) + 1);
 
+  constexpr int kDuration = 49;
   for (auto result : all_results) {
+    switch (result) {
+      case HpsTurnOnResult::kSuccess:
+        EXPECT_CALL(*GetMetricsLibraryMock(),
+                    SendToUMA(kHpsBootSuccessDuration, kDuration, _, _, _))
+            .Times(1);
+        break;
+      case HpsTurnOnResult::kMcuVersionMismatch:
+      case HpsTurnOnResult::kSpiNotVerified:
+      case HpsTurnOnResult::kMcuNotVerified:
+        break;
+      case HpsTurnOnResult::kStage1NotStarted:
+      case HpsTurnOnResult::kApplNotStarted:
+      case HpsTurnOnResult::kNoResponse:
+      case HpsTurnOnResult::kTimeout:
+      case HpsTurnOnResult::kBadMagic:
+      case HpsTurnOnResult::kFault:
+      case HpsTurnOnResult::kMcuUpdateFailure:
+      case HpsTurnOnResult::kSpiUpdateFailure:
+        EXPECT_CALL(*GetMetricsLibraryMock(),
+                    SendToUMA(kHpsBootFailedDuration, kDuration, _, _, _))
+            .Times(1);
+        break;
+    }
     EXPECT_CALL(*GetMetricsLibraryMock(),
                 SendEnumToUMA(kHpsTurnOnResult, static_cast<int>(result), _))
         .Times(1);
-    hps_metrics_.SendHpsTurnOnResult(result);
+    hps_metrics_.SendHpsTurnOnResult(
+        result, base::TimeDelta::FromMilliseconds(kDuration));
   }
 }
 
diff --git a/hps/hps_test.cc b/hps/hps_test.cc
index c5aacef..ba2da9e 100644
--- a/hps/hps_test.cc
+++ b/hps/hps_test.cc
@@ -289,6 +289,9 @@
   hps_->Init(version, mcu, spi1, spi2);
 
   // Boot the module.
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -323,6 +326,9 @@
   hps_->Init(version, mcu, spi1, spi2);
 
   // Boot the module.
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(2);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -363,6 +369,9 @@
   EXPECT_CALL(*GetMetricsLibraryMock(),
               SendToUMA(hps::kHpsUpdateMcuDuration, _, _, _, _))
       .Times(1);
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -409,6 +418,9 @@
   EXPECT_CALL(*GetMetricsLibraryMock(),
               SendToUMA(hps::kHpsUpdateSpiDuration, _, _, _, _))
       .Times(1);
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -467,6 +479,9 @@
   EXPECT_CALL(*GetMetricsLibraryMock(),
               SendToUMA(hps::kHpsUpdateSpiDuration, _, _, _, _))
       .Times(1);
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -506,6 +521,9 @@
   hps_->Init(version, mcu, spi1, spi2);
 
   // Boot the module.
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -556,6 +574,9 @@
   EXPECT_CALL(*GetMetricsLibraryMock(),
               SendToUMA(hps::kHpsUpdateMcuDuration, _, _, _, _))
       .Times(1);
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,
@@ -613,6 +634,9 @@
   EXPECT_CALL(*GetMetricsLibraryMock(),
               SendToUMA(hps::kHpsUpdateSpiDuration, _, _, _, _))
       .Times(1);
+  EXPECT_CALL(*GetMetricsLibraryMock(),
+              SendToUMA(kHpsBootSuccessDuration, _, _, _, _))
+      .Times(1);
   EXPECT_CALL(
       *GetMetricsLibraryMock(),
       SendEnumToUMA(hps::kHpsTurnOnResult,