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