Fix import error in mac_platform_backend.py
[chromium-blink-merge.git] / tools / deep_memory_profiler / lib / policy.py
blobd7a38977eb702ccb0058f6a5850e01a96a5b7ceb
1 # Copyright 2013 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.
5 import json
6 import logging
7 import os
8 import re
11 LOGGER = logging.getLogger('dmprof')
13 BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 POLICIES_JSON_PATH = os.path.join(BASE_PATH, 'policies.json')
16 # Heap Profile Policy versions
18 # POLICY_DEEP_1 DOES NOT include allocation_type columns.
19 # mmap regions are distincted w/ mmap frames in the pattern column.
20 POLICY_DEEP_1 = 'POLICY_DEEP_1'
22 # POLICY_DEEP_2 DOES include allocation_type columns.
23 # mmap regions are distincted w/ the allocation_type column.
24 POLICY_DEEP_2 = 'POLICY_DEEP_2'
26 # POLICY_DEEP_3 is in JSON format.
27 POLICY_DEEP_3 = 'POLICY_DEEP_3'
29 # POLICY_DEEP_3 contains typeinfo.
30 POLICY_DEEP_4 = 'POLICY_DEEP_4'
33 class Rule(object):
34 """Represents one matching rule in a policy file."""
36 def __init__(self,
37 name,
38 allocator_type,
39 stackfunction_pattern=None,
40 stacksourcefile_pattern=None,
41 typeinfo_pattern=None,
42 mappedpathname_pattern=None,
43 mappedpermission_pattern=None,
44 sharedwith=None):
45 self._name = name
46 self._allocator_type = allocator_type
48 self._stackfunction_pattern = None
49 if stackfunction_pattern:
50 self._stackfunction_pattern = re.compile(
51 stackfunction_pattern + r'\Z')
53 self._stacksourcefile_pattern = None
54 if stacksourcefile_pattern:
55 self._stacksourcefile_pattern = re.compile(
56 stacksourcefile_pattern + r'\Z')
58 self._typeinfo_pattern = None
59 if typeinfo_pattern:
60 self._typeinfo_pattern = re.compile(typeinfo_pattern + r'\Z')
62 self._mappedpathname_pattern = None
63 if mappedpathname_pattern:
64 self._mappedpathname_pattern = re.compile(mappedpathname_pattern + r'\Z')
66 self._mappedpermission_pattern = None
67 if mappedpermission_pattern:
68 self._mappedpermission_pattern = re.compile(
69 mappedpermission_pattern + r'\Z')
71 self._sharedwith = []
72 if sharedwith:
73 self._sharedwith = sharedwith
75 @property
76 def name(self):
77 return self._name
79 @property
80 def allocator_type(self):
81 return self._allocator_type
83 @property
84 def stackfunction_pattern(self):
85 return self._stackfunction_pattern
87 @property
88 def stacksourcefile_pattern(self):
89 return self._stacksourcefile_pattern
91 @property
92 def typeinfo_pattern(self):
93 return self._typeinfo_pattern
95 @property
96 def mappedpathname_pattern(self):
97 return self._mappedpathname_pattern
99 @property
100 def mappedpermission_pattern(self):
101 return self._mappedpermission_pattern
103 @property
104 def sharedwith(self):
105 return self._sharedwith
108 class Policy(object):
109 """Represents a policy, a content of a policy file."""
111 def __init__(self, rules, version, components):
112 self._rules = rules
113 self._version = version
114 self._components = components
116 @property
117 def rules(self):
118 return self._rules
120 @property
121 def version(self):
122 return self._version
124 @property
125 def components(self):
126 return self._components
128 def find_rule(self, component_name):
129 """Finds a rule whose name is |component_name|. """
130 for rule in self._rules:
131 if rule.name == component_name:
132 return rule
133 return None
135 def find_malloc(self, bucket):
136 """Finds a matching component name which a given |bucket| belongs to.
138 Args:
139 bucket: A Bucket object to be searched for.
141 Returns:
142 A string representing a component name.
144 assert not bucket or bucket.allocator_type == 'malloc'
146 if not bucket:
147 return 'no-bucket'
148 if bucket.component_cache:
149 return bucket.component_cache
151 stackfunction = bucket.symbolized_joined_stackfunction
152 stacksourcefile = bucket.symbolized_joined_stacksourcefile
153 typeinfo = bucket.symbolized_typeinfo
154 if typeinfo.startswith('0x'):
155 typeinfo = bucket.typeinfo_name
157 for rule in self._rules:
158 if (rule.allocator_type == 'malloc' and
159 (not rule.stackfunction_pattern or
160 rule.stackfunction_pattern.match(stackfunction)) and
161 (not rule.stacksourcefile_pattern or
162 rule.stacksourcefile_pattern.match(stacksourcefile)) and
163 (not rule.typeinfo_pattern or rule.typeinfo_pattern.match(typeinfo))):
164 bucket.component_cache = rule.name
165 return rule.name
167 assert False
169 def find_mmap(self, region, bucket_set,
170 pageframe=None, group_pfn_counts=None):
171 """Finds a matching component which a given mmap |region| belongs to.
173 It uses |bucket_set| to match with backtraces. If |pageframe| is given,
174 it considers memory sharing among processes.
176 NOTE: Don't use Bucket's |component_cache| for mmap regions because they're
177 classified not only with bucket information (mappedpathname for example).
179 Args:
180 region: A tuple representing a memory region.
181 bucket_set: A BucketSet object to look up backtraces.
182 pageframe: A PageFrame object representing a pageframe maybe including
183 a pagecount.
184 group_pfn_counts: A dict mapping a PFN to the number of times the
185 the pageframe is mapped by the known "group (Chrome)" processes.
187 Returns:
188 A string representing a component name.
190 assert region[0] == 'hooked'
191 bucket = bucket_set.get(region[1]['bucket_id'])
192 assert not bucket or bucket.allocator_type == 'mmap'
194 if not bucket:
195 return 'no-bucket', None
197 stackfunction = bucket.symbolized_joined_stackfunction
198 stacksourcefile = bucket.symbolized_joined_stacksourcefile
199 sharedwith = self._categorize_pageframe(pageframe, group_pfn_counts)
201 for rule in self._rules:
202 if (rule.allocator_type == 'mmap' and
203 (not rule.stackfunction_pattern or
204 rule.stackfunction_pattern.match(stackfunction)) and
205 (not rule.stacksourcefile_pattern or
206 rule.stacksourcefile_pattern.match(stacksourcefile)) and
207 (not rule.mappedpathname_pattern or
208 rule.mappedpathname_pattern.match(region[1]['vma']['name'])) and
209 (not rule.mappedpermission_pattern or
210 rule.mappedpermission_pattern.match(
211 region[1]['vma']['readable'] +
212 region[1]['vma']['writable'] +
213 region[1]['vma']['executable'] +
214 region[1]['vma']['private'])) and
215 (not rule.sharedwith or
216 not pageframe or sharedwith in rule.sharedwith)):
217 return rule.name, bucket
219 assert False
221 def find_unhooked(self, region, pageframe=None, group_pfn_counts=None):
222 """Finds a matching component which a given unhooked |region| belongs to.
224 If |pageframe| is given, it considers memory sharing among processes.
226 Args:
227 region: A tuple representing a memory region.
228 pageframe: A PageFrame object representing a pageframe maybe including
229 a pagecount.
230 group_pfn_counts: A dict mapping a PFN to the number of times the
231 the pageframe is mapped by the known "group (Chrome)" processes.
233 Returns:
234 A string representing a component name.
236 assert region[0] == 'unhooked'
237 sharedwith = self._categorize_pageframe(pageframe, group_pfn_counts)
239 for rule in self._rules:
240 if (rule.allocator_type == 'unhooked' and
241 (not rule.mappedpathname_pattern or
242 rule.mappedpathname_pattern.match(region[1]['vma']['name'])) and
243 (not rule.mappedpermission_pattern or
244 rule.mappedpermission_pattern.match(
245 region[1]['vma']['readable'] +
246 region[1]['vma']['writable'] +
247 region[1]['vma']['executable'] +
248 region[1]['vma']['private'])) and
249 (not rule.sharedwith or
250 not pageframe or sharedwith in rule.sharedwith)):
251 return rule.name
253 assert False
255 @staticmethod
256 def load(filename, filetype):
257 """Loads a policy file of |filename| in a |format|.
259 Args:
260 filename: A filename to be loaded.
261 filetype: A string to specify a type of the file. Only 'json' is
262 supported for now.
264 Returns:
265 A loaded Policy object.
267 with open(os.path.join(BASE_PATH, filename)) as policy_f:
268 return Policy.parse(policy_f, filetype)
270 @staticmethod
271 def parse(policy_f, filetype):
272 """Parses a policy file content in a |format|.
274 Args:
275 policy_f: An IO object to be loaded.
276 filetype: A string to specify a type of the file. Only 'json' is
277 supported for now.
279 Returns:
280 A loaded Policy object.
282 if filetype == 'json':
283 return Policy._parse_json(policy_f)
284 else:
285 return None
287 @staticmethod
288 def _parse_json(policy_f):
289 """Parses policy file in json format.
291 A policy file contains component's names and their stacktrace pattern
292 written in regular expression. Those patterns are matched against each
293 symbols of each stacktraces in the order written in the policy file
295 Args:
296 policy_f: A File/IO object to read.
298 Returns:
299 A loaded policy object.
301 policy = json.load(policy_f)
303 rules = []
304 for rule in policy['rules']:
305 stackfunction = rule.get('stackfunction') or rule.get('stacktrace')
306 stacksourcefile = rule.get('stacksourcefile')
307 rules.append(Rule(
308 rule['name'],
309 rule['allocator'], # allocator_type
310 stackfunction,
311 stacksourcefile,
312 rule['typeinfo'] if 'typeinfo' in rule else None,
313 rule.get('mappedpathname'),
314 rule.get('mappedpermission'),
315 rule.get('sharedwith')))
317 return Policy(rules, policy['version'], policy['components'])
319 @staticmethod
320 def _categorize_pageframe(pageframe, group_pfn_counts):
321 """Categorizes a pageframe based on its sharing status.
323 Returns:
324 'private' if |pageframe| is not shared with other processes. 'group'
325 if |pageframe| is shared only with group (Chrome-related) processes.
326 'others' if |pageframe| is shared with non-group processes.
328 if not pageframe:
329 return 'private'
331 if pageframe.pagecount:
332 if pageframe.pagecount == 1:
333 return 'private'
334 elif pageframe.pagecount <= group_pfn_counts.get(pageframe.pfn, 0) + 1:
335 return 'group'
336 else:
337 return 'others'
338 else:
339 if pageframe.pfn in group_pfn_counts:
340 return 'group'
341 else:
342 return 'private'
345 class PolicySet(object):
346 """Represents a set of policies."""
348 def __init__(self, policy_directory):
349 self._policy_directory = policy_directory
351 @staticmethod
352 def load(labels=None):
353 """Loads a set of policies via the "default policy directory".
355 The "default policy directory" contains pairs of policies and their labels.
356 For example, a policy "policy.l0.json" is labeled "l0" in the default
357 policy directory "policies.json".
359 All policies in the directory are loaded by default. Policies can be
360 limited by |labels|.
362 Args:
363 labels: An array that contains policy labels to be loaded.
365 Returns:
366 A PolicySet object.
368 default_policy_directory = PolicySet._load_default_policy_directory()
369 if labels:
370 specified_policy_directory = {}
371 for label in labels:
372 if label in default_policy_directory:
373 specified_policy_directory[label] = default_policy_directory[label]
374 # TODO(dmikurube): Load an un-labeled policy file.
375 return PolicySet._load_policies(specified_policy_directory)
376 else:
377 return PolicySet._load_policies(default_policy_directory)
379 def __len__(self):
380 return len(self._policy_directory)
382 def __iter__(self):
383 for label in self._policy_directory:
384 yield label
386 def __getitem__(self, label):
387 return self._policy_directory[label]
389 @staticmethod
390 def _load_default_policy_directory():
391 with open(POLICIES_JSON_PATH, mode='r') as policies_f:
392 default_policy_directory = json.load(policies_f)
393 return default_policy_directory
395 @staticmethod
396 def _load_policies(directory):
397 LOGGER.info('Loading policy files.')
398 policies = {}
399 for label in directory:
400 LOGGER.info(' %s: %s' % (label, directory[label]['file']))
401 loaded = Policy.load(directory[label]['file'], directory[label]['format'])
402 if loaded:
403 policies[label] = loaded
404 return PolicySet(policies)