116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata# Copyright 2014 The Chromium Authors. All rights reserved.
216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata# Use of this source code is governed by a BSD-style license that can be
316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata# found in the LICENSE file.
416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata"""Background tasks for the www_server module.
616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico GranataThis module has the logic for handling background tasks for the www frontend.
816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico GranataLong term actions (like periodic tracing), cannot be served synchronously in the
916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatacontext of a /ajax/endpoint request (would timeout HTTP). Instead, for such long
10d891f9b872103235cfd2ed452c6f14a4394d9b3aDaniel Maleaoperations, an instance of |BackgroundTask| is created here and the server
11d891f9b872103235cfd2ed452c6f14a4394d9b3aDaniel Maleareturns just its id. The client can later poll the status of the asynchronous
1216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatatask to check for its progress.
1316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
1416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico GranataFrom a technical viewpoint, each background task is just a python subprocess
15d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granatawhich communicates its progress updates through a Queue. The messages enqueued
1616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataare tuples with the following format: (completion_ratio%, 'message string').
17f509c5ec066599a3399fced39ea36996184939e8Enrico Granata"""
1816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
1916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataimport datetime
2016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataimport multiprocessing
2116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataimport Queue
2216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataimport time
2316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
2416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatafrom memory_inspector.core import backends
2516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatafrom memory_inspector.data import file_storage
2616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
2716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
2816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata_tasks = {}  #id (int) -> |BackgroundTask| instance.
2916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
3016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
3116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatadef StartTracer(process, storage_path, interval, count, trace_native_heap):
3216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  assert(isinstance(process, backends.Process))
3316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  task = BackgroundTask(
3416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      TracerMain_,
35d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      storage_path=storage_path,
36d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      backend_name=process.device.backend.name,
37d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      device_id=process.device.id,
38d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      pid=process.pid,
39d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      interval=interval,
40d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      count=count,
41d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata      trace_native_heap=trace_native_heap)
4216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  task.start()
4316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  _tasks[task.pid] = task
4416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  return task.pid
4516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
4616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
4716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatadef Get(task_id):
4816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  return _tasks.get(task_id)
4916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
5016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
5116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatadef TerminateAll():
5216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  for proc in _tasks.itervalues():
5316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    if proc.is_alive():
5416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      proc.terminate()
5516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  _tasks.clear()
5616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
5716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
5816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granatadef TracerMain_(log, storage_path, backend_name, device_id, pid, interval,
5916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    count, trace_native_heap):
6016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  """Entry point for the background periodic tracer task."""
6116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  # Initialize storage.
6216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  storage = file_storage.Storage(storage_path)
6316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
6416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  # Initialize the backend.
65d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  backend = backends.GetBackend(backend_name)
66d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  for k, v in storage.LoadSettings(backend_name).iteritems():
67d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata    backend.settings[k] = v
68d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata
69d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  # Initialize the device.
70d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  device = backends.GetDevice(backend_name, device_id)
71d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  for k, v in storage.LoadSettings(device_id).iteritems():
72d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata    device.settings[k] = v
73d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata
74d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  # Start periodic tracing.
75d760907c1d42726fa0c8c48efa28385ed339bb94Enrico Granata  process = device.GetProcess(pid)
7616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  log.put((1, 'Starting trace (%d dumps x %s s.). Device: %s, process: %s' % (
7716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      count, interval, device.name, process.name)))
7816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  datetime_str = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')
7916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  archive_name = '%s - %s - %s' % (datetime_str, device.name, process.name)
8016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  archive = storage.OpenArchive(archive_name, create=True)
8116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  heaps_to_symbolize = []
8216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
8316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  for i in xrange(1, count + 1):  # [1, count] range is easier to handle.
8416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    process = device.GetProcess(pid)
8516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    if not process:
8616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      log.put((100, 'Process %d died.' % pid))
8716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      return 1
8816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    # Calculate the completion rate proportionally to 80%. We keep the remaining
8916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    # 20% for the final symbolization step (just an approximate estimation).
9016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    completion = 80 * i / count
9116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    log.put((completion, 'Dumping trace %d of %d' % (i, count)))
9216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    archive.StartNewSnapshot()
9316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    # Freeze the process, so that the mmaps and the heap dump are consistent.
9416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    process.Freeze()
9516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    try:
9616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      if trace_native_heap:
9716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        nheap = process.DumpNativeHeap()
9816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        log.put((completion,
9916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata                 'Dumped %d native allocations' % len(nheap.allocations)))
10016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
10116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      # TODO(primiano): memdump has the bad habit of sending SIGCONT to the
10216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      # process. Fix that, so we are the only one in charge of controlling it.
10316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      mmaps = process.DumpMemoryMaps()
10416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      log.put((completion, 'Dumped %d memory maps' % len(mmaps)))
10516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      archive.StoreMemMaps(mmaps)
10616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
10716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      if trace_native_heap:
10816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        nheap.RelativizeStackFrames(mmaps)
10916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        nheap.CalculateResidentSize(mmaps)
11016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        archive.StoreNativeHeap(nheap)
11116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        heaps_to_symbolize += [nheap]
11216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    finally:
11316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      process.Unfreeze()
11416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
11516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    if i < count:
11616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      time.sleep(interval)
11716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
11816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  if heaps_to_symbolize:
11916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    log.put((90, 'Symbolizing'))
12016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    symbols = backend.ExtractSymbols(
1216f01c93497df194b6f2194630a81e87d806ce0e0Jim Ingham        heaps_to_symbolize, device.settings['native_symbol_paths'] or '')
1226f01c93497df194b6f2194630a81e87d806ce0e0Jim Ingham    expected_symbols_count = len(set.union(
12316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        *[set(x.stack_frames.iterkeys()) for x in heaps_to_symbolize]))
12416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    log.put((99, 'Symbolization complete. Got %d symbols (%.1f%%).' % (
12516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        len(symbols), 100.0 * len(symbols) / expected_symbols_count)))
12616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    archive.StoreSymbols(symbols)
12716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
12816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  log.put((100, 'Trace complete.'))
12916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  return 0
13016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
13116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
13216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granataclass BackgroundTask(multiprocessing.Process):
13316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  def __init__(self, entry_point, *args, **kwargs):
13416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    self._log_queue = multiprocessing.Queue()
13516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    self._progress_log = []  # A list of tuples [(50%, 'msg1'), (100%, 'msg2')].
13616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    super(BackgroundTask, self).__init__(
13716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        target=entry_point,
13816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        args=((self._log_queue,) + args),  # Just propagate all args.
13916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        kwargs=kwargs)
14016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata
14116376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata  def GetProgress(self):
14216376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    """ Returns a tuple (completion_rate, message). """
14316376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    while True:
14416376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      try:
14516376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        self._progress_log += [self._log_queue.get(block=False)]
14616376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      except Queue.Empty:
14716376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata        break
14816376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    if not self.is_alive() and self.exitcode != 0:
14916376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata      return self._progress_log + [(100, 'Failed with code %d' % self.exitcode)]
15016376ed044df3ee70fcf69e19f06af01e71a8e9aEnrico Granata    return self._progress_log