1# Copyright 2014 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
5import re
6
7
8def CreateLowOverheadFilter():
9  """Returns a filter with the least overhead possible.
10
11  This contains no sub-traces of thread tasks, so it's only useful for
12  capturing the cpu-time spent on threads (as well as needed benchmark
13  traces).
14
15  FIXME: Remove webkit.console when blink.console lands in chromium and
16  the ref builds are updated. crbug.com/386847
17  """
18  categories = [
19      "toplevel",
20      "base",
21      "benchmark",
22      "webkit.console",
23      "blink.console",
24      "trace_event_overhead"
25  ]
26  return ChromeTraceCategoryFilter(filter_string=','.join(categories))
27
28
29def CreateDefaultOverheadFilter():
30  """Returns a filter with the best-effort amount of overhead.
31
32  This matches Chrome tracing's default category filter setting, i.e., enable
33  all categories except the disabled-by-default-* ones.
34
35  We should use '*' instead of '' (empty string) here. On the Chrome side, both
36  '*' and '' mean default category filter setting. However, if someone adds
37  additional category filters, the behavior becomes different.
38
39  For example:
40  '*': enable all categories except the disabled-by-default-* ones.
41  '':  enable all categories except the disabled-by-default-* ones.
42
43  Now add an additional category filter 'abc' to '*' and '':
44  '*,abc': enable all categories (including 'abc') except the
45           disabled-by-default-* ones.
46  'abc':   enable only 'abc', and disable all other ones.
47  """
48  return ChromeTraceCategoryFilter(filter_string='*')
49
50
51def CreateDebugOverheadFilter():
52  """Returns a filter with as many traces enabled as is useful."""
53  return ChromeTraceCategoryFilter(
54      filter_string='*,disabled-by-default-cc.debug')
55
56
57_delay_re = re.compile(r'DELAY[(][A-Za-z0-9._;]+[)]')
58
59
60class ChromeTraceCategoryFilter(object):
61  """A set of included and excluded categories that should be traced.
62
63  The ChromeTraceCategoryFilter allows fine tuning of what data is traced for
64  Chrome. Basic choice of which tracers to use is done by TracingConfig.
65
66  Providing filter_string=None gives the default category filter, which leaves
67  what to trace up to the individual trace systems.
68  """
69  def __init__(self, filter_string=None):
70    self._included_categories = set()
71    self._excluded_categories = set()
72    self._disabled_by_default_categories = set()
73    self._synthetic_delays = set()
74    self.contains_wildcards = False
75    self.AddFilterString(filter_string)
76
77  def AddFilterString(self, filter_string):
78    if filter_string is None:
79      return
80
81    filter_set = set([cf.strip() for cf in filter_string.split(',')])
82    for category in filter_set:
83      self.AddFilter(category)
84
85  def AddFilter(self, category):
86    if category == '':
87      return
88
89    if ',' in category:
90      raise ValueError("Invalid category filter name: '%s'" % category)
91
92    if '*' in category or '?' in category:
93      self.contains_wildcards = True
94
95    if _delay_re.match(category):
96      self._synthetic_delays.add(category)
97      return
98
99    if category[0] == '-':
100      assert not category[1:] in self._included_categories
101      self._excluded_categories.add(category[1:])
102      return
103
104    if category.startswith('disabled-by-default-'):
105      self._disabled_by_default_categories.add(category)
106      return
107
108    assert not category in self._excluded_categories
109    self._included_categories.add(category)
110
111  @property
112  def included_categories(self):
113    return self._included_categories
114
115  @property
116  def excluded_categories(self):
117    return self._excluded_categories
118
119  @property
120  def disabled_by_default_categories(self):
121    return self._disabled_by_default_categories
122
123  @property
124  def synthetic_delays(self):
125    return self._synthetic_delays
126
127  @property
128  def filter_string(self):
129    return self._GetFilterString(stable_output=False)
130
131  @property
132  def stable_filter_string(self):
133    return self._GetFilterString(stable_output=True)
134
135  def _GetFilterString(self, stable_output):
136    # Note: This outputs fields in an order that intentionally matches
137    # trace_event_impl's CategoryFilter string order.
138    lists = []
139    lists.append(self._included_categories)
140    lists.append(self._disabled_by_default_categories)
141    lists.append(['-%s' % x for x in self._excluded_categories])
142    lists.append(self._synthetic_delays)
143    categories = []
144    for l in lists:
145      if stable_output:
146        l = list(l)
147        l.sort()
148      categories.extend(l)
149    return ','.join(categories)
150
151  def GetDictForChromeTracing(self):
152    INCLUDED_CATEGORIES_PARAM = 'included_categories'
153    EXCLUDED_CATEGORIES_PARAM = 'excluded_categories'
154    SYNTHETIC_DELAYS_PARAM = 'synthetic_delays'
155
156    result = {}
157    if self._included_categories or self._disabled_by_default_categories:
158      result[INCLUDED_CATEGORIES_PARAM] = list(
159          self._included_categories | self._disabled_by_default_categories)
160    if self._excluded_categories:
161      result[EXCLUDED_CATEGORIES_PARAM] = list(self._excluded_categories)
162    if self._synthetic_delays:
163      result[SYNTHETIC_DELAYS_PARAM] = list(self._synthetic_delays)
164    return result
165
166  def AddDisabledByDefault(self, category):
167    assert category.startswith('disabled-by-default-')
168    self._disabled_by_default_categories.add(category)
169
170  def AddIncludedCategory(self, category_glob):
171    """Explicitly enables anything matching category_glob."""
172    assert not category_glob.startswith('disabled-by-default-')
173    assert not category_glob in self._excluded_categories
174    self._included_categories.add(category_glob)
175
176  def AddExcludedCategory(self, category_glob):
177    """Explicitly disables anything matching category_glob."""
178    assert not category_glob.startswith('disabled-by-default-')
179    assert not category_glob in self._included_categories
180    self._excluded_categories.add(category_glob)
181
182  def AddSyntheticDelay(self, delay):
183    assert _delay_re.match(delay)
184    self._synthetic_delays.add(delay)
185
186  def IsSubset(self, other):
187    """ Determine if filter A (self) is a subset of filter B (other).
188        Returns True if A is a subset of B, False if A is not a subset of B,
189        and None if we can't tell for sure.
190    """
191    # We don't handle filters with wildcards in this test.
192    if self.contains_wildcards or other.contains_wildcards:
193      return None
194
195    # Disabled categories get into a trace if and only if they are contained in
196    # the 'disabled' set. Return False if A's disabled set is not a subset of
197    # B's disabled set.
198    if not self.disabled_by_default_categories <= \
199       other.disabled_by_default_categories:
200      return False
201
202    # If A defines more or different synthetic delays than B, then A is not a
203    # subset.
204    if not self.synthetic_delays <= other.synthetic_delays:
205      return False
206
207    if self.included_categories and other.included_categories:
208      # A and B have explicit include lists. If A includes something that B
209      # doesn't, return False.
210      if not self.included_categories <= other.included_categories:
211        return False
212    elif self.included_categories:
213      # Only A has an explicit include list. If A includes something that B
214      # excludes, return False.
215      if self.included_categories.intersection(other.excluded_categories):
216        return False
217    elif other.included_categories:
218      # Only B has an explicit include list. We don't know which categories are
219      # contained in the default list, so return None.
220      return None
221    else:
222      # None of the filter have explicit include list. If B excludes categories
223      # that A doesn't exclude, return False.
224      if not other.excluded_categories <= self.excluded_categories:
225        return False
226
227    return True
228