1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
|
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/report/report_controller.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/private_computing/private_computing_client.h"
#include "chromeos/ash/components/dbus/private_computing/private_computing_service.pb.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/dbus/system_clock/system_clock_sync_observation.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/report/device_metrics/actives/one_day_impl.h"
#include "chromeos/ash/components/report/device_metrics/actives/twenty_eight_day_impl.h"
#include "chromeos/ash/components/report/device_metrics/churn/cohort_impl.h"
#include "chromeos/ash/components/report/device_metrics/churn/observation_impl.h"
#include "chromeos/ash/components/report/device_metrics/use_case/use_case.h"
#include "chromeos/ash/components/report/prefs/fresnel_pref_names.h"
#include "chromeos/ash/components/report/utils/pref_utils.h"
#include "chromeos/ash/components/report/utils/time_utils.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/channel.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace psm_rlwe = private_membership::rlwe;
namespace ash::report {
namespace {
ReportController* g_ash_report_controller = nullptr;
// Amount of time to wait before triggering repeating timer.
constexpr base::TimeDelta kTimeToRepeat = base::Hours(1);
// Maximum time to wait for time sync before not reporting device as active
// in current attempt.
// This corresponds to at least seven TCP retransmissions attempts to
// the remote server used to update the system clock.
constexpr base::TimeDelta kSystemClockSyncWaitTimeout = base::Seconds(45);
// Create helper methods for lambda functions.
base::OnceClosure CreateSavePreservedFileCallback(
PrefService* local_state,
base::WeakPtr<ReportController> controller_weak_ptr) {
return base::BindOnce(
[](PrefService* local_state,
base::WeakPtr<ReportController> controller_weak_ptr) {
PrivateComputingClient::Get()->SaveLastPingDatesStatus(
utils::CreatePreservedFileContents(local_state),
base::BindOnce(
&ReportController::OnSaveLocalStateToPreservedFileComplete,
controller_weak_ptr));
},
local_state, controller_weak_ptr);
}
base::OnceClosure CreateReportObservationCallback(
base::WeakPtr<device_metrics::ObservationImpl> observation_weak_ptr,
base::OnceCallback<void()> save_preserved_file_cb) {
return base::BindOnce(&device_metrics::ObservationImpl::Run,
observation_weak_ptr,
std::move(save_preserved_file_cb));
}
base::OnceClosure CreateReportCohortCallback(
base::WeakPtr<device_metrics::CohortImpl> cohort_weak_ptr,
base::OnceCallback<void()> report_observation_cb) {
return base::BindOnce(&device_metrics::CohortImpl::Run, cohort_weak_ptr,
std::move(report_observation_cb));
}
base::OnceClosure CreateReport28DaCallback(
base::WeakPtr<device_metrics::TwentyEightDayImpl> twenty_eight_day_weak_ptr,
base::OnceCallback<void()> report_cohort_cb) {
return base::BindOnce(&device_metrics::TwentyEightDayImpl::Run,
twenty_eight_day_weak_ptr, std::move(report_cohort_cb));
}
// Record boolean indicating the preserved file has been written successfully.
void RecordPreservedFileWritten(bool is_written) {
base::UmaHistogramBoolean("Ash.Report.PreservedFileWritten", is_written);
}
// Record boolean for whether the device is classified as for testing.
void RecordIsTestDevice(bool is_test) {
base::UmaHistogramBoolean("Ash.Report.IsTestDevice", is_test);
}
// Record boolean for whether the psm stable secret key is read and parsed
// successfully from the VPD file that is loaded to machine statistics.
void RecordIsPsmSecretSet(bool is_set) {
base::UmaHistogramBoolean("Ash.Report.IsPsmSecretSet", is_set);
}
// Record boolean for if the system clock has been synced successfully or not.
void RecordIsSystemClockSynced(bool is_synced) {
base::UmaHistogramBoolean("Ash.Report.IsSystemClockSynced", is_synced);
}
} // namespace
ReportController* ReportController::Get() {
return g_ash_report_controller;
}
// static
void ReportController::RegisterPrefs(PrefRegistrySimple* registry) {
const base::Time unix_epoch = base::Time::UnixEpoch();
registry->RegisterTimePref(
prefs::kDeviceActiveLastKnown1DayActivePingTimestamp, unix_epoch);
registry->RegisterTimePref(
prefs::kDeviceActiveLastKnown28DayActivePingTimestamp, unix_epoch);
registry->RegisterTimePref(
prefs::kDeviceActiveChurnCohortMonthlyPingTimestamp, unix_epoch);
registry->RegisterTimePref(
prefs::kDeviceActiveChurnObservationMonthlyPingTimestamp, unix_epoch);
registry->RegisterIntegerPref(prefs::kDeviceActiveLastKnownChurnActiveStatus,
0);
registry->RegisterBooleanPref(
prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus0, false);
registry->RegisterBooleanPref(
prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus1, false);
registry->RegisterBooleanPref(
prefs::kDeviceActiveLastKnownIsActiveCurrentPeriodMinus2, false);
registry->RegisterDictionaryPref(prefs::kDeviceActive28DayActivePingCache);
}
ReportController::ReportController(
const device_metrics::ChromeDeviceMetadataParameters& chrome_device_params,
PrefService* local_state,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::unique_ptr<device_metrics::PsmClientManager> psm_client_manager)
: chrome_device_params_(chrome_device_params),
local_state_(local_state),
url_loader_factory_(url_loader_factory),
report_timer_(std::make_unique<base::RepeatingTimer>()),
network_state_handler_(NetworkHandler::Get()->network_state_handler()),
statistics_provider_(system::StatisticsProvider::GetInstance()),
clock_(base::DefaultClock::GetInstance()),
psm_client_manager_(std::move(psm_client_manager)) {
DCHECK(local_state);
DCHECK(psm_client_manager_.get());
DCHECK(!g_ash_report_controller);
g_ash_report_controller = this;
// Halt if device is a testimage/unknown channel.
if (chrome_device_params.chrome_channel == version_info::Channel::UNKNOWN) {
LOG(ERROR) << "Halt - Client should enter device active reporting logic. "
<< "Unknown and test image channels should not be counted as "
<< "legitimate device counts.";
return;
}
// Wrap with callback from |psm_device_active_secret_| retrieval using
// |SessionManagerClient| DBus.
SessionManagerClient::Get()->GetPsmDeviceActiveSecret(
base::BindOnce(&report::ReportController::OnPsmDeviceActiveSecretFetched,
weak_factory_.GetWeakPtr()));
}
ReportController::~ReportController() {
DCHECK_EQ(this, g_ash_report_controller);
g_ash_report_controller = nullptr;
// Reset all dependency unique_ptr objects of this class that are not needed.
report_timer_.reset();
system_clock_sync_observation_.reset();
// Reset all reporting use cases.
one_day_impl_.reset();
twenty_eight_day_impl_.reset();
cohort_impl_.reset();
observation_impl_.reset();
// Reset parameters that is passed to reporting use cases.
use_case_params_.reset();
}
void ReportController::DefaultNetworkChanged(const NetworkState* network) {
bool was_connected = network_connected_;
network_connected_ = network && network->IsOnline();
if (network_connected_ == was_connected) {
return;
}
if (network_connected_) {
OnNetworkOnline();
} else {
OnNetworkOffline();
}
}
void ReportController::OnShuttingDown() {
network_state_handler_observer_.Reset();
}
void ReportController::OnPsmDeviceActiveSecretFetched(
const std::string& psm_device_active_secret) {
// In order for the device actives to be reported, the psm device active
// secret must be retrieved successfully.
if (psm_device_active_secret.empty()) {
LOG(ERROR) << "PSM device secret is empty and could not be fetched.";
RecordIsPsmSecretSet(false);
return;
}
RecordIsPsmSecretSet(true);
high_entropy_seed_ = psm_device_active_secret;
// Continue when machine statistics are loaded, to avoid blocking.
statistics_provider_->ScheduleOnMachineStatisticsLoaded(
base::BindOnce(&report::ReportController::OnMachineStatisticsLoaded,
weak_factory_.GetWeakPtr()));
}
void ReportController::OnMachineStatisticsLoaded() {
// Block virtual machines and debug builds (dev mode enabled).
if (statistics_provider_->IsRunningOnVm() ||
statistics_provider_->IsCrosDebugMode()) {
LOG(ERROR) << "Terminate - device is running in VM or with cros_debug mode "
"enabled.";
LOG(ERROR) << "VM = " << statistics_provider_->IsRunningOnVm()
<< " and DEBUG = " << statistics_provider_->IsCrosDebugMode();
RecordIsTestDevice(true);
return;
}
RecordIsTestDevice(false);
// Send DBus method to that reads preserved files for missing local state
// prefs.
PrivateComputingClient::Get()->GetLastPingDatesStatus(
base::BindOnce(&ReportController::OnPreservedFileReadComplete,
weak_factory_.GetWeakPtr()));
}
void ReportController::OnPreservedFileReadComplete(
private_computing::GetStatusResponse response) {
if (response.has_error_message()) {
LOG(ERROR) << "Failed to read preserved file. "
<< "Error from DBus: " << response.error_message();
OnReadyToReport();
return;
}
utils::RestoreLocalStateWithPreservedFile(local_state_, response);
OnReadyToReport();
}
void ReportController::OnSaveLocalStateToPreservedFileComplete(
private_computing::SaveStatusResponse response) {
bool write_success = true;
if (response.has_error_message()) {
write_success = false;
LOG(ERROR) << "Failed to write to preserved file. "
<< "Error from DBus: " << response.error_message();
}
RecordPreservedFileWritten(write_success);
// Device is done reporting after writing to preserved file.
is_device_reporting_ = false;
// Reset all reporting use cases.
one_day_impl_.reset();
twenty_eight_day_impl_.reset();
cohort_impl_.reset();
observation_impl_.reset();
// Reset parameters that is passed to reporting use cases.
use_case_params_.reset();
}
bool ReportController::IsDeviceReportingForTesting() const {
return is_device_reporting_;
}
void ReportController::OnReadyToReport() {
// Retry reporting metrics every 1 hour.
report_timer_->Start(FROM_HERE, kTimeToRepeat, this,
&ReportController::ReportingTriggeredByTimer);
// Retry reporting metrics if network comes online.
// DefaultNetworkChanged method is called every time the
// device network properties change.
network_state_handler_observer_.Observe(network_state_handler_.get());
DefaultNetworkChanged(network_state_handler_->DefaultNetwork());
}
void ReportController::ReportingTriggeredByTimer() {
// Return early if the device is not connected to the network
// or if it is already in the middle of reporting.
if (!network_connected_ || is_device_reporting_) {
LOG(ERROR) << "Timer triggered reporting failed. "
<< "Network_connected = " << network_connected_
<< ". Is device reporting = " << is_device_reporting_ << ".";
return;
}
OnNetworkOnline();
}
void ReportController::OnNetworkOnline() {
// Async wait for the system clock to synchronize on network connection.
system_clock_sync_observation_ =
SystemClockSyncObservation::WaitForSystemClockSync(
SystemClockClient::Get(), kSystemClockSyncWaitTimeout,
base::BindOnce(&ReportController::OnSystemClockSyncResult,
weak_factory_.GetWeakPtr()));
}
void ReportController::OnNetworkOffline() {
// TODO: Evaluate if we should cancel callbacks here.
}
void ReportController::OnSystemClockSyncResult(bool system_clock_synchronized) {
RecordIsSystemClockSynced(system_clock_synchronized);
if (!system_clock_synchronized) {
LOG(ERROR) << "System clock failed to be synchronized.";
return;
}
StartReport();
}
void ReportController::StartReport() {
DCHECK(local_state_);
DCHECK(psm_client_manager_.get());
// Get new adjusted timestamp from GMT to Pacific Time.
active_ts_ = utils::ConvertGmtToPt(clock_);
// Create instances of use cases and parameters.
use_case_params_ = std::make_unique<device_metrics::UseCaseParameters>(
active_ts_, chrome_device_params_, url_loader_factory_,
high_entropy_seed_, local_state_, psm_client_manager_.get());
one_day_impl_ =
std::make_unique<device_metrics::OneDayImpl>(use_case_params_.get());
twenty_eight_day_impl_ = std::make_unique<device_metrics::TwentyEightDayImpl>(
use_case_params_.get());
cohort_impl_ =
std::make_unique<device_metrics::CohortImpl>(use_case_params_.get());
observation_impl_ =
std::make_unique<device_metrics::ObservationImpl>(use_case_params_.get());
// Create callbacks to report use cases in a specific order, and also a
// callback that updates the preserved file using the latest local state.
// Note that the order of the use case callbacks is important.
// Contact chromeos-data-eng@ before making changes here.
base::OnceClosure save_preserved_file_cb =
CreateSavePreservedFileCallback(local_state_, weak_factory_.GetWeakPtr());
base::OnceClosure report_observation_cb = CreateReportObservationCallback(
observation_impl_->GetWeakPtr(), std::move(save_preserved_file_cb));
base::OnceClosure report_cohort_cb = CreateReportCohortCallback(
cohort_impl_->GetWeakPtr(), std::move(report_observation_cb));
base::OnceClosure report_28da_cb = CreateReport28DaCallback(
twenty_eight_day_impl_->GetWeakPtr(), std::move(report_cohort_cb));
// Trigger sequential execution of callbacks by running first use case.
is_device_reporting_ = true;
one_day_impl_->Run(std::move(report_28da_cb));
}
} // namespace ash::report
|