// 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 #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 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& 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 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& 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& 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