blob: 91c477c3b4b64b541a5acf190ac9397b9addb0c5 [file] [log] [blame]
// 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/synthetic_trials.h"
namespace variations {
namespace {
// Size of the "num-experiments" crash key in bytes. 2048 bytes should be able
// to hold about 113 entries, given each entry is 18 bytes long (due to being
// of the form "8e7abfb0-c16397b7,").
constexpr size_t kVariationsKeySize = 2048;
// 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();
return;
}
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