Chromium Code Reviews| 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 |