1 // Copyright 2014 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ 6 #define COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ 7 8 #include <memory> 9 #include <string> 10 11 #include "base/callback_list.h" 12 #include "base/files/file_path.h" 13 #include "base/functional/callback.h" 14 #include "base/gtest_prod_util.h" 15 #include "base/memory/raw_ptr.h" 16 #include "base/metrics/field_trial.h" 17 #include "build/chromeos_buildflags.h" 18 #include "components/metrics/clean_exit_beacon.h" 19 #include "components/metrics/client_info.h" 20 #include "components/metrics/cloned_install_detector.h" 21 #include "components/metrics/entropy_state.h" 22 #include "components/variations/entropy_provider.h" 23 24 #if BUILDFLAG(IS_CHROMEOS_ASH) 25 #include "components/metrics/structured/neutrino_logging.h" // nogncheck 26 #endif // BUILDFLAG(IS_CHROMEOS_ASH) 27 28 class PrefService; 29 class PrefRegistrySimple; 30 31 namespace metrics { 32 33 class EnabledStateProvider; 34 class MetricsProvider; 35 36 // Denotes whether this session is a background or foreground session at 37 // startup. May be unknown. A background session refers to the situation in 38 // which the browser process starts; does some work, e.g. servicing a sync; and 39 // ends without ever becoming visible. Note that the point in startup at which 40 // this value is determined is likely before the UI is visible. 41 // 42 // These values are persisted to logs. Entries should not be renumbered and 43 // numeric values should never be reused. 44 enum class StartupVisibility { 45 kUnknown = 0, 46 kBackground = 1, 47 kForeground = 2, 48 kMaxValue = kForeground, 49 }; 50 51 // Denotes the type of EntropyProvider to use for default one-time 52 // randomization. 53 enum class EntropyProviderType { 54 kDefault = 0, // Enable high entropy randomization if possible. 55 kLow = 1, // Always use low entropy randomization. 56 }; 57 58 // Options to apply to trial randomization. 59 struct EntropyParams { 60 // The type of entropy to use for default one-time randomization. 61 EntropyProviderType default_entropy_provider_type = 62 EntropyProviderType::kDefault; 63 // Force trial randomization into benchmarking mode, which disables 64 // randomization. Users may also be put in this mode if the 65 // --enable_benchmarking command line flag is passed. 66 bool force_benchmarking_mode = false; 67 }; 68 69 // Responsible for managing MetricsService state prefs, specifically the UMA 70 // client id and low entropy source. Code outside the metrics directory should 71 // not be instantiating or using this class directly. 72 class MetricsStateManager final { 73 public: 74 // A callback that can be invoked to store client info to persistent storage. 75 // Storing an empty client_id will resulted in the backup being voided. 76 typedef base::RepeatingCallback<void(const ClientInfo& client_info)> 77 StoreClientInfoCallback; 78 79 // A callback that can be invoked to load client info stored through the 80 // StoreClientInfoCallback. 81 typedef base::RepeatingCallback<std::unique_ptr<ClientInfo>(void)> 82 LoadClientInfoCallback; 83 84 MetricsStateManager(const MetricsStateManager&) = delete; 85 MetricsStateManager& operator=(const MetricsStateManager&) = delete; 86 87 ~MetricsStateManager(); 88 89 std::unique_ptr<MetricsProvider> GetProvider(); 90 91 // Returns true if the user has consented to sending metric reports, and there 92 // is no other reason to disable reporting. One such reason is client 93 // sampling, and this client isn't in the sample. 94 bool IsMetricsReportingEnabled(); 95 96 // Returns true if Extended Variations Safe Mode is supported on this 97 // platform. Variations Safe Mode is a mechanism that allows Chrome to fall 98 // back to a "safe" seed so that clients can recover from a problematic 99 // experiment, for example, one that causes browser crashes. See the design 100 // doc for more details: 101 // https://docs.google.com/document/d/17UN2pLSa5JZqk8f3LeYZIftXewxqcITotgalTrJvGSY. 102 // 103 // Extended Variations Safe Mode builds on this by allowing clients to recover 104 // from problematic experiments that cause browser crashes earlier on in 105 // startup. 106 bool IsExtendedSafeModeSupported() const; 107 108 // Returns the client ID for this client, or the empty string if the user is 109 // not opted in to metrics reporting. client_id()110 const std::string& client_id() const { return client_id_; } 111 112 // Returns the low entropy source for this client. 113 int GetLowEntropySource(); 114 115 // The CleanExitBeacon, used to determine whether the previous Chrome browser 116 // session terminated gracefully. clean_exit_beacon()117 CleanExitBeacon* clean_exit_beacon() { return &clean_exit_beacon_; } clean_exit_beacon()118 const CleanExitBeacon* clean_exit_beacon() const { 119 return &clean_exit_beacon_; 120 } 121 122 // Returns true if the session was deemed a background session during startup. 123 // Note that this is not equivalent to !is_foreground_session() because the 124 // type of session may be unknown. is_background_session()125 bool is_background_session() const { 126 return startup_visibility_ == StartupVisibility::kBackground; 127 } 128 129 // Returns true if the session was deemed a foreground session during startup. 130 // Note that this is not equivalent to !is_background_session() because the 131 // type of session may be unknown. is_foreground_session()132 bool is_foreground_session() const { 133 return startup_visibility_ == StartupVisibility::kForeground; 134 } 135 136 // Instantiates the FieldTrialList. 137 // 138 // Side effect: Initializes |clean_exit_beacon_|. 139 void InstantiateFieldTrialList(); 140 141 // Signals whether the session has shutdown cleanly. Passing `false` for 142 // |has_session_shutdown_cleanly| means that Chrome has launched and has not 143 // yet shut down safely. Passing `true` signals that Chrome has shut down 144 // safely. 145 // 146 // Seeing a call with `false` without a matching call with `true` suggests 147 // that Chrome crashed or otherwise did not shut down cleanly, e.g. maybe the 148 // OS crashed. 149 // 150 // If |is_extended_safe_mode| is true, then |has_session_shutdown_cleanly| is 151 // written to disk synchronously. If false, a write is scheduled, and for 152 // clients in the Extended Variations Safe Mode experiment, a synchronous 153 // write is done, too. 154 // 155 // Note: |is_extended_safe_mode| should be true only for the Extended 156 // Variations Safe Mode experiment. 157 void LogHasSessionShutdownCleanly(bool has_session_shutdown_cleanly, 158 bool is_extended_safe_mode = false); 159 160 // Forces the client ID to be generated. This is useful in case it's needed 161 // before recording. 162 void ForceClientIdCreation(); 163 164 // Sets the external client id. Useful for callers that want explicit control 165 // of the next metrics client id. 166 void SetExternalClientId(const std::string& id); 167 168 // Checks if this install was cloned or imaged from another machine. If a 169 // clone is detected, resets the client id and low entropy source. This 170 // should not be called more than once. 171 void CheckForClonedInstall(); 172 173 // Checks if the cloned install detector says that client ids should be reset. 174 bool ShouldResetClientIdsOnClonedInstall(); 175 176 // Wrapper around ClonedInstallDetector::AddOnClonedInstallDetectedCallback(). 177 base::CallbackListSubscription AddOnClonedInstallDetectedCallback( 178 base::OnceClosure callback); 179 180 // Creates entropy providers for trial randomization. 181 // 182 // If this StateManager supports high entropy randomization, and there is 183 // either consent to report metrics or this is the first run of Chrome, 184 // this method returns an entropy provider that has a high source of entropy, 185 // partially based on the client ID or provisional client ID. Otherwise, it 186 // only returns an entropy provider that is based on a low entropy source. 187 std::unique_ptr<const variations::EntropyProviders> CreateEntropyProviders(); 188 cloned_install_detector_for_testing()189 ClonedInstallDetector* cloned_install_detector_for_testing() { 190 return &cloned_install_detector_; 191 } 192 193 // Creates the MetricsStateManager, enforcing that only a single instance 194 // of the class exists at a time. Returns nullptr if an instance exists 195 // already. 196 // 197 // On Windows, |backup_registry_key| is used to store a backup of the clean 198 // exit beacon. It is ignored on other platforms. 199 // 200 // |user_data_dir| is the path to the client's user data directory. If empty, 201 // a separate file will not be used for Variations Safe Mode prefs. 202 // 203 // |startup_visibility| denotes whether this session is expected to come to 204 // the foreground. 205 static std::unique_ptr<MetricsStateManager> Create( 206 PrefService* local_state, 207 EnabledStateProvider* enabled_state_provider, 208 const std::wstring& backup_registry_key, 209 const base::FilePath& user_data_dir, 210 StartupVisibility startup_visibility = StartupVisibility::kUnknown, 211 EntropyParams entropy_params = {}, 212 StoreClientInfoCallback store_client_info = StoreClientInfoCallback(), 213 LoadClientInfoCallback load_client_info = LoadClientInfoCallback(), 214 base::StringPiece external_client_id = base::StringPiece()); 215 216 // Registers local state prefs used by this class. 217 static void RegisterPrefs(PrefRegistrySimple* registry); 218 219 private: 220 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, CheckProviderResetIds); 221 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, CheckProviderLogNormal); 222 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, 223 CheckProviderLogNormalWithParams); 224 FRIEND_TEST_ALL_PREFIXES( 225 MetricsStateManagerTest, 226 CheckProviderResetIds_PreviousIdOnlyReportInResetSession); 227 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low); 228 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High); 229 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, 230 ProvisionalClientId_PromotedToClientId); 231 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, 232 ProvisionalClientId_PersistedAcrossFirstRuns); 233 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetBackup); 234 FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs); 235 236 // Designates which entropy source was returned from this class. 237 // This is used for testing to validate that we return the correct source 238 // depending on the state of the service. 239 enum EntropySourceType { 240 ENTROPY_SOURCE_NONE, 241 ENTROPY_SOURCE_LOW, 242 ENTROPY_SOURCE_HIGH, 243 ENTROPY_SOURCE_ENUM_SIZE, 244 }; 245 246 // These values are persisted to logs. Entries should not be renumbered and 247 // numerical values should never be reused. 248 enum class ClientIdSource { 249 // Recorded when the client ID in Local State matches the cached copy. 250 kClientIdMatches = 0, 251 // Recorded when we are somehow missing the cached client ID and we are 252 // able to recover it from the Local State. 253 kClientIdFromLocalState = 1, 254 // Recorded when we are somehow missing the client ID stored in Local State 255 // yet are able to recover it from a backup location. 256 kClientIdBackupRecovered = 2, 257 // Recorded when we are somehow missing the client ID in Local State, cache 258 // and backup and there is no provisional client ID, so a new client ID is 259 // generated. 260 kClientIdNew = 3, 261 // Recorded when we are somehow missing the client ID in Local State, cache 262 // and backup, so we promote the provisional client ID. 263 kClientIdFromProvisionalId = 4, 264 // Recorded when the client ID is passed in from external source. 265 // This is needed for Lacros since the client id is passed in from 266 // ash chrome. 267 kClientIdFromExternal = 5, 268 kMaxValue = kClientIdFromExternal, 269 }; 270 271 // Creates the MetricsStateManager with the given |local_state|. Uses 272 // |enabled_state_provider| to query whether there is consent for metrics 273 // reporting, and if it is enabled. Clients should instead use Create(), which 274 // enforces that a single instance of this class be alive at any given time. 275 // |store_client_info| should back up client info to persistent storage such 276 // that it is later retrievable by |load_client_info|. 277 MetricsStateManager(PrefService* local_state, 278 EnabledStateProvider* enabled_state_provider, 279 const std::wstring& backup_registry_key, 280 const base::FilePath& user_data_dir, 281 EntropyParams entropy_params, 282 StartupVisibility startup_visibility, 283 StoreClientInfoCallback store_client_info, 284 LoadClientInfoCallback load_client_info, 285 base::StringPiece external_client_id); 286 287 // Returns a MetricsStateManagerProvider instance and sets its 288 // |log_normal_metric_state_.gen| with the provided random seed. 289 std::unique_ptr<MetricsProvider> GetProviderAndSetRandomSeedForTesting( 290 int64_t seed); 291 292 // Backs up the current client info via |store_client_info_|. 293 void BackUpCurrentClientInfo(); 294 295 // Loads the client info via |load_client_info_|. 296 std::unique_ptr<ClientInfo> LoadClientInfo(); 297 298 // Returns the high entropy source for this client, which is composed of a 299 // client ID and the low entropy source. This is intended to be unique for 300 // each install. UMA must be enabled (and |client_id_| must be set) or 301 // |kMetricsProvisionalClientID| must be set before calling this. 302 std::string GetHighEntropySource(); 303 304 // Returns the old low entropy source for this client. 305 int GetOldLowEntropySource(); 306 307 // Updates |entropy_source_returned_| with |type| iff the current value is 308 // ENTROPY_SOURCE_NONE and logs the new value in a histogram. 309 void UpdateEntropySourceReturnedValue(EntropySourceType type); 310 311 // Returns the first entropy source that was returned by this service since 312 // start up, or NONE if neither was returned yet. This is exposed for testing 313 // only. entropy_source_returned()314 EntropySourceType entropy_source_returned() const { 315 return entropy_source_returned_; 316 } 317 initial_client_id_for_testing()318 std::string initial_client_id_for_testing() const { 319 return initial_client_id_; 320 } 321 322 // Reset the client id and low entropy source if the kMetricsResetMetricIDs 323 // pref is true. 324 void ResetMetricsIDsIfNecessary(); 325 326 bool ShouldGenerateProvisionalClientId(bool is_first_run); 327 328 #if BUILDFLAG(IS_CHROMEOS_ASH) 329 // Log to structured metrics when the client id is changed. 330 void LogClientIdChanged(metrics::structured::NeutrinoDevicesLocation location, 331 std::string previous_client_id); 332 #endif // BUILDFLAG(IS_CHROMEOS_ASH) 333 334 // Whether an instance of this class exists. Used to enforce that there aren't 335 // multiple instances of this class at a given time. 336 static bool instance_exists_; 337 338 // Weak pointer to the local state prefs store. 339 const raw_ptr<PrefService> local_state_; 340 341 // Weak pointer to an enabled state provider. Used to know whether the user 342 // has consented to reporting, and if reporting should be done. 343 raw_ptr<EnabledStateProvider, DanglingUntriaged> enabled_state_provider_; 344 345 // Specified options for controlling trial randomization. 346 const EntropyParams entropy_params_; 347 348 // A callback run during client id creation so this MetricsStateManager can 349 // store a backup of the newly generated ID. 350 const StoreClientInfoCallback store_client_info_; 351 352 // A callback run if this MetricsStateManager can't get the client id from 353 // its typical location and wants to attempt loading it from this backup. 354 const LoadClientInfoCallback load_client_info_; 355 356 // A beacon used to determine whether the previous Chrome browser session 357 // terminated gracefully. 358 CleanExitBeacon clean_exit_beacon_; 359 360 // The identifier that's sent to the server with the log reports. 361 std::string client_id_; 362 363 // The client id that was used do field trial randomization. This field should 364 // only be changed when we need to do group assignment. |initial_client_id| 365 // should left blank iff a client id was not used to do field trial 366 // randomization. 367 std::string initial_client_id_; 368 369 // If not empty, use an external client id passed in from another browser as 370 // |client_id_|. This is needed for the Lacros browser where client id needs 371 // be passed in from ash chrome. 372 std::string external_client_id_; 373 374 // An instance of EntropyState for getting the entropy source values. 375 EntropyState entropy_state_; 376 377 // The last entropy source returned by this service, used for testing. 378 EntropySourceType entropy_source_returned_; 379 380 // The value of prefs::kMetricsResetIds seen upon startup, i.e., the value 381 // that was appropriate in the previous session. Used when reporting previous 382 // session (stability) data. 383 bool metrics_ids_were_reset_; 384 385 // The value of the metrics id before reseting. Only possibly valid if the 386 // metrics id was reset. May be blank if the metrics id was reset but Chrome 387 // has no record of what the previous metrics id was. 388 std::string previous_client_id_; 389 390 // The detector for understanding the cloned nature of the install so that we 391 // can reset client ids. 392 ClonedInstallDetector cloned_install_detector_; 393 394 // The type of session, e.g. a foreground session, at startup. This value is 395 // used only during startup. On Android WebLayer, Android WebView, and iOS, 396 // the visibility is unknown at this point in startup. 397 const StartupVisibility startup_visibility_; 398 399 // Force enables the creation of a provisional client ID on first run even if 400 // this is not a Chrome-branded build. Used for testing. 401 static bool enable_provisional_client_id_for_testing_; 402 }; 403 404 } // namespace metrics 405 406 #endif // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ 407