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 DictionaryValue & dict)53 void TraceConfigCategoryFilter::InitializeFromConfigDict(
54 const DictionaryValue& dict) {
55 const ListValue* category_list = nullptr;
56 if (dict.GetList(kIncludedCategoriesParam, &category_list))
57 SetCategoriesFromIncludedList(*category_list);
58 if (dict.GetList(kExcludedCategoriesParam, &category_list))
59 SetCategoriesFromExcludedList(*category_list);
60 }
61
IsCategoryGroupEnabled(const StringPiece & category_group_name) const62 bool TraceConfigCategoryFilter::IsCategoryGroupEnabled(
63 const StringPiece& category_group_name) const {
64 bool had_enabled_by_default = false;
65 DCHECK(!category_group_name.empty());
66 CStringTokenizer category_group_tokens(category_group_name.begin(),
67 category_group_name.end(), ",");
68 while (category_group_tokens.GetNext()) {
69 StringPiece category_group_token = category_group_tokens.token_piece();
70 // Don't allow empty tokens, nor tokens with leading or trailing space.
71 DCHECK(IsCategoryNameAllowed(category_group_token))
72 << "Disallowed category string";
73 if (IsCategoryEnabled(category_group_token))
74 return true;
75
76 if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
77 had_enabled_by_default = true;
78 }
79 // Do a second pass to check for explicitly disabled categories
80 // (those explicitly enabled have priority due to first pass).
81 category_group_tokens.Reset();
82 bool category_group_disabled = false;
83 while (category_group_tokens.GetNext()) {
84 StringPiece category_group_token = category_group_tokens.token_piece();
85 for (const std::string& category : excluded_categories_) {
86 if (MatchPattern(category_group_token, category)) {
87 // Current token of category_group_name is present in excluded_list.
88 // Flag the exclusion and proceed further to check if any of the
89 // remaining categories of category_group_name is not present in the
90 // excluded_ list.
91 category_group_disabled = true;
92 break;
93 }
94 // One of the category of category_group_name is not present in
95 // excluded_ list. So, if it's not a disabled-by-default category,
96 // it has to be included_ list. Enable the category_group_name
97 // for recording.
98 if (!MatchPattern(category_group_token, TRACE_DISABLED_BY_DEFAULT("*")))
99 category_group_disabled = false;
100 }
101 // One of the categories present in category_group_name is not present in
102 // excluded_ list. Implies this category_group_name group can be enabled
103 // for recording, since one of its groups is enabled for recording.
104 if (!category_group_disabled)
105 break;
106 }
107 // If the category group is not excluded, and there are no included patterns
108 // we consider this category group enabled, as long as it had categories
109 // other than disabled-by-default.
110 return !category_group_disabled && had_enabled_by_default &&
111 included_categories_.empty();
112 }
113
IsCategoryEnabled(const StringPiece & category_name) const114 bool TraceConfigCategoryFilter::IsCategoryEnabled(
115 const StringPiece& category_name) const {
116 // Check the disabled- filters and the disabled-* wildcard first so that a
117 // "*" filter does not include the disabled.
118 for (const std::string& category : disabled_categories_) {
119 if (MatchPattern(category_name, category))
120 return true;
121 }
122
123 if (MatchPattern(category_name, TRACE_DISABLED_BY_DEFAULT("*")))
124 return false;
125
126 for (const std::string& category : included_categories_) {
127 if (MatchPattern(category_name, category))
128 return true;
129 }
130
131 return false;
132 }
133
Merge(const TraceConfigCategoryFilter & config)134 void TraceConfigCategoryFilter::Merge(const TraceConfigCategoryFilter& config) {
135 // Keep included patterns only if both filters have an included entry.
136 // Otherwise, one of the filter was specifying "*" and we want to honor the
137 // broadest filter.
138 if (!included_categories_.empty() && !config.included_categories_.empty()) {
139 included_categories_.insert(included_categories_.end(),
140 config.included_categories_.begin(),
141 config.included_categories_.end());
142 } else {
143 included_categories_.clear();
144 }
145
146 disabled_categories_.insert(disabled_categories_.end(),
147 config.disabled_categories_.begin(),
148 config.disabled_categories_.end());
149 excluded_categories_.insert(excluded_categories_.end(),
150 config.excluded_categories_.begin(),
151 config.excluded_categories_.end());
152 }
153
Clear()154 void TraceConfigCategoryFilter::Clear() {
155 included_categories_.clear();
156 disabled_categories_.clear();
157 excluded_categories_.clear();
158 }
159
ToDict(DictionaryValue * dict) const160 void TraceConfigCategoryFilter::ToDict(DictionaryValue* dict) const {
161 StringList categories(included_categories_);
162 categories.insert(categories.end(), disabled_categories_.begin(),
163 disabled_categories_.end());
164 AddCategoriesToDict(categories, kIncludedCategoriesParam, dict);
165 AddCategoriesToDict(excluded_categories_, kExcludedCategoriesParam, dict);
166 }
167
ToFilterString() const168 std::string TraceConfigCategoryFilter::ToFilterString() const {
169 std::string filter_string;
170 WriteCategoryFilterString(included_categories_, &filter_string, true);
171 WriteCategoryFilterString(disabled_categories_, &filter_string, true);
172 WriteCategoryFilterString(excluded_categories_, &filter_string, false);
173 return filter_string;
174 }
175
SetCategoriesFromIncludedList(const ListValue & included_list)176 void TraceConfigCategoryFilter::SetCategoriesFromIncludedList(
177 const ListValue& included_list) {
178 included_categories_.clear();
179 for (size_t i = 0; i < included_list.GetSize(); ++i) {
180 std::string category;
181 if (!included_list.GetString(i, &category))
182 continue;
183 if (category.compare(0, strlen(TRACE_DISABLED_BY_DEFAULT("")),
184 TRACE_DISABLED_BY_DEFAULT("")) == 0) {
185 disabled_categories_.push_back(category);
186 } else {
187 included_categories_.push_back(category);
188 }
189 }
190 }
191
SetCategoriesFromExcludedList(const ListValue & excluded_list)192 void TraceConfigCategoryFilter::SetCategoriesFromExcludedList(
193 const ListValue& excluded_list) {
194 excluded_categories_.clear();
195 for (size_t i = 0; i < excluded_list.GetSize(); ++i) {
196 std::string category;
197 if (excluded_list.GetString(i, &category))
198 excluded_categories_.push_back(category);
199 }
200 }
201
AddCategoriesToDict(const StringList & categories,const char * param,DictionaryValue * dict) const202 void TraceConfigCategoryFilter::AddCategoriesToDict(
203 const StringList& categories,
204 const char* param,
205 DictionaryValue* dict) const {
206 if (categories.empty())
207 return;
208
209 auto list = std::make_unique<ListValue>();
210 for (const std::string& category : categories)
211 list->AppendString(category);
212 dict->Set(param, std::move(list));
213 }
214
WriteCategoryFilterString(const StringList & values,std::string * out,bool included) const215 void TraceConfigCategoryFilter::WriteCategoryFilterString(
216 const StringList& values,
217 std::string* out,
218 bool included) const {
219 bool prepend_comma = !out->empty();
220 int token_cnt = 0;
221 for (const std::string& category : values) {
222 if (token_cnt > 0 || prepend_comma)
223 StringAppendF(out, ",");
224 StringAppendF(out, "%s%s", (included ? "" : "-"), category.c_str());
225 ++token_cnt;
226 }
227 }
228
229 // static
IsCategoryNameAllowed(StringPiece str)230 bool TraceConfigCategoryFilter::IsCategoryNameAllowed(StringPiece str) {
231 return !str.empty() && str.front() != ' ' && str.back() != ' ';
232 }
233
234 } // namespace trace_event
235 } // namespace base
236