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.
10 from lib
.pageframe
import PFNCounts
11 from lib
.policy
import PolicySet
12 from lib
.subcommand
import SubCommand
15 LOGGER
= logging
.getLogger('dmprof')
18 class PolicyCommands(SubCommand
):
19 def __init__(self
, command
):
20 super(PolicyCommands
, self
).__init
__(
21 'Usage: %%prog %s [-p POLICY] <first-dump> [shared-first-dumps...]' %
23 self
._parser
.add_option('-p', '--policy', type='string', dest
='policy',
24 help='profile with POLICY', metavar
='POLICY')
25 self
._parser
.add_option('--alternative-dirs', dest
='alternative_dirs',
26 metavar
='/path/on/target@/path/on/host[:...]',
27 help='Read files in /path/on/host/ instead of '
28 'files in /path/on/target/.')
29 self
._parser
.add_option('--timestamp', dest
='timestamp',
30 action
='store_true', help='Use timestamp.')
31 self
._timestamp
= False
33 def _set_up(self
, sys_argv
):
34 options
, args
= self
._parse
_args
(sys_argv
, 1)
36 shared_first_dump_paths
= args
[2:]
37 alternative_dirs_dict
= {}
38 if options
.alternative_dirs
:
39 for alternative_dir_pair
in options
.alternative_dirs
.split(':'):
40 target_path
, host_path
= alternative_dir_pair
.split('@', 1)
41 alternative_dirs_dict
[target_path
] = host_path
42 (bucket_set
, dumps
) = SubCommand
.load_basic_files(
43 dump_path
, True, alternative_dirs
=alternative_dirs_dict
)
45 self
._timestamp
= options
.timestamp
48 for shared_first_dump_path
in shared_first_dump_paths
:
49 shared_dumps
= SubCommand
._find
_all
_dumps
(shared_first_dump_path
)
50 for shared_dump
in shared_dumps
:
51 pfn_counts
= PFNCounts
.load(shared_dump
)
52 if pfn_counts
.pid
not in pfn_counts_dict
:
53 pfn_counts_dict
[pfn_counts
.pid
] = []
54 pfn_counts_dict
[pfn_counts
.pid
].append(pfn_counts
)
56 policy_set
= PolicySet
.load(SubCommand
._parse
_policy
_list
(options
.policy
))
57 return policy_set
, dumps
, pfn_counts_dict
, bucket_set
59 def _apply_policy(self
, dump
, pfn_counts_dict
, policy
, bucket_set
,
61 """Aggregates the total memory size of each component.
63 Iterate through all stacktraces and attribute them to one of the components
64 based on the policy. It is important to apply policy in right order.
68 pfn_counts_dict: A dict mapping a pid to a list of PFNCounts.
69 policy: A Policy object.
70 bucket_set: A BucketSet object.
71 first_dump_time: An integer representing time when the first dump is
75 A dict mapping components and their corresponding sizes.
77 LOGGER
.info(' %s' % dump
.path
)
80 LOGGER
.info(' shared with...')
81 for pid
, pfnset_list
in pfn_counts_dict
.iteritems():
82 closest_pfnset_index
= None
83 closest_pfnset_difference
= 1024.0
84 for index
, pfnset
in enumerate(pfnset_list
):
85 time_difference
= pfnset
.time
- dump
.time
86 if time_difference
>= 3.0:
88 elif ((time_difference
< 0.0 and pfnset
.reason
!= 'Exiting') or
89 (0.0 <= time_difference
and time_difference
< 3.0)):
90 closest_pfnset_index
= index
91 closest_pfnset_difference
= time_difference
92 elif time_difference
< 0.0 and pfnset
.reason
== 'Exiting':
93 closest_pfnset_index
= None
95 if closest_pfnset_index
:
96 for pfn
, count
in pfnset_list
[closest_pfnset_index
].iter_pfn
:
97 all_pfn_dict
[pfn
] = all_pfn_dict
.get(pfn
, 0) + count
98 LOGGER
.info(' %s (time difference = %f)' %
99 (pfnset_list
[closest_pfnset_index
].path
,
100 closest_pfnset_difference
))
102 LOGGER
.info(' (no match with pid:%d)' % pid
)
104 sizes
= dict((c
, 0) for c
in policy
.components
)
106 PolicyCommands
._accumulate
_malloc
(dump
, policy
, bucket_set
, sizes
)
107 verify_global_stats
= PolicyCommands
._accumulate
_maps
(
108 dump
, all_pfn_dict
, policy
, bucket_set
, sizes
)
110 # TODO(dmikurube): Remove the verifying code when GLOBAL_STATS is removed.
111 # http://crbug.com/245603.
112 for verify_key
, verify_value
in verify_global_stats
.iteritems():
113 dump_value
= dump
.global_stat('%s_committed' % verify_key
)
114 if dump_value
!= verify_value
:
115 LOGGER
.warn('%25s: %12d != %d (%d)' % (
116 verify_key
, dump_value
, verify_value
, dump_value
- verify_value
))
118 sizes
['mmap-no-log'] = (
119 dump
.global_stat('profiled-mmap_committed') -
120 sizes
['mmap-total-log'])
121 sizes
['mmap-total-record'] = dump
.global_stat('profiled-mmap_committed')
122 sizes
['mmap-total-record-vm'] = dump
.global_stat('profiled-mmap_virtual')
124 sizes
['tc-no-log'] = (
125 dump
.global_stat('profiled-malloc_committed') -
126 sizes
['tc-total-log'])
127 sizes
['tc-total-record'] = dump
.global_stat('profiled-malloc_committed')
128 sizes
['tc-unused'] = (
129 sizes
['mmap-tcmalloc'] -
130 dump
.global_stat('profiled-malloc_committed'))
131 if sizes
['tc-unused'] < 0:
132 LOGGER
.warn(' Assuming tc-unused=0 as it is negative: %d (bytes)' %
134 sizes
['tc-unused'] = 0
135 sizes
['tc-total'] = sizes
['mmap-tcmalloc']
137 # TODO(dmikurube): global_stat will be deprecated.
138 # See http://crbug.com/245603.
140 'total': 'total_committed',
141 'filemapped': 'file_committed',
142 'absent': 'absent_committed',
143 'file-exec': 'file-exec_committed',
144 'file-nonexec': 'file-nonexec_committed',
145 'anonymous': 'anonymous_committed',
146 'stack': 'stack_committed',
147 'other': 'other_committed',
148 'unhooked-absent': 'nonprofiled-absent_committed',
149 'total-vm': 'total_virtual',
150 'filemapped-vm': 'file_virtual',
151 'anonymous-vm': 'anonymous_virtual',
152 'other-vm': 'other_virtual' }.iteritems():
154 sizes
[key
] = dump
.global_stat(value
)
156 if 'mustbezero' in sizes
:
158 'profiled-mmap_committed',
159 'nonprofiled-absent_committed',
160 'nonprofiled-anonymous_committed',
161 'nonprofiled-file-exec_committed',
162 'nonprofiled-file-nonexec_committed',
163 'nonprofiled-stack_committed',
164 'nonprofiled-other_committed')
165 sizes
['mustbezero'] = (
166 dump
.global_stat('total_committed') -
167 sum(dump
.global_stat(removed
) for removed
in removed_list
))
168 if 'total-exclude-profiler' in sizes
:
169 sizes
['total-exclude-profiler'] = (
170 dump
.global_stat('total_committed') -
171 (sizes
['mmap-profiler'] + sizes
['mmap-type-profiler']))
173 sizes
['hour'] = (dump
.time
- first_dump_time
) / 60.0 / 60.0
174 if 'minute' in sizes
:
175 sizes
['minute'] = (dump
.time
- first_dump_time
) / 60.0
176 if 'second' in sizes
:
178 sizes
['second'] = datetime
.datetime
.fromtimestamp(dump
.time
).isoformat()
180 sizes
['second'] = dump
.time
- first_dump_time
185 def _accumulate_malloc(dump
, policy
, bucket_set
, sizes
):
186 for bucket_id
, _
, committed
, _
, _
in dump
.iter_stacktrace
:
187 bucket
= bucket_set
.get(bucket_id
)
188 if not bucket
or bucket
.allocator_type
== 'malloc':
189 component_match
= policy
.find_malloc(bucket
)
190 elif bucket
.allocator_type
== 'mmap':
194 sizes
[component_match
] += committed
196 assert not component_match
.startswith('mmap-')
197 if component_match
.startswith('tc-'):
198 sizes
['tc-total-log'] += committed
200 sizes
['other-total-log'] += committed
203 def _accumulate_maps(dump
, pfn_dict
, policy
, bucket_set
, sizes
):
204 # TODO(dmikurube): Remove the dict when GLOBAL_STATS is removed.
205 # http://crbug.com/245603.
213 'nonprofiled-file-exec': 0,
214 'nonprofiled-file-nonexec': 0,
215 'nonprofiled-anonymous': 0,
216 'nonprofiled-stack': 0,
217 'nonprofiled-other': 0,
221 for key
, value
in dump
.iter_map
:
222 # TODO(dmikurube): Remove the subtotal code when GLOBAL_STATS is removed.
223 # It's temporary verification code for transition described in
224 # http://crbug.com/245603.
226 if 'committed' in value
[1]:
227 committed
= value
[1]['committed']
228 global_stats
['total'] += committed
230 name
= value
[1]['vma']['name']
231 if name
.startswith('/'):
232 if value
[1]['vma']['executable'] == 'x':
236 elif name
== '[stack]':
240 global_stats
[key
] += committed
241 if value
[0] == 'unhooked':
242 global_stats
['nonprofiled-' + key
] += committed
243 if value
[0] == 'hooked':
244 global_stats
['profiled-mmap'] += committed
246 if value
[0] == 'unhooked':
247 if pfn_dict
and dump
.pageframe_length
:
248 for pageframe
in value
[1]['pageframe']:
249 component_match
= policy
.find_unhooked(value
, pageframe
, pfn_dict
)
250 sizes
[component_match
] += pageframe
.size
252 component_match
= policy
.find_unhooked(value
)
253 sizes
[component_match
] += int(value
[1]['committed'])
254 elif value
[0] == 'hooked':
255 if pfn_dict
and dump
.pageframe_length
:
256 for pageframe
in value
[1]['pageframe']:
257 component_match
, _
= policy
.find_mmap(
258 value
, bucket_set
, pageframe
, pfn_dict
)
259 sizes
[component_match
] += pageframe
.size
260 assert not component_match
.startswith('tc-')
261 if component_match
.startswith('mmap-'):
262 sizes
['mmap-total-log'] += pageframe
.size
264 sizes
['other-total-log'] += pageframe
.size
266 component_match
, _
= policy
.find_mmap(value
, bucket_set
)
267 sizes
[component_match
] += int(value
[1]['committed'])
268 if component_match
.startswith('mmap-'):
269 sizes
['mmap-total-log'] += int(value
[1]['committed'])
271 sizes
['other-total-log'] += int(value
[1]['committed'])
273 LOGGER
.error('Unrecognized mapping status: %s' % value
[0])
278 class CSVCommand(PolicyCommands
):
280 super(CSVCommand
, self
).__init
__('csv')
282 def do(self
, sys_argv
):
283 policy_set
, dumps
, pfn_counts_dict
, bucket_set
= self
._set
_up
(sys_argv
)
285 policy_set
, dumps
, pfn_counts_dict
, bucket_set
, sys
.stdout
)
287 def _output(self
, policy_set
, dumps
, pfn_counts_dict
, bucket_set
, out
):
289 for label
in policy_set
:
290 max_components
= max(max_components
, len(policy_set
[label
].components
))
292 for label
in sorted(policy_set
):
293 components
= policy_set
[label
].components
294 if len(policy_set
) > 1:
295 out
.write('%s%s\n' % (label
, ',' * (max_components
- 1)))
296 out
.write('%s%s\n' % (
297 ','.join(components
), ',' * (max_components
- len(components
))))
299 LOGGER
.info('Applying a policy %s to...' % label
)
300 for index
, dump
in enumerate(dumps
):
302 first_dump_time
= dump
.time
303 component_sizes
= self
._apply
_policy
(
304 dump
, pfn_counts_dict
, policy_set
[label
], bucket_set
,
308 if c
in ('hour', 'minute', 'second'):
309 if isinstance(component_sizes
[c
], str):
310 s
.append('%s' % component_sizes
[c
])
312 s
.append('%05.5f' % (component_sizes
[c
]))
314 s
.append('%05.5f' % (component_sizes
[c
] / 1024.0 / 1024.0))
315 out
.write('%s%s\n' % (
316 ','.join(s
), ',' * (max_components
- len(components
))))
318 bucket_set
.clear_component_cache()
323 class JSONCommand(PolicyCommands
):
325 super(JSONCommand
, self
).__init
__('json')
327 def do(self
, sys_argv
):
328 policy_set
, dumps
, pfn_counts_dict
, bucket_set
= self
._set
_up
(sys_argv
)
330 policy_set
, dumps
, pfn_counts_dict
, bucket_set
, sys
.stdout
)
332 def _output(self
, policy_set
, dumps
, pfn_counts_dict
, bucket_set
, out
):
334 'version': 'JSON_DEEP_2',
338 for label
in sorted(policy_set
):
339 json_base
['policies'][label
] = {
340 'legends': policy_set
[label
].components
,
344 LOGGER
.info('Applying a policy %s to...' % label
)
345 for index
, dump
in enumerate(dumps
):
347 first_dump_time
= dump
.time
348 component_sizes
= self
._apply
_policy
(
349 dump
, pfn_counts_dict
, policy_set
[label
], bucket_set
,
351 component_sizes
['dump_path'] = dump
.path
352 component_sizes
['dump_time'] = datetime
.datetime
.fromtimestamp(
353 dump
.time
).strftime('%Y-%m-%d %H:%M:%S')
354 json_base
['policies'][label
]['snapshots'].append(component_sizes
)
356 bucket_set
.clear_component_cache()
358 json
.dump(json_base
, out
, indent
=2, sort_keys
=True)
363 class ListCommand(PolicyCommands
):
365 super(ListCommand
, self
).__init
__('list')
367 def do(self
, sys_argv
):
368 policy_set
, dumps
, pfn_counts_dict
, bucket_set
= self
._set
_up
(sys_argv
)
370 policy_set
, dumps
, pfn_counts_dict
, bucket_set
, sys
.stdout
)
372 def _output(self
, policy_set
, dumps
, pfn_counts_dict
, bucket_set
, out
):
373 for label
in sorted(policy_set
):
374 LOGGER
.info('Applying a policy %s to...' % label
)
376 component_sizes
= self
._apply
_policy
(
377 dump
, pfn_counts_dict
, policy_set
[label
], bucket_set
, dump
.time
)
378 out
.write('%s for %s:\n' % (label
, dump
.path
))
379 for c
in policy_set
[label
].components
:
380 if c
in ['hour', 'minute', 'second']:
381 out
.write('%40s %12.3f\n' % (c
, component_sizes
[c
]))
383 out
.write('%40s %12d\n' % (c
, component_sizes
[c
]))
385 bucket_set
.clear_component_cache()