File: variations_crash_keys.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (338 lines) | stat: -rw-r--r-- 13,201 bytes parent folder | download | duplicates (5)
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
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/variations/variations_crash_keys.h"

#include <set>
#include <string>

#include "base/command_line.h"
#include "base/debug/leak_annotations.h"
#include "base/metrics/field_trial_list_including_low_anonymity.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "build/buildflag.h"
#include "components/crash/core/common/crash_key.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/buildflags.h"
#include "components/variations/synthetic_trials.h"
#include "components/variations/variations_switches.h"

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#include "base/task/thread_pool.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "base/task/cancelable_task_tracker.h"
#include "components/variations/variations_crash_keys_android.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "components/variations/variations_crash_keys_chromeos.h"
#endif

namespace variations {

namespace {

// Size of the "variations" crash key (kExperimentListKey) in bytes.
// 1024*6 bytes should be able to hold about 341 entries, given each entry is
// 18 bytes long (due to being of the form "8e7abfb0-c16397b7,").
#if BUILDFLAG(LARGE_VARIATION_KEY_SIZE)
constexpr size_t kVariationsKeySize = 1024 * 8;
constexpr char kVariationKeySizeHistogram[] =
    "Variations.Limits.VariationKeySize.Large";
#else
constexpr size_t kVariationsKeySize = 1024 * 6;
constexpr char kVariationKeySizeHistogram[] =
    "Variations.Limits.VariationKeySize.Default";
#endif
constexpr size_t kVariationsKeySizeNumBuckets = 16;

// Crash key reporting the number of experiments. 8 is the size of the crash key
// in bytes, which is used to hold an int as a string.
crash_reporter::CrashKeyString<8> g_num_variations_crash_key(
    kNumExperimentsKey);

// Crash key reporting the variations state.
crash_reporter::CrashKeyString<kVariationsKeySize> g_variations_crash_key(
    kExperimentListKey);

crash_reporter::CrashKeyString<64> g_variations_seed_version_crash_key(
    kVariationsSeedVersionKey);

}  // namespace

class VariationsCrashKeys final : public base::FieldTrialList::Observer {
 public:
  VariationsCrashKeys();

  VariationsCrashKeys(const VariationsCrashKeys&) = delete;
  VariationsCrashKeys& operator=(const VariationsCrashKeys&) = delete;

  ~VariationsCrashKeys() override;

  // base::FieldTrialList::Observer:
  void OnFieldTrialGroupFinalized(const base::FieldTrial& trial,
                                  const std::string& group_name) override;

  // Notifies the object that the list of synthetic field trial groups has
  // changed. Note: This matches the SyntheticTrialObserver interface, but this
  // object isn't a direct observer, so doesn't implement it.
  void OnSyntheticTrialsChanged(const std::vector<SyntheticTrialGroup>& groups);

  // Gets the list of experiments and number of experiments in the format we
  // want to place it in the crash keys.
  ExperimentListInfo GetExperimentListInfo();

 private:
  // Adds an entry for the specified field trial to internal state, without
  // updating crash keys. Returns true if it was successfully added. Returns
  // false otherwise (i.e., the trial was already added previously).
  bool AppendFieldTrial(const std::string& trial_name,
                        const std::string& group_name,
                        bool is_overridden);

  // Updates crash keys based on internal state.
  void UpdateCrashKeys();

  void AppendFieldTrialAndUpdateCrashKeys(const std::string& trial_name,
                                          const std::string& group_name,
                                          bool is_overridden);

  // List of active trials, used to prevent duplicates from being appended to
  // |variations_string_|.
  std::set<std::string> active_trials_;

  // Task runner corresponding to the UI thread, used to reschedule synchronous
  // observer calls that happen on a different thread.
  scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner_;

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  // Task runner corresponding to a background thread, used for tasks that may
  // block.
  scoped_refptr<base::SequencedTaskRunner> background_thread_task_runner_;
#endif  // IS_CHROMEOS || IS_ANDROID

#if BUILDFLAG(IS_ANDROID)
  // A task tracker that allows us to cancel any tasks that have been posted
  // but have not started to run.
  base::CancelableTaskTracker cancelable_task_tracker_;
#endif  // IS_ANDROID

