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