1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/common/metrics/variations/variations_util.h"
6
7#include <vector>
8
9#include "base/strings/string16.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_split.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "chrome/common/child_process_logging.h"
16#include "chrome/installer/util/google_update_experiment_util.h"
17
18namespace chrome_variations {
19
20namespace {
21
22const char kVariationPrefix[] = "CrVar";
23const char kExperimentLabelSep[] = ";";
24
25// Populates |name_group_ids| based on |active_groups|.
26void GetFieldTrialActiveGroupIdsForActiveGroups(
27    const base::FieldTrial::ActiveGroups& active_groups,
28    std::vector<ActiveGroupId>* name_group_ids) {
29  DCHECK(name_group_ids->empty());
30  for (base::FieldTrial::ActiveGroups::const_iterator it =
31       active_groups.begin(); it != active_groups.end(); ++it) {
32    name_group_ids->push_back(MakeActiveGroupId(it->trial_name,
33                                                it->group_name));
34  }
35}
36
37// This method builds a single experiment label for a Chrome Variation,
38// including a timestamp that is a year in the future from now. Since multiple
39// headers can be transmitted, |count| is a number that is appended after the
40// label key to differentiate the labels.
41string16 CreateSingleExperimentLabel(int count, VariationID id) {
42  // Build the parts separately so they can be validated.
43  const string16 key =
44      ASCIIToUTF16(kVariationPrefix) + base::IntToString16(count);
45  DCHECK_LE(key.size(), 8U);
46  const string16 value = base::IntToString16(id);
47  DCHECK_LE(value.size(), 8U);
48  string16 label(key);
49  label += ASCIIToUTF16("=");
50  label += value;
51  label += ASCIIToUTF16("|");
52  label += installer::BuildExperimentDateString();
53  return label;
54}
55
56}  // namespace
57
58void GetFieldTrialActiveGroupIds(
59    std::vector<ActiveGroupId>* name_group_ids) {
60  DCHECK(name_group_ids->empty());
61  // A note on thread safety: Since GetActiveFieldTrialGroups() is thread
62  // safe, and we operate on a separate list of that data, this function is
63  // technically thread safe as well, with respect to the FieldTriaList data.
64  base::FieldTrial::ActiveGroups active_groups;
65  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
66  GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
67                                             name_group_ids);
68}
69
70void GetFieldTrialActiveGroupIdsAsStrings(
71    std::vector<string16>* output) {
72  DCHECK(output->empty());
73  std::vector<ActiveGroupId> name_group_ids;
74  GetFieldTrialActiveGroupIds(&name_group_ids);
75  for (size_t i = 0; i < name_group_ids.size(); ++i) {
76    output->push_back(UTF8ToUTF16(base::StringPrintf(
77        "%x-%x", name_group_ids[i].name, name_group_ids[i].group)));
78  }
79}
80
81void GenerateVariationChunks(const std::vector<string16>& experiments,
82                             std::vector<string16>* chunks) {
83  string16 current_chunk;
84  for (size_t i = 0; i < experiments.size(); ++i) {
85    const size_t needed_length =
86        (current_chunk.empty() ? 1 : 0) + experiments[i].length();
87    if (current_chunk.length() + needed_length > kMaxVariationChunkSize) {
88      chunks->push_back(current_chunk);
89      current_chunk = experiments[i];
90    } else {
91      if (!current_chunk.empty())
92        current_chunk.push_back(',');
93      current_chunk += experiments[i];
94    }
95  }
96  if (!current_chunk.empty())
97    chunks->push_back(current_chunk);
98}
99
100void SetChildProcessLoggingVariationList() {
101  std::vector<string16> experiment_strings;
102  GetFieldTrialActiveGroupIdsAsStrings(&experiment_strings);
103  child_process_logging::SetExperimentList(experiment_strings);
104}
105
106string16 BuildGoogleUpdateExperimentLabel(
107    const base::FieldTrial::ActiveGroups& active_groups) {
108  string16 experiment_labels;
109  int counter = 0;
110
111  // Find all currently active VariationIDs associated with Google Update.
112  for (base::FieldTrial::ActiveGroups::const_iterator it =
113       active_groups.begin(); it != active_groups.end(); ++it) {
114    const VariationID id = GetGoogleVariationID(GOOGLE_UPDATE_SERVICE,
115                                                it->trial_name, it->group_name);
116
117    if (id == EMPTY_ID)
118      continue;
119
120    if (!experiment_labels.empty())
121      experiment_labels += ASCIIToUTF16(kExperimentLabelSep);
122    experiment_labels += CreateSingleExperimentLabel(++counter, id);
123  }
124
125  return experiment_labels;
126}
127
128string16 ExtractNonVariationLabels(const string16& labels) {
129  const string16 separator = ASCIIToUTF16(kExperimentLabelSep);
130  string16 non_variation_labels;
131
132  // First, split everything by the label separator.
133  std::vector<string16> entries;
134  base::SplitStringUsingSubstr(labels, separator, &entries);
135
136  // For each label, keep the ones that do not look like a Variations label.
137  for (std::vector<string16>::const_iterator it = entries.begin();
138       it != entries.end(); ++it) {
139    if (it->empty() || StartsWith(*it, ASCIIToUTF16(kVariationPrefix), false))
140      continue;
141
142    // Dump the whole thing, including the timestamp.
143    if (!non_variation_labels.empty())
144      non_variation_labels += separator;
145    non_variation_labels += *it;
146  }
147
148  return non_variation_labels;
149}
150
151string16 CombineExperimentLabels(const string16& variation_labels,
152                                 const string16& other_labels) {
153  const string16 separator = ASCIIToUTF16(kExperimentLabelSep);
154  DCHECK(!StartsWith(variation_labels, separator, false));
155  DCHECK(!EndsWith(variation_labels, separator, false));
156  DCHECK(!StartsWith(other_labels, separator, false));
157  DCHECK(!EndsWith(other_labels, separator, false));
158  // Note that if either label is empty, a separator is not necessary.
159  string16 combined_labels = other_labels;
160  if (!other_labels.empty() && !variation_labels.empty())
161    combined_labels += separator;
162  combined_labels += variation_labels;
163  return combined_labels;
164}
165
166// Functions below are exposed for testing explicitly behind this namespace.
167// They simply wrap existing functions in this file.
168namespace testing {
169
170void TestGetFieldTrialActiveGroupIds(
171    const base::FieldTrial::ActiveGroups& active_groups,
172    std::vector<ActiveGroupId>* name_group_ids) {
173  GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
174                                             name_group_ids);
175}
176
177}  // namespace testing
178
179}  // namespace chrome_variations
180