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
|
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 <string>
#include "base/debug/leak_annotations.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.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"
namespace variations {
namespace {
// Size of the "num-experiments" crash key in bytes. 4096 bytes should be able
// to hold about 227 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 = 8192;
#else
constexpr size_t kVariationsKeySize = 4096;
#endif
// 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("num-experiments");
// Crash key reporting the variations state.
crash_reporter::CrashKeyString<kVariationsKeySize> g_variations_crash_key(
"variations");
std::string ActiveGroupToString(const ActiveGroupId& active_group) {
return base::StringPrintf("%x-%x,", active_group.name, active_group.group);
}
class VariationsCrashKeys final : public base::FieldTrialList::Observer {
public:
VariationsCrashKeys();
~VariationsCrashKeys() override;
// base::FieldTrialList::Observer:
void OnFieldTrialGroupFinalized(const std::string& trial_name,
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);
private:
// Adds an entry for the specified field trial to internal state, without
// updating crash keys.
void AppendFieldTrial(const std::string& trial_name,
const std::string& group_name);
// Updates crash keys based on internal state.
void UpdateCrashKeys();
// 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_;
// A serialized string containing the variations state.
std::string variations_string_;
// Number of entries in |variations_string_|.
size_t num_variations_ = 0;
// 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_);
DISALLOW_COPY_AND_ASSIGN(VariationsCrashKeys);
};
VariationsCrashKeys::VariationsCrashKeys() {
base::FieldTrial::ActiveGroups active_groups;
base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
for (const auto& entry : active_groups) {
AppendFieldTrial(entry.trial_name, entry.group_name);
}
UpdateCrashKeys();
ui_thread_task_runner_ = base::SequencedTaskRunnerHandle::Get();
base::FieldTrialList::SetSynchronousObserver(this);
}
VariationsCrashKeys::~VariationsCrashKeys() {
base::FieldTrialList::RemoveSynchronousObserver(this);
g_num_variations_crash_key.Clear();
g_variations_crash_key.Clear();
}
void VariationsCrashKeys::OnFieldTrialGroupFinalized(
const std::string& trial_name,
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::OnFieldTrialGroupFinalized,
// base::Unretained() is safe here because this object is
// never deleted in production.
base::Unretained(this), trial_name, group_name));
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AppendFieldTrial(trial_name, group_name);
UpdateCrashKeys();
}
void VariationsCrashKeys::AppendFieldTrial(const std::string& trial_name,
const std::string& group_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto active_group_id = MakeActiveGroupId(trial_name, group_name);
auto variation = ActiveGroupToString(active_group_id);
variations_string_ += variation;
++num_variations_;
}
void VariationsCrashKeys::UpdateCrashKeys() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
g_num_variations_crash_key.Set(
base::NumberToString(num_variations_ + num_synthetic_trials_));
std::string combined_string;
combined_string.reserve(variations_string_.size() +
synthetic_trials_string_.size());
combined_string.append(variations_string_);
combined_string.append(synthetic_trials_string_);
if (combined_string.size() > kVariationsKeySize) {
// If size exceeded, truncate to the last full entry.
int comma_index = combined_string.substr(0, kVariationsKeySize).rfind(',');
combined_string.resize(comma_index + 1);
// NOTREACHED() will let us know of the problem and adjust the limit.
NOTREACHED();
}
g_variations_crash_key.Set(combined_string);
}
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;
} // namespace
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 ClearCrashKeysInstanceForTesting() {
DCHECK(g_variations_crash_keys);
delete g_variations_crash_keys;
g_variations_crash_keys = nullptr;
}
} // namespace variations
|