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