  // A serialized string containing the variations state.
  std::string variations_string_;

  // A serialized string containing the synthetic trials state.
  std::string synthetic_trials_string_;

  // Number of entries in |synthetic_trials_string_|.
  size_t num_synthetic_trials_ = 0;

  SEQUENCE_CHECKER(sequence_checker_);
};

VariationsCrashKeys::VariationsCrashKeys() {
  // Set |ui_thread_task_runner_| *before* observering field trials. Otherwise,
  // it would be possible for a field trial to be activated on a different
  // thread, calling OnFieldTrialGroupFinalized(), and accessing
  // |ui_thread_task_runner_| before it is set.
  ui_thread_task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  // Set |background_thread_task_runner_| before observering field trials for
  // the same reason mentioned above.
  background_thread_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
      {base::TaskPriority::BEST_EFFORT, base::MayBlock()});
#endif  // IS_CHROMEOS || IS_ANDROID

  // Observe field trials before filling the crash key with the currently
  // active field trials. Otherwise, there could be a race condition where a
  // trial is activated on a different thread before we started observing.
  // Similarly, it is possible a trial is added twice if it is activated on
  // a different thread after starting to observe, but before the call to
  // GetActiveFieldTrialGroups() below. However, this is addressed with the use
  // of |active_trials_|.
  // TODO(crbug.com/40266142): This would not be necessary to do assuming this
  // is called while Chrome is still in single-threaded mode. While this is true
  // for the browser process, child processes call this relatively late (and
  // possibly other platforms as well). Remove |active_trials_| when this is
  // fixed.
  base::FieldTrialListIncludingLowAnonymity::AddObserver(this);

  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups(
      &active_groups);
  for (const auto& entry : active_groups) {
    AppendFieldTrial(entry.trial_name, entry.group_name, entry.is_overridden);
  }

  UpdateCrashKeys();
}

VariationsCrashKeys::~VariationsCrashKeys() {
  base::FieldTrialListIncludingLowAnonymity::RemoveObserver(this);
  g_num_variations_crash_key.Clear();
  g_variations_crash_key.Clear();
  g_variations_seed_version_crash_key.Clear();
}

void VariationsCrashKeys::OnFieldTrialGroupFinalized(
    const base::FieldTrial& trial,
    const std::string& group_name) {
  // If this is called on a different thread, post it back to the UI thread.
  // Note: This is safe to do because in production, this object is never
  // deleted and if this is called, it means the constructor has already run,
  // which is the only place that |ui_thread_task_runner_| is set.
  if (!ui_thread_task_runner_->RunsTasksInCurrentSequence()) {
    ui_thread_task_runner_->PostTask(
        FROM_HERE,
        BindOnce(&VariationsCrashKeys::AppendFieldTrialAndUpdateCrashKeys,
                 // base::Unretained() is safe here because this object is
                 // never deleted in production.
                 base::Unretained(this), trial.trial_name(), group_name,
                 trial.IsOverridden()));
    return;
  }

  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  AppendFieldTrialAndUpdateCrashKeys(trial.trial_name(), group_name,
                                     trial.IsOverridden());
}

bool VariationsCrashKeys::AppendFieldTrial(const std::string& trial_name,
                                           const std::string& group_name,
                                           bool is_overridden) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!active_trials_.insert(trial_name).second) {
    return false;
  }

  auto active_group_id =
      MakeActiveGroupId(trial_name, group_name, is_overridden);
  auto variation = ActiveGroupToString(active_group_id);

  variations_string_ += variation;

  return true;
}

void VariationsCrashKeys::AppendFieldTrialAndUpdateCrashKeys(
    const std::string& trial_name,
    const std::string& group_name,
    bool is_overridden) {
  if (AppendFieldTrial(trial_name, group_name, is_overridden)) {
    UpdateCrashKeys();
  }
}

