1 // Copyright 2017 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 "base/trace_event/trace_config_category_filter.h"
6
7 #include "base/memory/ptr_util.h"
8 #include "base/strings/pattern.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_tokenizer.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/trace_event/trace_event.h"
14
15 namespace base {
16 namespace trace_event {
17
18 namespace {
19 const char kIncludedCategoriesParam[] = "included_categories";
20 const char kExcludedCategoriesParam[] = "excluded_categories";
21 }
22
23 TraceConfigCategoryFilter::TraceConfigCategoryFilter() = default;
24
25 TraceConfigCategoryFilter::TraceConfigCategoryFilter(
26 const TraceConfigCategoryFilter& other) = default;
27
28 TraceConfigCategoryFilter::~TraceConfigCategoryFilter() = default;
29
30 TraceConfigCategoryFilter& TraceConfigCategoryFilter::operator=(
31 const TraceConfigCategoryFilter& rhs) = default;
32
InitializeFromString(const StringPiece & category_filter_string)33 void TraceConfigCategoryFilter::InitializeFromString(
34 const StringPiece& category_filter_string) {
35 std::vector<StringPiece> split = SplitStringPiece(
36 category_filter_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL);
37 for (const StringPiece& category : split) {
38 // Ignore empty categories.
39 if (category.empty())
40 continue;
41 if (category.front() == '-') {
42 // Excluded categories start with '-'.
43 // Remove '-' from category string.
44 excluded_categories_.push_back(category.substr(1).as_string());
45 } else if (category.starts_with(TRACE_DISABLED_BY_DEFAULT(""))) {
46 disabled_categories_.push_back(category.as_string());
47 } else {
48 included_categories_.push_back(category.as_string());
49 }
50 }
51 }
52
InitializeFromConfigDict(const Value & dict)53 void TraceConfigCategoryFilter::InitializeFromConfigDict(const Value& dict) {
54 const Value* included_category_list =
55 dict.FindListKey(kIncludedCategoriesParam);
56 if (included_category_list)
57 SetCategoriesFromIncludedList(*included_category_list);
58 const Value* excluded_category_list =
59 dict.FindListKey(kExcludedCategoriesParam);
60 if (excluded_category_list)
61 SetCategoriesFromExcludedList(*excluded_category_list);
62 }
63
IsCategoryGroupEnabled(const StringPiece & category_group_name) const64 bool TraceConfigCategoryFilter::IsCategoryGroupEnabled(
65 const StringPiece& category_group_name) const {
66 bool had_enabled_by_default = false;
67 DCHECK(!category_group_name.empty());
68 CStringTokenizer category_group_tokens(category_group_name.begin(),
69 category_group_name.end(), ",");
70 while (category_group_tokens.GetNext()) {
71 StringPiece category_group_token = category_group_tokens.token_piece();
72 // Don't allow empty tokens, nor tokens with leading or trailing space.
73 DCHECK(IsCategoryNameAllowed(category_group_token))
74 << "Disallowed category string";
75 if (IsCategoryEnabled(category_group_token))
76 return true;
77
78 if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
79 had_enabled_by_default = true;
80 }
81 // Do a second pass to check for explicitly disabled categories
82 // (those explicitly enabled have priority due to first pass).
83 category_group_tokens.Reset();
84 bool category_group_disabled = false;
85 while (category_group_tokens.GetNext()) {
86 StringPiece category_group_token = category_group_tokens.token_piece();
87 for (const std::string& category : excluded_categories_) {
88 if (MatchPattern(category_group_token, category)) {
89 // Current token of category_group_name is present in excluded_list.
90 // Flag the exclusion and proceed further to check if any of the
91 // remaining categories of category_group_name is not present in the
92 // excluded_ list.
93 category_group_disabled = true;
94 break;
95 }
96 // One of the category of category_group_name is not present in
97 // excluded_ list. So, if it's not a disabled-by-default category,
98 // it has to be included_ list. Enable the category_group_name
99 // for recording.
100 if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
101 category_group_disabled = false;
102 }
103 // One of the categories present in category_group_name is not present in
104 // excluded_ list. Implies this category_group_name group can be enabled
105 // for recording, since one of its groups is enabled for recording.
106 if (!category_group_disabled)
107 break;
108 }
109 // If the category group is not excluded, and there are no included patterns
110 // we consider this category group enabled, as long as it had categories
111 // other than disabled-by-default.
112 return !category_group_disabled && had_enabled_by_default &&
113 included_categories_.empty();
114 }
115
IsCategoryEnabled(const StringPiece & category_name) const116 bool TraceConfigCategoryFilter::IsCategoryEnabled(
117 const StringPiece& category_name) const {
118 // Check the disabled- filters and the disabled-* wildcard first so that a
119 // "*" filter does not include the disabled.
120 for (const std::string& category : disabled_categories_) {
121 if (MatchPattern(category_name, category))
122 return true;
123 }
124
125 if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*")))
126 return false;
127
128 for (const std::string& category : included_categories_) {
129 if (MatchPattern(category_name, category))
130 return true;
131 }
132
133 return false;
134 }
135
Merge(const TraceConfigCategoryFilter & config)136 void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) {
137 // Keep included patterns only if both filters have an included entry.
138 // Otherwise, one of the filter was specifying "*" and we want to honor the
139 // broadest filter.
140 if (!included_categories_.empty() && !config.included_categories_.empty()) {
141 included_categories_.insert(included_categories_.end(),
142 config.included_categories_.begin(),
143 config.included_categories_.end());
144 } else {
145 included_categories_.clear();
146 }
147
148 disabled_categories_.insert(disabled_categories_.end(),
149 config.disabled_categories_.begin(),
150 config.disabled_categories_.end());
151 excluded_categories_.insert(excluded_categories_.end(),
152 config.excluded_categories_.begin(),
153 config.excluded_categories_.end());
154 }
155
Clear()156 void TraceConfigCategoryFilter::Clear() {
157 included_categories_.clear();
158 disabled_categories_.clear();
159 excluded_categories_.clear();
160 }
161
ToDict(Value * dict) const162 void TraceConfigCategoryFilter::ToDict(Value* dict) const {
163 StringList categories(included_categories_);
164 categories.insert(categories.end(), disabled_categories_.begin(),
165 disabled_categories_.end());
166 AddCategoriesToDict(categories, kIncludedCategoriesParam, dict);
167 AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict);
168 }
169
ToFilterString() const170 std::string TraceConfigCategoryFilter::ToFilterString() const {
171 std::string filter_string;
172 WriteCategoryFilterString(included_categories_, &filter_string, true);
173 WriteCategoryFilterString(disabled_categories_, &filter_string, true);
174 WriteCategoryFilterString(excluded_categories_, &filter_string, false);
175 return filter_string;
176 }
177
SetCategoriesFromIncludedList(const Value & included_list)178 void TraceConfigCategoryFilter::SetCategoriesFromIncludedList(
179 const Value& included_list) {
180 included_categories_.clear();
181 for (const Value& item : included_list.GetList()) {
182 if (!item.is_string())
183 continue;
184 const std::string& category = item.GetString();
185 if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")),
186 TRACE_DISABLED_BY_DEFAULT("")) == 0) {
187 disabled_categories_.push_back(category);
188 } else {
189 included_categories_.push_back(category);
190 }
191 }
192 }
193
SetCategoriesFromExcludedList(const Value & excluded_list)194 void TraceConfigCategoryFilter::SetCategoriesFromExcludedList(
195 const Value& excluded_list) {
196 excluded_categories_.clear();
197 for (const Value& item : excluded_list.GetList()) {
198 if (item.is_string())
199 excluded_categories_.push_back(item.GetString());
200 }
201 }
202
AddCategoriesToDict(const StringList & categories,const char * param,Value * dict) const203 void TraceConfigCategoryFilter::AddCategoriesToDict(
204 const StringList& categories,
205 const char* param,
206 Value* dict) const {
207 if (categories.empty())
208 return;
209
210 std::vector<base::Value> list;
211 for (const std::string& category : categories)
212 list.emplace_back(category);
213 dict->SetKey(param, base::Value(std::move(list)));
214 }
215
WriteCategoryFilterString(const StringList & values,std::string * out,bool included) const216 void TraceConfigCategoryFilter::WriteCategoryFilterString(
217 const StringList& values,
218 std::string* out,
219 bool included) const {
220 bool prepend_comma = !out->empty();
221 int token_cnt = 0;
222 for (const std::string& category : values) {
223 if (token_cnt > 0 || prepend_comma)
224 StringAppendF(out, ",");
225 StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str());
226 ++token_cnt;
227 }
228 }
229
230 // static
IsCategoryNameAllowed(StringPiece str)231 bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) {
232 return !str.empty() && str.front() != ' ' && str.back() != ' ';
233 }
234
235 } // namespace trace_event
236 } // namespace base
237