Add metrics to crostini installer.
Currently, there are a significant number of install failures we don't
have much visibility into, especially on low-powered devices. This CL
adds metrics for free disk space and time to install, and records
failures due to network flake in the correct bucket, to give us a
better understanding of this.
Change-Id: I34ce13eabb698a9962db3da8cfe76eb48e33c3b4
Bug: 953545
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1569369
Reviewed-by: Mark Pearson <mpearson@chromium.org>
Reviewed-by: Nicholas Verne <nverne@chromium.org>
Commit-Queue: Fergus Dall <sidereal@google.com>
Auto-Submit: Fergus Dall <sidereal@google.com>
Cr-Commit-Position: refs/heads/master@{#652041}
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
index d0770a9..09076bc 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
@@ -10,6 +10,7 @@
#include "ash/public/cpp/ash_typography.h"
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
#include "base/numerics/ranges.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
@@ -56,6 +57,8 @@
// TODO(timloh): This is just a placeholder.
constexpr int kDownloadSizeInBytes = 300 * 1024 * 1024;
+constexpr int kUninitializedDiskSpace = -1;
+
constexpr gfx::Insets kOOBEButtonRowInsets(32, 64, 32, 64);
constexpr int kOOBEWindowWidth = 768;
// TODO(timloh): The button row's preferred height (48px) adds to this. I'm not
@@ -64,12 +67,22 @@
constexpr int kOOBEWindowHeight = 640 - 48;
constexpr int kLinuxIllustrationWidth = 448;
constexpr int kLinuxIllustrationHeight = 180;
+constexpr base::FilePath::CharType kHomeDirectory[] =
+ FILE_PATH_LITERAL("/home");
constexpr char kCrostiniSetupResultHistogram[] = "Crostini.SetupResult";
constexpr char kCrostiniSetupSourceHistogram[] = "Crostini.SetupSource";
constexpr char kCrostiniTimeFromDeviceSetupToInstall[] =
"Crostini.TimeFromDeviceSetupToInstall";
constexpr char kCrostiniDiskImageSizeHistogram[] = "Crostini.DiskImageSize";
+constexpr char kCrostiniTimeToInstallSuccess[] =
+ "Crostini.TimeToInstallSuccess";
+constexpr char kCrostiniTimeToInstallCancel[] = "Crostini.TimeToInstallCancel";
+constexpr char kCrostiniTimeToInstallError[] = "Crostini.TimeToInstallError";
+constexpr char kCrostiniAvailableDiskSuccess[] =
+ "Crostini.AvailableDiskSuccess";
+constexpr char kCrostiniAvailableDiskCancel[] = "Crostini.AvailableDiskCancel";
+constexpr char kCrostiniAvailableDiskError[] = "Crostini.AvailableDiskError";
void RecordTimeFromDeviceSetupToInstallMetric() {
base::PostTaskWithTraitsAndReplyWithResult(
@@ -118,6 +131,18 @@
crostini::CrostiniManager::GetForProfile(profile)->SetInstallerViewStatus(
true);
+
+ base::PostTaskWithTraitsAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace,
+ base::FilePath(kHomeDirectory)),
+ base::BindOnce(
+ &CrostiniInstallerView::OnAvailableDiskSpace,
+ g_crostini_installer_view->weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CrostiniInstallerView::OnAvailableDiskSpace(int64_t bytes) {
+ free_disk_space_ = bytes;
}
int CrostiniInstallerView::GetDialogButtons() const {
@@ -165,6 +190,7 @@
UpdateState(State::INSTALL_START);
profile_->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
+ install_start_time_ = base::TimeTicks::Now();
// The default value of kCrostiniContainers is set to migrate existing
// crostini users who don't have the pref set. If crostini is being installed,
@@ -200,6 +226,19 @@
}
bool CrostiniInstallerView::Cancel() {
+ if (!has_logged_timing_result_ &&
+ restart_id_ == crostini::CrostiniManager::kUninitializedRestartId) {
+ UMA_HISTOGRAM_LONG_TIMES(kCrostiniTimeToInstallCancel,
+ base::TimeTicks::Now() - install_start_time_);
+ has_logged_timing_result_ = true;
+ }
+ if (!has_logged_free_disk_result_ &&
+ restart_id_ == crostini::CrostiniManager::kUninitializedRestartId &&
+ free_disk_space_ != kUninitializedDiskSpace) {
+ base::UmaHistogramCounts1M(kCrostiniAvailableDiskCancel,
+ free_disk_space_ >> 20);
+ has_logged_free_disk_result_ = true;
+ }
if (state_ != State::INSTALL_END && state_ != State::CLEANUP &&
state_ != State::CLEANUP_FINISHED &&
restart_id_ != crostini::CrostiniManager::kUninitializedRestartId) {
@@ -254,10 +293,18 @@
DCHECK_EQ(state_, State::INSTALL_IMAGE_LOADER);
if (result != CrostiniResult::SUCCESS) {
- LOG(ERROR) << "Failed to install the cros-termina component";
- HandleError(
- l10n_util::GetStringUTF16(IDS_CROSTINI_INSTALLER_LOAD_TERMINA_ERROR),
- SetupResult::kErrorLoadingTermina);
+ if (content::GetNetworkConnectionTracker()->IsOffline()) {
+ LOG(ERROR) << "Network connection dropped while downloading cros-termina";
+ const base::string16 device_type = ui::GetChromeOSDeviceName();
+ HandleError(l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_INSTALLER_OFFLINE_ERROR, device_type),
+ SetupResult::kErrorOffline);
+ } else {
+ LOG(ERROR) << "Failed to install the cros-termina component";
+ HandleError(
+ l10n_util::GetStringUTF16(IDS_CROSTINI_INSTALLER_LOAD_TERMINA_ERROR),
+ SetupResult::kErrorLoadingTermina);
+ }
return;
}
VLOG(1) << "cros-termina install success";
@@ -334,11 +381,19 @@
DCHECK_EQ(state_, State::SETUP_CONTAINER);
if (result != CrostiniResult::SUCCESS) {
- LOG(ERROR) << "Failed to set up container with error code: "
- << static_cast<int>(result);
- HandleError(
- l10n_util::GetStringUTF16(IDS_CROSTINI_INSTALLER_SETUP_CONTAINER_ERROR),
- SetupResult::kErrorSettingUpContainer);
+ if (content::GetNetworkConnectionTracker()->IsOffline()) {
+ LOG(ERROR) << "Network connection dropped while downloading container";
+ const base::string16 device_type = ui::GetChromeOSDeviceName();
+ HandleError(l10n_util::GetStringFUTF16(
+ IDS_CROSTINI_INSTALLER_OFFLINE_ERROR, device_type),
+ SetupResult::kErrorOffline);
+ } else {
+ LOG(ERROR) << "Failed to set up container with error code: "
+ << static_cast<int>(result);
+ HandleError(l10n_util::GetStringUTF16(
+ IDS_CROSTINI_INSTALLER_SETUP_CONTAINER_ERROR),
+ SetupResult::kErrorSettingUpContainer);
+ }
return;
}
VLOG(1) << "Set up container successfully";
@@ -393,7 +448,9 @@
}
CrostiniInstallerView::CrostiniInstallerView(Profile* profile)
- : profile_(profile), weak_ptr_factory_(this) {
+ : profile_(profile),
+ free_disk_space_(kUninitializedDiskSpace),
+ weak_ptr_factory_(this) {
// Layout constants from the spec.
constexpr gfx::Insets kDialogInsets(60, 64, 0, 64);
constexpr int kDialogSpacingVertical = 32;
@@ -504,6 +561,18 @@
if (state_ == State::ERROR)
return;
+ if (!has_logged_timing_result_) {
+ UMA_HISTOGRAM_LONG_TIMES(kCrostiniTimeToInstallError,
+ base::TimeTicks::Now() - install_start_time_);
+ has_logged_timing_result_ = true;
+ }
+ if (!has_logged_free_disk_result_ &&
+ free_disk_space_ != kUninitializedDiskSpace) {
+ base::UmaHistogramCounts1M(kCrostiniAvailableDiskError,
+ free_disk_space_ >> 20);
+ has_logged_free_disk_result_ = true;
+ }
+
RecordSetupResultHistogram(result);
restart_id_ = crostini::CrostiniManager::kUninitializedRestartId;
UpdateState(State::ERROR);
@@ -545,6 +614,17 @@
RecordSetupResultHistogram(SetupResult::kSuccess);
crostini_manager->UpdateLaunchMetricsForEnterpriseReporting();
RecordTimeFromDeviceSetupToInstallMetric();
+ if (!has_logged_timing_result_) {
+ UMA_HISTOGRAM_LONG_TIMES(kCrostiniTimeToInstallSuccess,
+ base::TimeTicks::Now() - install_start_time_);
+ has_logged_timing_result_ = true;
+ }
+ if (!has_logged_free_disk_result_ &&
+ free_disk_space_ != kUninitializedDiskSpace) {
+ base::UmaHistogramCounts1M(kCrostiniAvailableDiskSuccess,
+ free_disk_space_ >> 20);
+ has_logged_free_disk_result_ = true;
+ }
GetWidget()->Close();
}
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.h b/chrome/browser/ui/views/crostini/crostini_installer_view.h
index 5b84d025..f208a5e4 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.h
@@ -135,6 +135,8 @@
void RecordSetupResultHistogram(SetupResult result);
+ void OnAvailableDiskSpace(int64_t bytes);
+
State state_ = State::PROMPT;
views::ImageView* logo_image_ = nullptr;
views::Label* big_message_label_ = nullptr;
@@ -149,6 +151,8 @@
base::Time state_start_time_;
std::unique_ptr<base::RepeatingTimer> state_progress_timer_;
bool do_cleanup_ = true;
+ base::TimeTicks install_start_time_;
+ int64_t free_disk_space_;
// Whether the result has been logged or not is stored to prevent multiple
// results being logged for a given setup flow. This can happen due to
@@ -156,6 +160,10 @@
// able to hit Cancel after any errors occur.
bool has_logged_result_ = false;
+ bool has_logged_timing_result_ = false;
+
+ bool has_logged_free_disk_result_ = false;
+
base::RepeatingCallback<void(double)> progress_bar_callback_for_testing_;
base::OnceClosure quit_closure_for_testing_;
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index eb0eff1..5395e1d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -20715,6 +20715,50 @@
</summary>
</histogram>
+<histogram name="Crostini.AvailableDiskCancel" units="MiB"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The available disk space at the start of the crostini install flow, recorded
+ when installation was canceled. This is only recorded if the user cancels
+ the install before it finishes. It is not recorded if the user did not start
+ the install (i.e. pressed cancel before pressing accept), or if the
+ cancellation happens after an error, including if they press retry. For
+ clarity, at most one of Crostini.AvailableDiskCancel,
+ Crostini.AvailableDiskError, and Crostini.AvailableDiskSuccess will be
+ recorded between opening the installer view and closing it.
+ </summary>
+</histogram>
+
+<histogram name="Crostini.AvailableDiskError" units="MiB"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The available disk space at the start of the crostini install flow, recorded
+ when installation returned an error. This is only recorded the first time an
+ error occurs, it is not re-recorded if the user presses retry and an error
+ occurs again. For clarity, at most one of Crostini.AvailableDiskCancel,
+ Crostini.AvailableDiskError, and Crostini.AvailableDiskSuccess will be
+ recorded between opening the installer view and closing it.
+ </summary>
+</histogram>
+
+<histogram name="Crostini.AvailableDiskSuccess" units="MiB"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The available disk space at the start of the crostini install flow, recorded
+ when installation succeeded. This is not recorded if an error occurred, the
+ user pressed retry, and the install succeeded on a second or subsequent
+ attempt. For clarity, at most one of Crostini.AvailableDiskCancel,
+ Crostini.AvailableDiskError, and Crostini.AvailableDiskSuccess will be
+ recorded between opening the installer view and closing it.
+ </summary>
+</histogram>
+
<histogram name="Crostini.Backup" enum="CrostiniExportContainerResult"
expires_after="2020-01-01">
<owner>joelhockey@chromium.org</owner>
@@ -20824,6 +20868,50 @@
</summary>
</histogram>
+<histogram name="Crostini.TimeToInstallCancel" units="ms"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The time taken for the crostini installer to be canceled by the user. This
+ is only recorded if the user cancels the install before it finishes. It is
+ not recorded if the user did not start the install (i.e. pressed cancel
+ before pressing accept), or if the cancellation happens after an error,
+ including if they press retry. For clarity, at most one of
+ Crostini.TimeToInstallCancel, Crostini.TimeToInstallError, and
+ Crostini.TimeToInstallSuccess will be recorded between opening the installer
+ view and closing it.
+ </summary>
+</histogram>
+
+<histogram name="Crostini.TimeToInstallError" units="ms"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The time taken for the crostini installer to fail due to an error. This is
+ only recorded the first time an error occurs, it is not re-recorded if the
+ user presses retry and an error occurs again. For clarity, at most one of
+ Crostini.TimeToInstallCancel, Crostini.TimeToInstallError, and
+ Crostini.TimeToInstallSuccess will be recorded between opening the installer
+ view and closing it.
+ </summary>
+</histogram>
+
+<histogram name="Crostini.TimeToInstallSuccess" units="ms"
+ expires_after="2020-04-16">
+ <owner>sidereal@google.com</owner>
+ <owner>nverne@chromium.org</owner>
+ <summary>
+ The time taken for the crostini installer to finish successfully. This is
+ not recorded if an error occurred, the user pressed retry, and the install
+ succeeded on a second or subsequent attempt. For clarity, at most one of
+ Crostini.TimeToInstallCancel, Crostini.TimeToInstallError, and
+ Crostini.TimeToInstallSuccess will be recorded between opening the installer
+ view and closing it.
+ </summary>
+</histogram>
+
<histogram name="Crostini.UninstallResult" enum="CrostiniUninstallResult">
<owner>benwells@chromium.org</owner>
<owner>tbuckley@chromium.org</owner>