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.
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'
34 """Represents one matching rule in a policy file."""
39 stackfunction_pattern
=None,
40 stacksourcefile_pattern
=None,
41 typeinfo_pattern
=None,
42 mappedpathname_pattern
=None,
43 mappedpermission_pattern
=None,
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
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')
73 self
._sharedwith
= sharedwith
80 def allocator_type(self
):
81 return self
._allocator
_type
84 def stackfunction_pattern(self
):
85 return self
._stackfunction
_pattern
88 def stacksourcefile_pattern(self
):
89 return self
._stacksourcefile
_pattern
92 def typeinfo_pattern(self
):
93 return self
._typeinfo
_pattern
96 def mappedpathname_pattern(self
):
97 return self
._mappedpathname
_pattern
100 def mappedpermission_pattern(self
):
101 return self
._mappedpermission
_pattern
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
):
113 self
._version
= version
114 self
._components
= components
125 def components(self
):
126 return self
._components
128 def find_rule(self
, component_name
, after_rule
=None):
129 """Finds a rule whose name is |component_name|.
131 If there are multiple rules with same component name,
132 use |after_rule| to search the rule after it.
134 found_after_rule
= False
135 for rule
in self
._rules
:
136 if after_rule
and not found_after_rule
:
137 if rule
== after_rule
:
138 found_after_rule
= True
141 if rule
.name
== component_name
:
145 def find_malloc(self
, bucket
):
146 """Finds a matching component name which a given |bucket| belongs to.
149 bucket: A Bucket object to be searched for.
152 A string representing a component name.
154 assert not bucket
or bucket
.allocator_type
== 'malloc'
158 if bucket
.component_cache
:
159 return bucket
.component_cache
161 stackfunction
= bucket
.symbolized_joined_stackfunction
162 stacksourcefile
= bucket
.symbolized_joined_stacksourcefile
163 typeinfo
= bucket
.symbolized_typeinfo
164 if typeinfo
.startswith('0x'):
165 typeinfo
= bucket
.typeinfo_name
167 for rule
in self
._rules
:
168 if (rule
.allocator_type
== 'malloc' and
169 (not rule
.stackfunction_pattern
or
170 rule
.stackfunction_pattern
.match(stackfunction
)) and
171 (not rule
.stacksourcefile_pattern
or
172 rule
.stacksourcefile_pattern
.match(stacksourcefile
)) and
173 (not rule
.typeinfo_pattern
or rule
.typeinfo_pattern
.match(typeinfo
))):
174 bucket
.component_cache
= rule
.name
179 def find_mmap(self
, region
, bucket_set
,
180 pageframe
=None, group_pfn_counts
=None):
181 """Finds a matching component which a given mmap |region| belongs to.
183 It uses |bucket_set| to match with backtraces. If |pageframe| is given,
184 it considers memory sharing among processes.
186 NOTE: Don't use Bucket's |component_cache| for mmap regions because they're
187 classified not only with bucket information (mappedpathname for example).
190 region: A tuple representing a memory region.
191 bucket_set: A BucketSet object to look up backtraces.
192 pageframe: A PageFrame object representing a pageframe maybe including
194 group_pfn_counts: A dict mapping a PFN to the number of times the
195 the pageframe is mapped by the known "group (Chrome)" processes.
198 A string representing a component name.
200 assert region
[0] == 'hooked'
201 bucket
= bucket_set
.get(region
[1]['bucket_id'])
202 assert not bucket
or bucket
.allocator_type
== 'mmap'
205 return 'no-bucket', None
207 stackfunction
= bucket
.symbolized_joined_stackfunction
208 stacksourcefile
= bucket
.symbolized_joined_stacksourcefile
209 sharedwith
= self
._categorize
_pageframe
(pageframe
, group_pfn_counts
)
211 for rule
in self
._rules
:
212 if (rule
.allocator_type
== 'mmap' and
213 (not rule
.stackfunction_pattern
or
214 rule
.stackfunction_pattern
.match(stackfunction
)) and
215 (not rule
.stacksourcefile_pattern
or
216 rule
.stacksourcefile_pattern
.match(stacksourcefile
)) and
217 (not rule
.mappedpathname_pattern
or
218 rule
.mappedpathname_pattern
.match(region
[1]['vma']['name'])) and
219 (not rule
.mappedpermission_pattern
or
220 rule
.mappedpermission_pattern
.match(
221 region
[1]['vma']['readable'] +
222 region
[1]['vma']['writable'] +
223 region
[1]['vma']['executable'] +
224 region
[1]['vma']['private'])) and
225 (not rule
.sharedwith
or
226 not pageframe
or sharedwith
in rule
.sharedwith
)):
227 return rule
.name
, bucket
231 def find_unhooked(self
, region
, pageframe
=None, group_pfn_counts
=None):
232 """Finds a matching component which a given unhooked |region| belongs to.
234 If |pageframe| is given, it considers memory sharing among processes.
237 region: A tuple representing a memory region.
238 pageframe: A PageFrame object representing a pageframe maybe including
240 group_pfn_counts: A dict mapping a PFN to the number of times the
241 the pageframe is mapped by the known "group (Chrome)" processes.
244 A string representing a component name.
246 assert region
[0] == 'unhooked'
247 sharedwith
= self
._categorize
_pageframe
(pageframe
, group_pfn_counts
)
249 for rule
in self
._rules
:
250 if (rule
.allocator_type
== 'unhooked' and
251 (not rule
.mappedpathname_pattern
or
252 rule
.mappedpathname_pattern
.match(region
[1]['vma']['name'])) and
253 (not rule
.mappedpermission_pattern
or
254 rule
.mappedpermission_pattern
.match(
255 region
[1]['vma']['readable'] +
256 region
[1]['vma']['writable'] +
257 region
[1]['vma']['executable'] +
258 region
[1]['vma']['private'])) and
259 (not rule
.sharedwith
or
260 not pageframe
or sharedwith
in rule
.sharedwith
)):
266 def load(filename
, filetype
):
267 """Loads a policy file of |filename| in a |format|.
270 filename: A filename to be loaded.
271 filetype: A string to specify a type of the file. Only 'json' is
275 A loaded Policy object.
277 with
open(os
.path
.join(BASE_PATH
, filename
)) as policy_f
:
278 return Policy
.parse(policy_f
, filetype
)
281 def parse(policy_f
, filetype
):
282 """Parses a policy file content in a |format|.
285 policy_f: An IO object to be loaded.
286 filetype: A string to specify a type of the file. Only 'json' is
290 A loaded Policy object.
292 if filetype
== 'json':
293 return Policy
._parse
_json
(policy_f
)
297 JSON_COMMENT_REGEX
= re
.compile(r
'//.*')
300 def _parse_json(policy_f
):
301 """Parses policy file in json format.
303 A policy file contains component's names and their stacktrace pattern
304 written in regular expression. Those patterns are matched against each
305 symbols of each stacktraces in the order written in the policy file
308 policy_f: A File/IO object to read.
311 A loaded policy object.
313 policy_json
= policy_f
.read()
314 policy_json
= re
.sub(Policy
.JSON_COMMENT_REGEX
, '', policy_json
)
315 policy
= json
.loads(policy_json
)
318 for rule
in policy
['rules']:
319 stackfunction
= rule
.get('stackfunction') or rule
.get('stacktrace')
320 stacksourcefile
= rule
.get('stacksourcefile')
323 rule
['allocator'], # allocator_type
326 rule
['typeinfo'] if 'typeinfo' in rule
else None,
327 rule
.get('mappedpathname'),
328 rule
.get('mappedpermission'),
329 rule
.get('sharedwith')))
331 return Policy(rules
, policy
['version'], policy
['components'])
334 def _categorize_pageframe(pageframe
, group_pfn_counts
):
335 """Categorizes a pageframe based on its sharing status.
338 'private' if |pageframe| is not shared with other processes. 'group'
339 if |pageframe| is shared only with group (Chrome-related) processes.
340 'others' if |pageframe| is shared with non-group processes.
345 if pageframe
.pagecount
:
346 if pageframe
.pagecount
== 1:
348 elif pageframe
.pagecount
<= group_pfn_counts
.get(pageframe
.pfn
, 0) + 1:
353 if pageframe
.pfn
in group_pfn_counts
:
359 class PolicySet(object):
360 """Represents a set of policies."""
362 def __init__(self
, policy_directory
):
363 self
._policy
_directory
= policy_directory
366 def load(labels
=None):
367 """Loads a set of policies via the "default policy directory".
369 The "default policy directory" contains pairs of policies and their labels.
370 For example, a policy "policy.l0.json" is labeled "l0" in the default
371 policy directory "policies.json".
373 All policies in the directory are loaded by default. Policies can be
377 labels: An array that contains policy labels to be loaded.
382 default_policy_directory
= PolicySet
._load
_default
_policy
_directory
()
384 specified_policy_directory
= {}
386 if label
in default_policy_directory
:
387 specified_policy_directory
[label
] = default_policy_directory
[label
]
388 # TODO(dmikurube): Load an un-labeled policy file.
389 return PolicySet
._load
_policies
(specified_policy_directory
)
391 return PolicySet
._load
_policies
(default_policy_directory
)
394 return len(self
._policy
_directory
)
397 for label
in self
._policy
_directory
:
400 def __getitem__(self
, label
):
401 return self
._policy
_directory
[label
]
404 def _load_default_policy_directory():
405 with
open(POLICIES_JSON_PATH
, mode
='r') as policies_f
:
406 default_policy_directory
= json
.load(policies_f
)
407 return default_policy_directory
410 def _load_policies(directory
):
411 LOGGER
.info('Loading policy files.')
413 for label
in directory
:
414 LOGGER
.info(' %s: %s' % (label
, directory
[label
]['file']))
415 loaded
= Policy
.load(directory
[label
]['file'], directory
[label
]['format'])
417 policies
[label
] = loaded
418 return PolicySet(policies
)