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