ExperimentListInfo VariationsCrashKeys::GetExperimentListInfo() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ExperimentListInfo result;
  result.num_experiments = active_trials_.size() + num_synthetic_trials_;
  result.experiment_list.reserve(variations_string_.size() +
                                 synthetic_trials_string_.size());
  result.experiment_list.append(variations_string_);
  result.experiment_list.append(synthetic_trials_string_);
  return result;
}

void VariationsCrashKeys::UpdateCrashKeys() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  ExperimentListInfo info = GetExperimentListInfo();
  g_num_variations_crash_key.Set(base::NumberToString(info.num_experiments));

  const size_t count_of_kbs = info.experiment_list.size() / 1024;
  UMA_HISTOGRAM_EXACT_LINEAR(kVariationKeySizeHistogram, count_of_kbs,
                             kVariationsKeySizeNumBuckets);
  if (info.experiment_list.size() > kVariationsKeySize) {
    // If size exceeded, truncate to the last full entry.
    int comma_index =
        info.experiment_list.substr(0, kVariationsKeySize).rfind(',');
    info.experiment_list.resize(comma_index + 1);
  }

  g_variations_crash_key.Set(info.experiment_list);

  // If we're in the child process, set the variations seed version from the
  // command line, which is passed from the browser process. In the browser
  // process, SetVariationsSeedVersionCrashKey() gets called on startup.
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(variations::switches::kVariationsSeedVersion)) {
    SetVariationsSeedVersionCrashKey(command_line->GetSwitchValueASCII(
        variations::switches::kVariationsSeedVersion));
  }

#if BUILDFLAG(IS_ANDROID)
  SaveVariationsForAnrReporting(&cancelable_task_tracker_,
                                background_thread_task_runner_, info);
#endif  // IS_ANDROID

#if BUILDFLAG(IS_CHROMEOS)
  ReportVariationsToChromeOs(background_thread_task_runner_, info);
#endif  // IS_CHROMEOS
}

void VariationsCrashKeys::OnSyntheticTrialsChanged(
    const std::vector<SyntheticTrialGroup>& synthetic_trials) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Note: This part is inefficient as each time synthetic trials change, this
  // code recomputes all their name hashes. However, given that there should
  // not be too many synthetic trials, this is not too big of an issue.
  synthetic_trials_string_.clear();
  for (const auto& synthetic_trial : synthetic_trials) {
    synthetic_trials_string_ += ActiveGroupToString(synthetic_trial.id());
  }
  num_synthetic_trials_ = synthetic_trials.size();

  UpdateCrashKeys();
}

// Singletone crash key manager. Allocated once at process start up and
// intentionally leaked since it needs to live for the duration of the process
// there's no benefit in cleaning it up at exit.
VariationsCrashKeys* g_variations_crash_keys = nullptr;

const char kNumExperimentsKey[] = "num-experiments";
const char kExperimentListKey[] = "variations";
const char kVariationsSeedVersionKey[] = "variations-seed-version";

void InitCrashKeys() {
  DCHECK(!g_variations_crash_keys);
  g_variations_crash_keys = new VariationsCrashKeys();
  ANNOTATE_LEAKING_OBJECT_PTR(g_variations_crash_keys);
}

void UpdateCrashKeysWithSyntheticTrials(
    const std::vector<SyntheticTrialGroup>& synthetic_trials) {
  DCHECK(g_variations_crash_keys);
  g_variations_crash_keys->OnSyntheticTrialsChanged(synthetic_trials);
}

void SetVariationsSeedVersionCrashKey(const std::string& seed_version) {
  g_variations_seed_version_crash_key.Set(seed_version);
}

void ClearCrashKeysInstanceForTesting() {
  DCHECK(g_variations_crash_keys);
  delete g_variations_crash_keys;
  g_variations_crash_keys = nullptr;
}

ExperimentListInfo GetExperimentListInfo() {
  DCHECK(g_variations_crash_keys);
  return g_variations_crash_keys->GetExperimentListInfo();
}

std::string ActiveGroupToString(const ActiveGroupId& active_group) {
  return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
}

}  // namespace variations