OLD | NEW |
---|---|
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 |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
177 self._lib_file_name = posixpath.basename(symbolizer.elf_file_path) | 177 self._lib_file_name = posixpath.basename(symbolizer.elf_file_path) |
178 | 178 |
179 # The request queue (i.e. addresses pushed to addr2line's stdin and not | 179 # The request queue (i.e. addresses pushed to addr2line's stdin and not |
180 # yet retrieved on stdout) | 180 # yet retrieved on stdout) |
181 self._request_queue = collections.deque() | 181 self._request_queue = collections.deque() |
182 | 182 |
183 # This is essentially len(self._request_queue). It has been optimized to a | 183 # 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. | 184 # separate field because turned out to be a perf hot-spot. |
185 self.queue_size = 0 | 185 self.queue_size = 0 |
186 | 186 |
187 # Keep track of requests count so that we can replace addr2line | |
188 # processes before they consume all the memory of the machine | |
189 # and become slow and evil. | |
190 self._max_requests_per_process = 1000 | |
Andrew Hayden (chromium.org)
2014/05/30 15:42:29
I think I left out a zero. I want to say this was
Primiano Tucci (use gerrit)
2014/05/30 16:56:20
Two notes:
You eventually want to make this an arg
Daniel Bratell
2014/06/04 15:31:16
I did some measurements at my computer. It becomes
Daniel Bratell
2014/06/04 15:31:16
You were right, 1000 was too small. See measuremen
| |
191 self._queued_request_count = 0 | |
Primiano Tucci (use gerrit)
2014/05/30 16:56:20
You just need one "counter" variable, not two (see
| |
192 self._received_answer_count = 0 | |
193 | |
187 # Objects required to handle the addr2line subprocess. | 194 # Objects required to handle the addr2line subprocess. |
188 self._proc = None # Subprocess.Popen(...) instance. | 195 self._proc = None # Subprocess.Popen(...) instance. |
189 self._thread = None # Threading.thread instance. | 196 self._thread = None # Threading.thread instance. |
190 self._out_queue = None # Queue.Queue instance (for buffering a2l stdout). | 197 self._out_queue = None # Queue.Queue instance (for buffering a2l stdout). |
191 self._RestartAddr2LineProcess() | 198 self._RestartAddr2LineProcess() |
192 | 199 |
193 def EnqueueRequest(self, addr, callback_arg): | 200 def EnqueueRequest(self, addr, callback_arg): |
194 """Pushes an address to addr2line's stdin (and keeps track of it).""" | 201 """Pushes an address to addr2line's stdin (and keeps track of it).""" |
195 self._symbolizer.requests_counter += 1 # For global "age" of requests. | 202 self._symbolizer.requests_counter += 1 # For global "age" of requests. |
196 req_idx = self._symbolizer.requests_counter | 203 req_idx = self._symbolizer.requests_counter |
197 self._request_queue.append((addr, callback_arg, req_idx)) | 204 self._request_queue.append((addr, callback_arg, req_idx)) |
198 self.queue_size += 1 | 205 self.queue_size += 1 |
199 self._WriteToA2lStdin(addr) | 206 self._queued_request_count += 1 |
Primiano Tucci (use gerrit)
2014/05/30 16:56:20
This extra logic here is unnecessary.
You can jus
| |
207 if self._queued_request_count <= self._max_requests_per_process: | |
208 # Otherwise they will be written once we restart the process. | |
209 self._WriteToA2lStdin(addr) | |
200 | 210 |
201 def WaitForIdle(self): | 211 def WaitForIdle(self): |
202 """Waits until all the pending requests have been symbolized.""" | 212 """Waits until all the pending requests have been symbolized.""" |
203 while self.queue_size > 0: | 213 while self.queue_size > 0: |
204 self.WaitForNextSymbolInQueue() | 214 self.WaitForNextSymbolInQueue() |
205 | 215 |
206 def WaitForNextSymbolInQueue(self): | 216 def WaitForNextSymbolInQueue(self): |
207 """Waits for the next pending request to be symbolized.""" | 217 """Waits for the next pending request to be symbolized.""" |
208 if not self.queue_size: | 218 if not self.queue_size: |
209 return | 219 return |
210 | 220 |
211 # This outer loop guards against a2l hanging (detecting stdout timeout). | 221 # This outer loop guards against a2l hanging (detecting stdout timeout). |
212 while True: | 222 while True: |
213 start_time = datetime.datetime.now() | 223 start_time = datetime.datetime.now() |
214 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) | 224 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) |
215 | 225 |
216 # The inner loop guards against a2l crashing (checking if it exited). | 226 # The inner loop guards against a2l crashing (checking if it exited). |
217 while (datetime.datetime.now() - start_time < timeout): | 227 while (datetime.datetime.now() - start_time < timeout): |
228 # Restart the addr2line process if it has been running for a | |
229 # while. It keeps growing so otherwise it will make the | |
230 # whole computer unresponsive, including itself. | |
231 if self._received_answer_count >= self._max_requests_per_process: | |
232 self._queued_request_count -= self._received_answer_count | |
Primiano Tucci (use gerrit)
2014/05/30 16:56:20
This math seems rendudant.
Here you just want to k
Daniel Bratell
2014/06/04 15:31:16
Seems it became a little bit faster by this change
| |
233 self._received_answer_count = 0 | |
234 self._RestartAddr2LineProcess() | |
218 # poll() returns !None if the process exited. a2l should never exit. | 235 # poll() returns !None if the process exited. a2l should never exit. |
219 if self._proc.poll(): | 236 if self._proc.poll(): |
220 logging.warning('addr2line crashed, respawning (lib: %s).' % | 237 logging.warning('addr2line crashed, respawning (lib: %s).' % |
221 self._lib_file_name) | 238 self._lib_file_name) |
222 self._RestartAddr2LineProcess() | 239 self._RestartAddr2LineProcess() |
223 # TODO(primiano): the best thing to do in this case would be | 240 # TODO(primiano): the best thing to do in this case would be |
224 # shrinking the pool size as, very likely, addr2line is crashed | 241 # shrinking the pool size as, very likely, addr2line is crashed |
225 # due to low memory (and the respawned one will die again soon). | 242 # due to low memory (and the respawned one will die again soon). |
226 | 243 |
227 try: | 244 try: |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
290 source_line = int(m.group(2)) | 307 source_line = int(m.group(2)) |
291 else: | 308 else: |
292 logging.warning('Got invalid symbol path from addr2line: %s' % line2) | 309 logging.warning('Got invalid symbol path from addr2line: %s' % line2) |
293 | 310 |
294 sym_info = ELFSymbolInfo(name, source_path, source_line) | 311 sym_info = ELFSymbolInfo(name, source_path, source_line) |
295 if prev_sym_info: | 312 if prev_sym_info: |
296 prev_sym_info.inlined_by = sym_info | 313 prev_sym_info.inlined_by = sym_info |
297 if not innermost_sym_info: | 314 if not innermost_sym_info: |
298 innermost_sym_info = sym_info | 315 innermost_sym_info = sym_info |
299 | 316 |
317 self._received_answer_count += 1 | |
300 self._symbolizer.callback(innermost_sym_info, callback_arg) | 318 self._symbolizer.callback(innermost_sym_info, callback_arg) |
301 | 319 |
302 def _RestartAddr2LineProcess(self): | 320 def _RestartAddr2LineProcess(self): |
303 if self._proc: | 321 if self._proc: |
304 self.Terminate() | 322 self.Terminate() |
305 | 323 |
306 # The only reason of existence of this Queue (and the corresponding | 324 # The only reason of existence of this Queue (and the corresponding |
307 # Thread below) is the lack of a subprocess.stdout.poll_avail_lines(). | 325 # 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. | 326 # Essentially this is a pipe able to extract a couple of lines atomically. |
309 self._out_queue = Queue.Queue() | 327 self._out_queue = Queue.Queue() |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
374 self.name = name | 392 self.name = name |
375 self.source_path = source_path | 393 self.source_path = source_path |
376 self.source_line = source_line | 394 self.source_line = source_line |
377 # In the case of |inlines|=True, the |inlined_by| points to the outer | 395 # 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). | 396 # function inlining the current one (and so on, to form a chain). |
379 self.inlined_by = None | 397 self.inlined_by = None |
380 | 398 |
381 def __str__(self): | 399 def __str__(self): |
382 return '%s [%s:%d]' % ( | 400 return '%s [%s:%d]' % ( |
383 self.name or '??', self.source_path or '??', self.source_line or 0) | 401 self.name or '??', self.source_path or '??', self.source_line or 0) |
OLD | NEW |