Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(450)

Side by Side Diff: build/android/pylib/symbols/elf_symbolizer.py

Issue 311443002: elf_symbolizer: Use a process for max 4000 lookups and then restart (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Recycling: Addressed review comments. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import collections 5 import collections
6 import datetime 6 import datetime
7 import logging 7 import logging
8 import multiprocessing 8 import multiprocessing
9 import os 9 import os
10 import posixpath 10 import posixpath
11 import Queue 11 import Queue
12 import re 12 import re
13 import subprocess 13 import subprocess
14 import sys 14 import sys
15 import threading 15 import threading
16 16
17 17
18 # addr2line builds a possibly infinite memory cache that can exhaust
19 # the computer's memory if allowed to grow for too long. This constant
20 # controls how many lookups we do before restarting the process. 4000
21 # gives near peak performance without extreme memory usage.
22 ADDR2LINE_RECYCLE_LIMIT = 4000
23
24
18 class ELFSymbolizer(object): 25 class ELFSymbolizer(object):
19 """An uber-fast (multiprocessing, pipelined and asynchronous) ELF symbolizer. 26 """An uber-fast (multiprocessing, pipelined and asynchronous) ELF symbolizer.
20 27
21 This class is a frontend for addr2line (part of GNU binutils), designed to 28 This class is a frontend for addr2line (part of GNU binutils), designed to
22 symbolize batches of large numbers of symbols for a given ELF file. It 29 symbolize batches of large numbers of symbols for a given ELF file. It
23 supports sharding symbolization against many addr2line instances and 30 supports sharding symbolization against many addr2line instances and
24 pipelining of multiple requests per each instance (in order to hide addr2line 31 pipelining of multiple requests per each instance (in order to hide addr2line
25 internals and OS pipe latencies). 32 internals and OS pipe latencies).
26 33
27 The interface exhibited by this class is a very simple asynchronous interface, 34 The interface exhibited by this class is a very simple asynchronous interface,
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 117
111 Args: 118 Args:
112 addr: address to symbolize. 119 addr: address to symbolize.
113 callback_arg: optional argument which will be passed to the |callback|.""" 120 callback_arg: optional argument which will be passed to the |callback|."""
114 assert(isinstance(addr, int)) 121 assert(isinstance(addr, int))
115 122
116 # Process all the symbols that have been resolved in the meanwhile. 123 # Process all the symbols that have been resolved in the meanwhile.
117 # Essentially, this drains all the addr2line(s) out queues. 124 # Essentially, this drains all the addr2line(s) out queues.
118 for a2l_to_purge in self._a2l_instances: 125 for a2l_to_purge in self._a2l_instances:
119 a2l_to_purge.ProcessAllResolvedSymbolsInQueue() 126 a2l_to_purge.ProcessAllResolvedSymbolsInQueue()
127 a2l_to_purge.RecycleIfNecessary()
120 128
121 # Find the best instance according to this logic: 129 # Find the best instance according to this logic:
122 # 1. Find an existing instance with the shortest queue. 130 # 1. Find an existing instance with the shortest queue.
123 # 2. If all of instances' queues are full, but there is room in the pool, 131 # 2. If all of instances' queues are full, but there is room in the pool,
124 # (i.e. < |max_concurrent_jobs|) create a new instance. 132 # (i.e. < |max_concurrent_jobs|) create a new instance.
125 # 3. If there were already |max_concurrent_jobs| instances and all of them 133 # 3. If there were already |max_concurrent_jobs| instances and all of them
126 # had full queues, make back-pressure. 134 # had full queues, make back-pressure.
127 135
128 # 1. 136 # 1.
129 def _SortByQueueSizeAndReqID(a2l): 137 def _SortByQueueSizeAndReqID(a2l):
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
177 self._lib_file_name = posixpath.basename(symbolizer.elf_file_path) 185 self._lib_file_name = posixpath.basename(symbolizer.elf_file_path)
178 186
179 # The request queue (i.e. addresses pushed to addr2line's stdin and not 187 # The request queue (i.e. addresses pushed to addr2line's stdin and not
180 # yet retrieved on stdout) 188 # yet retrieved on stdout)
181 self._request_queue = collections.deque() 189 self._request_queue = collections.deque()
182 190
183 # This is essentially len(self._request_queue). It has been optimized to a 191 # This is essentially len(self._request_queue). It has been optimized to a
184 # separate field because turned out to be a perf hot-spot. 192 # separate field because turned out to be a perf hot-spot.
185 self.queue_size = 0 193 self.queue_size = 0
186 194
195 # Keep track of the number of symbols a process has processed to
196 # avoid a single process growing too big and using all the memory.
197 self._processed_symbols_count = 0
Primiano Tucci (use gerrit) 2014/06/05 08:57:45 Move this hunk in _RestartAddr2LineProcess, so you
Daniel Bratell 2014/06/05 09:07:52 It made pylint unhappy: W0201:348,6:ELFSymbolizer.
Primiano Tucci (use gerrit) 2014/06/05 12:57:18 Oh right, you definitely need to define it in __in
198
187 # Objects required to handle the addr2line subprocess. 199 # Objects required to handle the addr2line subprocess.
188 self._proc = None # Subprocess.Popen(...) instance. 200 self._proc = None # Subprocess.Popen(...) instance.
189 self._thread = None # Threading.thread instance. 201 self._thread = None # Threading.thread instance.
190 self._out_queue = None # Queue.Queue instance (for buffering a2l stdout). 202 self._out_queue = None # Queue.Queue instance (for buffering a2l stdout).
191 self._RestartAddr2LineProcess() 203 self._RestartAddr2LineProcess()
192 204
193 def EnqueueRequest(self, addr, callback_arg): 205 def EnqueueRequest(self, addr, callback_arg):
194 """Pushes an address to addr2line's stdin (and keeps track of it).""" 206 """Pushes an address to addr2line's stdin (and keeps track of it)."""
195 self._symbolizer.requests_counter += 1 # For global "age" of requests. 207 self._symbolizer.requests_counter += 1 # For global "age" of requests.
196 req_idx = self._symbolizer.requests_counter 208 req_idx = self._symbolizer.requests_counter
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
244 """Consumes all the addr2line output lines produced (without blocking).""" 256 """Consumes all the addr2line output lines produced (without blocking)."""
245 if not self.queue_size: 257 if not self.queue_size:
246 return 258 return
247 while True: 259 while True:
248 try: 260 try:
249 lines = self._out_queue.get_nowait() 261 lines = self._out_queue.get_nowait()
250 except Queue.Empty: 262 except Queue.Empty:
251 break 263 break
252 self._ProcessSymbolOutput(lines) 264 self._ProcessSymbolOutput(lines)
253 265
266 def RecycleIfNecessary(self):
267 """Restarts the process if it has been used for too long.
268
269 A long running addr2line process will consume excessive amounts
270 of memory without any gain in performance."""
271 if self._processed_symbols_count >= ADDR2LINE_RECYCLE_LIMIT:
272 self._processed_symbols_count = 0
273 self._RestartAddr2LineProcess()
274
275
254 def Terminate(self): 276 def Terminate(self):
255 """Kills the underlying addr2line process. 277 """Kills the underlying addr2line process.
256 278
257 The poller |_thread| will terminate as well due to the broken pipe.""" 279 The poller |_thread| will terminate as well due to the broken pipe."""
258 try: 280 try:
259 self._proc.kill() 281 self._proc.kill()
260 self._proc.communicate() # Essentially wait() without risking deadlock. 282 self._proc.communicate() # Essentially wait() without risking deadlock.
261 except Exception: # An exception while terminating? How interesting. 283 except Exception: # An exception while terminating? How interesting.
262 pass 284 pass
263 self._proc = None 285 self._proc = None
(...skipping 26 matching lines...) Expand all
290 source_line = int(m.group(2)) 312 source_line = int(m.group(2))
291 else: 313 else:
292 logging.warning('Got invalid symbol path from addr2line: %s' % line2) 314 logging.warning('Got invalid symbol path from addr2line: %s' % line2)
293 315
294 sym_info = ELFSymbolInfo(name, source_path, source_line) 316 sym_info = ELFSymbolInfo(name, source_path, source_line)
295 if prev_sym_info: 317 if prev_sym_info:
296 prev_sym_info.inlined_by = sym_info 318 prev_sym_info.inlined_by = sym_info
297 if not innermost_sym_info: 319 if not innermost_sym_info:
298 innermost_sym_info = sym_info 320 innermost_sym_info = sym_info
299 321
322 self._processed_symbols_count += 1
300 self._symbolizer.callback(innermost_sym_info, callback_arg) 323 self._symbolizer.callback(innermost_sym_info, callback_arg)
301 324
302 def _RestartAddr2LineProcess(self): 325 def _RestartAddr2LineProcess(self):
303 if self._proc: 326 if self._proc:
304 self.Terminate() 327 self.Terminate()
305 328
306 # The only reason of existence of this Queue (and the corresponding 329 # The only reason of existence of this Queue (and the corresponding
307 # Thread below) is the lack of a subprocess.stdout.poll_avail_lines(). 330 # Thread below) is the lack of a subprocess.stdout.poll_avail_lines().
308 # Essentially this is a pipe able to extract a couple of lines atomically. 331 # Essentially this is a pipe able to extract a couple of lines atomically.
309 self._out_queue = Queue.Queue() 332 self._out_queue = Queue.Queue()
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 self.name = name 397 self.name = name
375 self.source_path = source_path 398 self.source_path = source_path
376 self.source_line = source_line 399 self.source_line = source_line
377 # In the case of |inlines|=True, the |inlined_by| points to the outer 400 # In the case of |inlines|=True, the |inlined_by| points to the outer
378 # function inlining the current one (and so on, to form a chain). 401 # function inlining the current one (and so on, to form a chain).
379 self.inlined_by = None 402 self.inlined_by = None
380 403
381 def __str__(self): 404 def __str__(self):
382 return '%s [%s:%d]' % ( 405 return '%s [%s:%d]' % (
383 self.name or '??', self.source_path or '??', self.source_line or 0) 406 self.name or '??', self.source_path or '??', self.source_line or 0)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698