1*6777b538SAndroid Build Coastguard Worker // Copyright 2014 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "components/metrics/clean_exit_beacon.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <algorithm>
8*6777b538SAndroid Build Coastguard Worker #include <memory>
9*6777b538SAndroid Build Coastguard Worker #include <utility>
10*6777b538SAndroid Build Coastguard Worker
11*6777b538SAndroid Build Coastguard Worker #include "base/check_op.h"
12*6777b538SAndroid Build Coastguard Worker #include "base/command_line.h"
13*6777b538SAndroid Build Coastguard Worker #include "base/files/file_util.h"
14*6777b538SAndroid Build Coastguard Worker #include "base/json/json_file_value_serializer.h"
15*6777b538SAndroid Build Coastguard Worker #include "base/json/json_string_value_serializer.h"
16*6777b538SAndroid Build Coastguard Worker #include "base/logging.h"
17*6777b538SAndroid Build Coastguard Worker #include "base/metrics/field_trial.h"
18*6777b538SAndroid Build Coastguard Worker #include "base/metrics/histogram_functions.h"
19*6777b538SAndroid Build Coastguard Worker #include "base/metrics/histogram_macros.h"
20*6777b538SAndroid Build Coastguard Worker #include "base/path_service.h"
21*6777b538SAndroid Build Coastguard Worker #include "base/strings/string_number_conversions.h"
22*6777b538SAndroid Build Coastguard Worker #include "base/strings/stringprintf.h"
23*6777b538SAndroid Build Coastguard Worker #include "base/threading/thread_restrictions.h"
24*6777b538SAndroid Build Coastguard Worker #include "build/build_config.h"
25*6777b538SAndroid Build Coastguard Worker #include "components/metrics/metrics_pref_names.h"
26*6777b538SAndroid Build Coastguard Worker #include "components/prefs/pref_registry_simple.h"
27*6777b538SAndroid Build Coastguard Worker #include "components/prefs/pref_service.h"
28*6777b538SAndroid Build Coastguard Worker #include "components/variations/pref_names.h"
29*6777b538SAndroid Build Coastguard Worker #include "components/variations/variations_switches.h"
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN)
32*6777b538SAndroid Build Coastguard Worker #include <windows.h>
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Worker #include "base/strings/string_util_win.h"
35*6777b538SAndroid Build Coastguard Worker #include "base/strings/utf_string_conversions.h"
36*6777b538SAndroid Build Coastguard Worker #include "base/win/registry.h"
37*6777b538SAndroid Build Coastguard Worker #endif
38*6777b538SAndroid Build Coastguard Worker
39*6777b538SAndroid Build Coastguard Worker namespace metrics {
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker namespace {
42*6777b538SAndroid Build Coastguard Worker
43*6777b538SAndroid Build Coastguard Worker using ::variations::prefs::kVariationsCrashStreak;
44*6777b538SAndroid Build Coastguard Worker
45*6777b538SAndroid Build Coastguard Worker // Denotes whether Chrome should perform clean shutdown steps: signaling that
46*6777b538SAndroid Build Coastguard Worker // Chrome is exiting cleanly and then CHECKing that is has shutdown cleanly.
47*6777b538SAndroid Build Coastguard Worker // This may be modified by SkipCleanShutdownStepsForTesting().
48*6777b538SAndroid Build Coastguard Worker bool g_skip_clean_shutdown_steps = false;
49*6777b538SAndroid Build Coastguard Worker
50*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
51*6777b538SAndroid Build Coastguard Worker // Records the the combined state of two distinct beacons' values in a
52*6777b538SAndroid Build Coastguard Worker // histogram.
RecordBeaconConsistency(std::optional<bool> beacon_file_beacon_value,std::optional<bool> platform_specific_beacon_value)53*6777b538SAndroid Build Coastguard Worker void RecordBeaconConsistency(
54*6777b538SAndroid Build Coastguard Worker std::optional<bool> beacon_file_beacon_value,
55*6777b538SAndroid Build Coastguard Worker std::optional<bool> platform_specific_beacon_value) {
56*6777b538SAndroid Build Coastguard Worker CleanExitBeaconConsistency consistency =
57*6777b538SAndroid Build Coastguard Worker CleanExitBeaconConsistency::kDirtyDirty;
58*6777b538SAndroid Build Coastguard Worker
59*6777b538SAndroid Build Coastguard Worker if (!beacon_file_beacon_value) {
60*6777b538SAndroid Build Coastguard Worker if (!platform_specific_beacon_value) {
61*6777b538SAndroid Build Coastguard Worker consistency = CleanExitBeaconConsistency::kMissingMissing;
62*6777b538SAndroid Build Coastguard Worker } else {
63*6777b538SAndroid Build Coastguard Worker consistency = platform_specific_beacon_value.value()
64*6777b538SAndroid Build Coastguard Worker ? CleanExitBeaconConsistency::kMissingClean
65*6777b538SAndroid Build Coastguard Worker : CleanExitBeaconConsistency::kMissingDirty;
66*6777b538SAndroid Build Coastguard Worker }
67*6777b538SAndroid Build Coastguard Worker } else if (!platform_specific_beacon_value) {
68*6777b538SAndroid Build Coastguard Worker consistency = beacon_file_beacon_value.value()
69*6777b538SAndroid Build Coastguard Worker ? CleanExitBeaconConsistency::kCleanMissing
70*6777b538SAndroid Build Coastguard Worker : CleanExitBeaconConsistency::kDirtyMissing;
71*6777b538SAndroid Build Coastguard Worker } else if (beacon_file_beacon_value.value()) {
72*6777b538SAndroid Build Coastguard Worker consistency = platform_specific_beacon_value.value()
73*6777b538SAndroid Build Coastguard Worker ? CleanExitBeaconConsistency::kCleanClean
74*6777b538SAndroid Build Coastguard Worker : CleanExitBeaconConsistency::kCleanDirty;
75*6777b538SAndroid Build Coastguard Worker } else {
76*6777b538SAndroid Build Coastguard Worker consistency = platform_specific_beacon_value.value()
77*6777b538SAndroid Build Coastguard Worker ? CleanExitBeaconConsistency::kDirtyClean
78*6777b538SAndroid Build Coastguard Worker : CleanExitBeaconConsistency::kDirtyDirty;
79*6777b538SAndroid Build Coastguard Worker }
80*6777b538SAndroid Build Coastguard Worker base::UmaHistogramEnumeration("UMA.CleanExitBeaconConsistency3", consistency);
81*6777b538SAndroid Build Coastguard Worker }
82*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
83*6777b538SAndroid Build Coastguard Worker
84*6777b538SAndroid Build Coastguard Worker // Increments kVariationsCrashStreak if |did_previous_session_exit_cleanly| is
85*6777b538SAndroid Build Coastguard Worker // false. Also, emits the crash streak to a histogram.
86*6777b538SAndroid Build Coastguard Worker //
87*6777b538SAndroid Build Coastguard Worker // If |beacon_file_contents| are given, then the beacon file is used to retrieve
88*6777b538SAndroid Build Coastguard Worker // the crash streak. Otherwise, |local_state| is used.
MaybeIncrementCrashStreak(bool did_previous_session_exit_cleanly,base::Value * beacon_file_contents,PrefService * local_state)89*6777b538SAndroid Build Coastguard Worker void MaybeIncrementCrashStreak(bool did_previous_session_exit_cleanly,
90*6777b538SAndroid Build Coastguard Worker base::Value* beacon_file_contents,
91*6777b538SAndroid Build Coastguard Worker PrefService* local_state) {
92*6777b538SAndroid Build Coastguard Worker int num_crashes;
93*6777b538SAndroid Build Coastguard Worker if (beacon_file_contents) {
94*6777b538SAndroid Build Coastguard Worker std::optional<int> crash_streak =
95*6777b538SAndroid Build Coastguard Worker beacon_file_contents->GetDict().FindInt(kVariationsCrashStreak);
96*6777b538SAndroid Build Coastguard Worker // Any contents without the key should have been rejected by
97*6777b538SAndroid Build Coastguard Worker // MaybeGetFileContents().
98*6777b538SAndroid Build Coastguard Worker DCHECK(crash_streak);
99*6777b538SAndroid Build Coastguard Worker num_crashes = crash_streak.value();
100*6777b538SAndroid Build Coastguard Worker } else {
101*6777b538SAndroid Build Coastguard Worker // TODO(crbug.com/40850830): Consider not falling back to Local State for
102*6777b538SAndroid Build Coastguard Worker // clients on platforms that support the beacon file.
103*6777b538SAndroid Build Coastguard Worker num_crashes = local_state->GetInteger(kVariationsCrashStreak);
104*6777b538SAndroid Build Coastguard Worker }
105*6777b538SAndroid Build Coastguard Worker
106*6777b538SAndroid Build Coastguard Worker if (!did_previous_session_exit_cleanly) {
107*6777b538SAndroid Build Coastguard Worker // Increment the crash streak if the previous session crashed. Note that the
108*6777b538SAndroid Build Coastguard Worker // streak is not cleared if the previous run didn’t crash. Instead, it’s
109*6777b538SAndroid Build Coastguard Worker // incremented on each crash until Chrome is able to successfully fetch a
110*6777b538SAndroid Build Coastguard Worker // new seed. This way, a seed update that mostly destabilizes Chrome still
111*6777b538SAndroid Build Coastguard Worker // results in a fallback to Variations Safe Mode.
112*6777b538SAndroid Build Coastguard Worker //
113*6777b538SAndroid Build Coastguard Worker // The crash streak is incremented here rather than in a variations-related
114*6777b538SAndroid Build Coastguard Worker // class for two reasons. First, the crash streak depends on whether Chrome
115*6777b538SAndroid Build Coastguard Worker // exited cleanly in the last session, which is first checked via
116*6777b538SAndroid Build Coastguard Worker // CleanExitBeacon::Initialize(). Second, if the crash streak were updated
117*6777b538SAndroid Build Coastguard Worker // in another function, any crash between beacon initialization and the
118*6777b538SAndroid Build Coastguard Worker // other function might cause the crash streak to not be to incremented.
119*6777b538SAndroid Build Coastguard Worker // "Might" because the updated crash streak also needs to be persisted to
120*6777b538SAndroid Build Coastguard Worker // disk. A consequence of failing to increment the crash streak is that
121*6777b538SAndroid Build Coastguard Worker // Chrome might undercount or be completely unaware of repeated crashes
122*6777b538SAndroid Build Coastguard Worker // early on in startup.
123*6777b538SAndroid Build Coastguard Worker ++num_crashes;
124*6777b538SAndroid Build Coastguard Worker // For platforms that use the beacon file, the crash streak is written
125*6777b538SAndroid Build Coastguard Worker // synchronously to disk later on in startup via
126*6777b538SAndroid Build Coastguard Worker // MaybeExtendVariationsSafeMode() and WriteBeaconFile(). The crash streak
127*6777b538SAndroid Build Coastguard Worker // is intentionally not written to the beacon file here. If the beacon file
128*6777b538SAndroid Build Coastguard Worker // indicates that Chrome failed to exit cleanly, then Chrome got at
129*6777b538SAndroid Build Coastguard Worker // least as far as MaybeExtendVariationsSafeMode(), which is during the
130*6777b538SAndroid Build Coastguard Worker // PostEarlyInitialization stage when native code is being synchronously
131*6777b538SAndroid Build Coastguard Worker // executed. Chrome should also be able to reach that point in this session.
132*6777b538SAndroid Build Coastguard Worker //
133*6777b538SAndroid Build Coastguard Worker // For platforms that do not use the beacon file, the crash streak is
134*6777b538SAndroid Build Coastguard Worker // scheduled to be written to disk later on in startup. At the latest, this
135*6777b538SAndroid Build Coastguard Worker // is done when a Local State write is scheduled via WriteBeaconFile(). A
136*6777b538SAndroid Build Coastguard Worker // write is not scheduled here for three reasons.
137*6777b538SAndroid Build Coastguard Worker //
138*6777b538SAndroid Build Coastguard Worker // 1. It is an expensive operation.
139*6777b538SAndroid Build Coastguard Worker // 2. Android WebLayer (one of the two platforms that does not use the
140*6777b538SAndroid Build Coastguard Worker // beacon file) did not appear to benefit from scheduling the write. See
141*6777b538SAndroid Build Coastguard Worker // crbug/1341850 for details.
142*6777b538SAndroid Build Coastguard Worker // 3. Android WebView (the other beacon-file-less platform) has its own
143*6777b538SAndroid Build Coastguard Worker // Variations Safe Mode mechanism and does not need the crash streak.
144*6777b538SAndroid Build Coastguard Worker local_state->SetInteger(kVariationsCrashStreak, num_crashes);
145*6777b538SAndroid Build Coastguard Worker }
146*6777b538SAndroid Build Coastguard Worker base::UmaHistogramSparse("Variations.SafeMode.Streak.Crashes",
147*6777b538SAndroid Build Coastguard Worker std::clamp(num_crashes, 0, 100));
148*6777b538SAndroid Build Coastguard Worker }
149*6777b538SAndroid Build Coastguard Worker
150*6777b538SAndroid Build Coastguard Worker // Records |file_state| in a histogram.
RecordBeaconFileState(BeaconFileState file_state)151*6777b538SAndroid Build Coastguard Worker void RecordBeaconFileState(BeaconFileState file_state) {
152*6777b538SAndroid Build Coastguard Worker base::UmaHistogramEnumeration(
153*6777b538SAndroid Build Coastguard Worker "Variations.ExtendedSafeMode.BeaconFileStateAtStartup", file_state);
154*6777b538SAndroid Build Coastguard Worker }
155*6777b538SAndroid Build Coastguard Worker
156*6777b538SAndroid Build Coastguard Worker // Returns the contents of the file at |beacon_file_path| if the following
157*6777b538SAndroid Build Coastguard Worker // conditions are all true. Otherwise, returns nullptr.
158*6777b538SAndroid Build Coastguard Worker //
159*6777b538SAndroid Build Coastguard Worker // 1. The file path is non-empty.
160*6777b538SAndroid Build Coastguard Worker // 2. The file exists.
161*6777b538SAndroid Build Coastguard Worker // 3. The file is successfully read.
162*6777b538SAndroid Build Coastguard Worker // 4. The file contents are in the expected format with the expected info.
163*6777b538SAndroid Build Coastguard Worker //
164*6777b538SAndroid Build Coastguard Worker // The file may not exist for the below reasons:
165*6777b538SAndroid Build Coastguard Worker //
166*6777b538SAndroid Build Coastguard Worker // 1. The file is unsupported on the platform.
167*6777b538SAndroid Build Coastguard Worker // 2. This is the first session after a client updates to or installs a Chrome
168*6777b538SAndroid Build Coastguard Worker // version that uses the beacon file. The beacon file launched on desktop
169*6777b538SAndroid Build Coastguard Worker // and iOS in M102 and on Android Chrome in M103.
170*6777b538SAndroid Build Coastguard Worker // 3. Android Chrome clients with only background sessions may never write a
171*6777b538SAndroid Build Coastguard Worker // beacon file.
172*6777b538SAndroid Build Coastguard Worker // 4. A user may delete the file.
MaybeGetFileContents(const base::FilePath & beacon_file_path)173*6777b538SAndroid Build Coastguard Worker std::unique_ptr<base::Value> MaybeGetFileContents(
174*6777b538SAndroid Build Coastguard Worker const base::FilePath& beacon_file_path) {
175*6777b538SAndroid Build Coastguard Worker if (beacon_file_path.empty())
176*6777b538SAndroid Build Coastguard Worker return nullptr;
177*6777b538SAndroid Build Coastguard Worker
178*6777b538SAndroid Build Coastguard Worker int error_code;
179*6777b538SAndroid Build Coastguard Worker JSONFileValueDeserializer deserializer(beacon_file_path);
180*6777b538SAndroid Build Coastguard Worker std::unique_ptr<base::Value> beacon_file_contents =
181*6777b538SAndroid Build Coastguard Worker deserializer.Deserialize(&error_code, /*error_message=*/nullptr);
182*6777b538SAndroid Build Coastguard Worker
183*6777b538SAndroid Build Coastguard Worker if (!beacon_file_contents) {
184*6777b538SAndroid Build Coastguard Worker RecordBeaconFileState(BeaconFileState::kNotDeserializable);
185*6777b538SAndroid Build Coastguard Worker base::UmaHistogramSparse(
186*6777b538SAndroid Build Coastguard Worker "Variations.ExtendedSafeMode.BeaconFileDeserializationError",
187*6777b538SAndroid Build Coastguard Worker error_code);
188*6777b538SAndroid Build Coastguard Worker return nullptr;
189*6777b538SAndroid Build Coastguard Worker }
190*6777b538SAndroid Build Coastguard Worker if (!beacon_file_contents->is_dict() ||
191*6777b538SAndroid Build Coastguard Worker beacon_file_contents->GetDict().empty()) {
192*6777b538SAndroid Build Coastguard Worker RecordBeaconFileState(BeaconFileState::kMissingDictionary);
193*6777b538SAndroid Build Coastguard Worker return nullptr;
194*6777b538SAndroid Build Coastguard Worker }
195*6777b538SAndroid Build Coastguard Worker const base::Value::Dict& beacon_dict = beacon_file_contents->GetDict();
196*6777b538SAndroid Build Coastguard Worker if (!beacon_dict.FindInt(kVariationsCrashStreak)) {
197*6777b538SAndroid Build Coastguard Worker RecordBeaconFileState(BeaconFileState::kMissingCrashStreak);
198*6777b538SAndroid Build Coastguard Worker return nullptr;
199*6777b538SAndroid Build Coastguard Worker }
200*6777b538SAndroid Build Coastguard Worker if (!beacon_dict.FindBool(prefs::kStabilityExitedCleanly)) {
201*6777b538SAndroid Build Coastguard Worker RecordBeaconFileState(BeaconFileState::kMissingBeacon);
202*6777b538SAndroid Build Coastguard Worker return nullptr;
203*6777b538SAndroid Build Coastguard Worker }
204*6777b538SAndroid Build Coastguard Worker RecordBeaconFileState(BeaconFileState::kReadable);
205*6777b538SAndroid Build Coastguard Worker return beacon_file_contents;
206*6777b538SAndroid Build Coastguard Worker }
207*6777b538SAndroid Build Coastguard Worker
208*6777b538SAndroid Build Coastguard Worker } // namespace
209*6777b538SAndroid Build Coastguard Worker
210*6777b538SAndroid Build Coastguard Worker const base::FilePath::CharType kCleanExitBeaconFilename[] =
211*6777b538SAndroid Build Coastguard Worker FILE_PATH_LITERAL("Variations");
212*6777b538SAndroid Build Coastguard Worker
CleanExitBeacon(const std::wstring & backup_registry_key,const base::FilePath & user_data_dir,PrefService * local_state)213*6777b538SAndroid Build Coastguard Worker CleanExitBeacon::CleanExitBeacon(const std::wstring& backup_registry_key,
214*6777b538SAndroid Build Coastguard Worker const base::FilePath& user_data_dir,
215*6777b538SAndroid Build Coastguard Worker PrefService* local_state)
216*6777b538SAndroid Build Coastguard Worker : backup_registry_key_(backup_registry_key),
217*6777b538SAndroid Build Coastguard Worker user_data_dir_(user_data_dir),
218*6777b538SAndroid Build Coastguard Worker local_state_(local_state),
219*6777b538SAndroid Build Coastguard Worker initial_browser_last_live_timestamp_(
220*6777b538SAndroid Build Coastguard Worker local_state->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)) {
221*6777b538SAndroid Build Coastguard Worker DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING,
222*6777b538SAndroid Build Coastguard Worker local_state_->GetInitializationStatus());
223*6777b538SAndroid Build Coastguard Worker }
224*6777b538SAndroid Build Coastguard Worker
Initialize()225*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::Initialize() {
226*6777b538SAndroid Build Coastguard Worker DCHECK(!initialized_);
227*6777b538SAndroid Build Coastguard Worker
228*6777b538SAndroid Build Coastguard Worker if (!user_data_dir_.empty()) {
229*6777b538SAndroid Build Coastguard Worker // Platforms that pass an empty path do so deliberately. They should not
230*6777b538SAndroid Build Coastguard Worker // use the beacon file.
231*6777b538SAndroid Build Coastguard Worker beacon_file_path_ = user_data_dir_.Append(kCleanExitBeaconFilename);
232*6777b538SAndroid Build Coastguard Worker }
233*6777b538SAndroid Build Coastguard Worker
234*6777b538SAndroid Build Coastguard Worker std::unique_ptr<base::Value> beacon_file_contents =
235*6777b538SAndroid Build Coastguard Worker MaybeGetFileContents(beacon_file_path_);
236*6777b538SAndroid Build Coastguard Worker
237*6777b538SAndroid Build Coastguard Worker did_previous_session_exit_cleanly_ =
238*6777b538SAndroid Build Coastguard Worker DidPreviousSessionExitCleanly(beacon_file_contents.get());
239*6777b538SAndroid Build Coastguard Worker
240*6777b538SAndroid Build Coastguard Worker MaybeIncrementCrashStreak(did_previous_session_exit_cleanly_,
241*6777b538SAndroid Build Coastguard Worker beacon_file_contents.get(), local_state_);
242*6777b538SAndroid Build Coastguard Worker initialized_ = true;
243*6777b538SAndroid Build Coastguard Worker }
244*6777b538SAndroid Build Coastguard Worker
DidPreviousSessionExitCleanly(base::Value * beacon_file_contents)245*6777b538SAndroid Build Coastguard Worker bool CleanExitBeacon::DidPreviousSessionExitCleanly(
246*6777b538SAndroid Build Coastguard Worker base::Value* beacon_file_contents) {
247*6777b538SAndroid Build Coastguard Worker if (!IsBeaconFileSupported())
248*6777b538SAndroid Build Coastguard Worker return local_state_->GetBoolean(prefs::kStabilityExitedCleanly);
249*6777b538SAndroid Build Coastguard Worker
250*6777b538SAndroid Build Coastguard Worker std::optional<bool> beacon_file_beacon_value =
251*6777b538SAndroid Build Coastguard Worker beacon_file_contents ? beacon_file_contents->GetDict().FindBool(
252*6777b538SAndroid Build Coastguard Worker prefs::kStabilityExitedCleanly)
253*6777b538SAndroid Build Coastguard Worker : std::nullopt;
254*6777b538SAndroid Build Coastguard Worker
255*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
256*6777b538SAndroid Build Coastguard Worker std::optional<bool> backup_beacon_value = ExitedCleanly();
257*6777b538SAndroid Build Coastguard Worker RecordBeaconConsistency(beacon_file_beacon_value, backup_beacon_value);
258*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
259*6777b538SAndroid Build Coastguard Worker
260*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_IOS)
261*6777b538SAndroid Build Coastguard Worker // TODO(crbug.com/40190558): For the time being, this is a no-op; i.e.,
262*6777b538SAndroid Build Coastguard Worker // ShouldUseUserDefaultsBeacon() always returns false.
263*6777b538SAndroid Build Coastguard Worker if (ShouldUseUserDefaultsBeacon())
264*6777b538SAndroid Build Coastguard Worker return backup_beacon_value.value_or(true);
265*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_IOS)
266*6777b538SAndroid Build Coastguard Worker
267*6777b538SAndroid Build Coastguard Worker return beacon_file_beacon_value.value_or(true);
268*6777b538SAndroid Build Coastguard Worker }
269*6777b538SAndroid Build Coastguard Worker
IsExtendedSafeModeSupported() const270*6777b538SAndroid Build Coastguard Worker bool CleanExitBeacon::IsExtendedSafeModeSupported() const {
271*6777b538SAndroid Build Coastguard Worker // All platforms that support the beacon file mechanism also happen to support
272*6777b538SAndroid Build Coastguard Worker // Extended Variations Safe Mode.
273*6777b538SAndroid Build Coastguard Worker return IsBeaconFileSupported();
274*6777b538SAndroid Build Coastguard Worker }
275*6777b538SAndroid Build Coastguard Worker
WriteBeaconValue(bool exited_cleanly,bool is_extended_safe_mode)276*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::WriteBeaconValue(bool exited_cleanly,
277*6777b538SAndroid Build Coastguard Worker bool is_extended_safe_mode) {
278*6777b538SAndroid Build Coastguard Worker DCHECK(initialized_);
279*6777b538SAndroid Build Coastguard Worker if (g_skip_clean_shutdown_steps)
280*6777b538SAndroid Build Coastguard Worker return;
281*6777b538SAndroid Build Coastguard Worker
282*6777b538SAndroid Build Coastguard Worker UpdateLastLiveTimestamp();
283*6777b538SAndroid Build Coastguard Worker
284*6777b538SAndroid Build Coastguard Worker if (has_exited_cleanly_ && has_exited_cleanly_.value() == exited_cleanly) {
285*6777b538SAndroid Build Coastguard Worker // It is possible to call WriteBeaconValue() with the same value for
286*6777b538SAndroid Build Coastguard Worker // |exited_cleanly| twice during startup and shutdown on some platforms. If
287*6777b538SAndroid Build Coastguard Worker // the current beacon value matches |exited_cleanly|, then return here to
288*6777b538SAndroid Build Coastguard Worker // skip redundantly updating Local State, writing a beacon file, and on
289*6777b538SAndroid Build Coastguard Worker // Windows and iOS, writing to platform-specific locations.
290*6777b538SAndroid Build Coastguard Worker return;
291*6777b538SAndroid Build Coastguard Worker }
292*6777b538SAndroid Build Coastguard Worker
293*6777b538SAndroid Build Coastguard Worker if (is_extended_safe_mode) {
294*6777b538SAndroid Build Coastguard Worker // |is_extended_safe_mode| can be true for only some platforms.
295*6777b538SAndroid Build Coastguard Worker DCHECK(IsExtendedSafeModeSupported());
296*6777b538SAndroid Build Coastguard Worker // |has_exited_cleanly_| should always be unset before starting to watch for
297*6777b538SAndroid Build Coastguard Worker // browser crashes.
298*6777b538SAndroid Build Coastguard Worker DCHECK(!has_exited_cleanly_);
299*6777b538SAndroid Build Coastguard Worker // When starting to watch for browser crashes in the code covered by
300*6777b538SAndroid Build Coastguard Worker // Extended Variations Safe Mode, the only valid value for |exited_cleanly|
301*6777b538SAndroid Build Coastguard Worker // is `false`. `true` signals that Chrome should stop watching for crashes.
302*6777b538SAndroid Build Coastguard Worker DCHECK(!exited_cleanly);
303*6777b538SAndroid Build Coastguard Worker WriteBeaconFile(exited_cleanly);
304*6777b538SAndroid Build Coastguard Worker } else {
305*6777b538SAndroid Build Coastguard Worker // TODO(crbug.com/40851383): Stop updating |kStabilityExitedCleanly| on
306*6777b538SAndroid Build Coastguard Worker // platforms that support the beacon file.
307*6777b538SAndroid Build Coastguard Worker local_state_->SetBoolean(prefs::kStabilityExitedCleanly, exited_cleanly);
308*6777b538SAndroid Build Coastguard Worker if (IsBeaconFileSupported()) {
309*6777b538SAndroid Build Coastguard Worker WriteBeaconFile(exited_cleanly);
310*6777b538SAndroid Build Coastguard Worker } else {
311*6777b538SAndroid Build Coastguard Worker // Schedule a Local State write on platforms that back the beacon value
312*6777b538SAndroid Build Coastguard Worker // using Local State rather than the beacon file.
313*6777b538SAndroid Build Coastguard Worker local_state_->CommitPendingWrite();
314*6777b538SAndroid Build Coastguard Worker }
315*6777b538SAndroid Build Coastguard Worker }
316*6777b538SAndroid Build Coastguard Worker
317*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN)
318*6777b538SAndroid Build Coastguard Worker base::win::RegKey regkey;
319*6777b538SAndroid Build Coastguard Worker if (regkey.Create(HKEY_CURRENT_USER, backup_registry_key_.c_str(),
320*6777b538SAndroid Build Coastguard Worker KEY_ALL_ACCESS) == ERROR_SUCCESS) {
321*6777b538SAndroid Build Coastguard Worker regkey.WriteValue(base::ASCIIToWide(prefs::kStabilityExitedCleanly).c_str(),
322*6777b538SAndroid Build Coastguard Worker exited_cleanly ? 1u : 0u);
323*6777b538SAndroid Build Coastguard Worker }
324*6777b538SAndroid Build Coastguard Worker #elif BUILDFLAG(IS_IOS)
325*6777b538SAndroid Build Coastguard Worker SetUserDefaultsBeacon(exited_cleanly);
326*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_WIN)
327*6777b538SAndroid Build Coastguard Worker
328*6777b538SAndroid Build Coastguard Worker has_exited_cleanly_ = std::make_optional(exited_cleanly);
329*6777b538SAndroid Build Coastguard Worker }
330*6777b538SAndroid Build Coastguard Worker
331*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
ExitedCleanly()332*6777b538SAndroid Build Coastguard Worker std::optional<bool> CleanExitBeacon::ExitedCleanly() {
333*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_WIN)
334*6777b538SAndroid Build Coastguard Worker base::win::RegKey regkey;
335*6777b538SAndroid Build Coastguard Worker DWORD value = 0u;
336*6777b538SAndroid Build Coastguard Worker if (regkey.Open(HKEY_CURRENT_USER, backup_registry_key_.c_str(),
337*6777b538SAndroid Build Coastguard Worker KEY_ALL_ACCESS) == ERROR_SUCCESS &&
338*6777b538SAndroid Build Coastguard Worker regkey.ReadValueDW(
339*6777b538SAndroid Build Coastguard Worker base::ASCIIToWide(prefs::kStabilityExitedCleanly).c_str(), &value) ==
340*6777b538SAndroid Build Coastguard Worker ERROR_SUCCESS) {
341*6777b538SAndroid Build Coastguard Worker return value ? true : false;
342*6777b538SAndroid Build Coastguard Worker }
343*6777b538SAndroid Build Coastguard Worker return std::nullopt;
344*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_WIN)
345*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_IOS)
346*6777b538SAndroid Build Coastguard Worker if (HasUserDefaultsBeacon())
347*6777b538SAndroid Build Coastguard Worker return GetUserDefaultsBeacon();
348*6777b538SAndroid Build Coastguard Worker return std::nullopt;
349*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_IOS)
350*6777b538SAndroid Build Coastguard Worker }
351*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
352*6777b538SAndroid Build Coastguard Worker
UpdateLastLiveTimestamp()353*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::UpdateLastLiveTimestamp() {
354*6777b538SAndroid Build Coastguard Worker local_state_->SetTime(prefs::kStabilityBrowserLastLiveTimeStamp,
355*6777b538SAndroid Build Coastguard Worker base::Time::Now());
356*6777b538SAndroid Build Coastguard Worker }
357*6777b538SAndroid Build Coastguard Worker
GetUserDataDirForTesting() const358*6777b538SAndroid Build Coastguard Worker const base::FilePath CleanExitBeacon::GetUserDataDirForTesting() const {
359*6777b538SAndroid Build Coastguard Worker return user_data_dir_;
360*6777b538SAndroid Build Coastguard Worker }
361*6777b538SAndroid Build Coastguard Worker
GetBeaconFilePathForTesting() const362*6777b538SAndroid Build Coastguard Worker base::FilePath CleanExitBeacon::GetBeaconFilePathForTesting() const {
363*6777b538SAndroid Build Coastguard Worker return beacon_file_path_;
364*6777b538SAndroid Build Coastguard Worker }
365*6777b538SAndroid Build Coastguard Worker
366*6777b538SAndroid Build Coastguard Worker // static
RegisterPrefs(PrefRegistrySimple * registry)367*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::RegisterPrefs(PrefRegistrySimple* registry) {
368*6777b538SAndroid Build Coastguard Worker registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true);
369*6777b538SAndroid Build Coastguard Worker
370*6777b538SAndroid Build Coastguard Worker registry->RegisterTimePref(prefs::kStabilityBrowserLastLiveTimeStamp,
371*6777b538SAndroid Build Coastguard Worker base::Time(), PrefRegistry::LOSSY_PREF);
372*6777b538SAndroid Build Coastguard Worker
373*6777b538SAndroid Build Coastguard Worker // This Variations-Safe-Mode-related pref is registered here rather than in
374*6777b538SAndroid Build Coastguard Worker // SafeSeedManager::RegisterPrefs() because the CleanExitBeacon is
375*6777b538SAndroid Build Coastguard Worker // responsible for incrementing this value. (See the comments in
376*6777b538SAndroid Build Coastguard Worker // MaybeIncrementCrashStreak() for more details.)
377*6777b538SAndroid Build Coastguard Worker registry->RegisterIntegerPref(kVariationsCrashStreak, 0);
378*6777b538SAndroid Build Coastguard Worker }
379*6777b538SAndroid Build Coastguard Worker
380*6777b538SAndroid Build Coastguard Worker // static
EnsureCleanShutdown(PrefService * local_state)381*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::EnsureCleanShutdown(PrefService* local_state) {
382*6777b538SAndroid Build Coastguard Worker if (!g_skip_clean_shutdown_steps)
383*6777b538SAndroid Build Coastguard Worker CHECK(local_state->GetBoolean(prefs::kStabilityExitedCleanly));
384*6777b538SAndroid Build Coastguard Worker }
385*6777b538SAndroid Build Coastguard Worker
386*6777b538SAndroid Build Coastguard Worker // static
SetStabilityExitedCleanlyForTesting(PrefService * local_state,bool exited_cleanly)387*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::SetStabilityExitedCleanlyForTesting(
388*6777b538SAndroid Build Coastguard Worker PrefService* local_state,
389*6777b538SAndroid Build Coastguard Worker bool exited_cleanly) {
390*6777b538SAndroid Build Coastguard Worker local_state->SetBoolean(prefs::kStabilityExitedCleanly, exited_cleanly);
391*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_IOS)
392*6777b538SAndroid Build Coastguard Worker SetUserDefaultsBeacon(exited_cleanly);
393*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_IOS)
394*6777b538SAndroid Build Coastguard Worker }
395*6777b538SAndroid Build Coastguard Worker
396*6777b538SAndroid Build Coastguard Worker // static
CreateBeaconFileContentsForTesting(bool exited_cleanly,int crash_streak)397*6777b538SAndroid Build Coastguard Worker std::string CleanExitBeacon::CreateBeaconFileContentsForTesting(
398*6777b538SAndroid Build Coastguard Worker bool exited_cleanly,
399*6777b538SAndroid Build Coastguard Worker int crash_streak) {
400*6777b538SAndroid Build Coastguard Worker const std::string exited_cleanly_str = exited_cleanly ? "true" : "false";
401*6777b538SAndroid Build Coastguard Worker return base::StringPrintf(
402*6777b538SAndroid Build Coastguard Worker "{\n"
403*6777b538SAndroid Build Coastguard Worker " \"user_experience_metrics.stability.exited_cleanly\":%s,\n"
404*6777b538SAndroid Build Coastguard Worker " \"variations_crash_streak\":%s\n"
405*6777b538SAndroid Build Coastguard Worker "}",
406*6777b538SAndroid Build Coastguard Worker exited_cleanly_str.data(), base::NumberToString(crash_streak).data());
407*6777b538SAndroid Build Coastguard Worker }
408*6777b538SAndroid Build Coastguard Worker
409*6777b538SAndroid Build Coastguard Worker // static
ResetStabilityExitedCleanlyForTesting(PrefService * local_state)410*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::ResetStabilityExitedCleanlyForTesting(
411*6777b538SAndroid Build Coastguard Worker PrefService* local_state) {
412*6777b538SAndroid Build Coastguard Worker local_state->ClearPref(prefs::kStabilityExitedCleanly);
413*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_IOS)
414*6777b538SAndroid Build Coastguard Worker ResetUserDefaultsBeacon();
415*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_IOS)
416*6777b538SAndroid Build Coastguard Worker }
417*6777b538SAndroid Build Coastguard Worker
418*6777b538SAndroid Build Coastguard Worker // static
SkipCleanShutdownStepsForTesting()419*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::SkipCleanShutdownStepsForTesting() {
420*6777b538SAndroid Build Coastguard Worker g_skip_clean_shutdown_steps = true;
421*6777b538SAndroid Build Coastguard Worker }
422*6777b538SAndroid Build Coastguard Worker
IsBeaconFileSupported() const423*6777b538SAndroid Build Coastguard Worker bool CleanExitBeacon::IsBeaconFileSupported() const {
424*6777b538SAndroid Build Coastguard Worker return !beacon_file_path_.empty();
425*6777b538SAndroid Build Coastguard Worker }
426*6777b538SAndroid Build Coastguard Worker
WriteBeaconFile(bool exited_cleanly) const427*6777b538SAndroid Build Coastguard Worker void CleanExitBeacon::WriteBeaconFile(bool exited_cleanly) const {
428*6777b538SAndroid Build Coastguard Worker base::Value::Dict dict;
429*6777b538SAndroid Build Coastguard Worker dict.Set(prefs::kStabilityExitedCleanly, exited_cleanly);
430*6777b538SAndroid Build Coastguard Worker dict.Set(kVariationsCrashStreak,
431*6777b538SAndroid Build Coastguard Worker local_state_->GetInteger(kVariationsCrashStreak));
432*6777b538SAndroid Build Coastguard Worker
433*6777b538SAndroid Build Coastguard Worker std::string json_string;
434*6777b538SAndroid Build Coastguard Worker JSONStringValueSerializer serializer(&json_string);
435*6777b538SAndroid Build Coastguard Worker bool success = serializer.Serialize(dict);
436*6777b538SAndroid Build Coastguard Worker DCHECK(success);
437*6777b538SAndroid Build Coastguard Worker DCHECK(!json_string.empty());
438*6777b538SAndroid Build Coastguard Worker {
439*6777b538SAndroid Build Coastguard Worker base::ScopedAllowBlocking allow_io;
440*6777b538SAndroid Build Coastguard Worker success = base::WriteFile(beacon_file_path_, json_string);
441*6777b538SAndroid Build Coastguard Worker }
442*6777b538SAndroid Build Coastguard Worker base::UmaHistogramBoolean("Variations.ExtendedSafeMode.BeaconFileWrite",
443*6777b538SAndroid Build Coastguard Worker success);
444*6777b538SAndroid Build Coastguard Worker }
445*6777b538SAndroid Build Coastguard Worker
446*6777b538SAndroid Build Coastguard Worker } // namespace metrics
447