1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved. 2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# found in the LICENSE file. 4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)"""This module implements a simple WSGI server for the memory_inspector Web UI. 6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)The WSGI server essentially handles two kinds of requests: 8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - /ajax/foo/bar: The AJAX endpoints which exchange JSON data with the JS. 9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Requests routing is achieved using a simple @uri decorator which simply 10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) performs regex matching on the request path. 11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - /static/content: Anything not matching the /ajax/ prefix is treated as a 12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) static content request (for serving the index.html and JS/CSS resources). 13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)The following HTTP status code are returned by the server: 15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - 200 - OK: The request was handled correctly. 16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - 404 - Not found: None of the defined handlers did match the /request/path. 17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) - 410 - Gone: The path was matched but the handler returned an empty response. 18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) This typically happens when the target device is disconnected. 19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)""" 20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochimport cgi 22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import collections 23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import datetime 24effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport dateutil.parser 25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import glob 261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport json 27effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport memory_inspector 28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import mimetypes 291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport os 301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport posixpath 31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import re 32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import urlparse 33effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport uuid 34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import wsgiref.simple_server 35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from memory_inspector import constants 37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from memory_inspector.core import backends 38effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom memory_inspector.core import memory_map 39effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom memory_inspector.classification import mmap_classifier 400529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochfrom memory_inspector.classification import native_heap_classifier 41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from memory_inspector.data import serialization 42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from memory_inspector.data import file_storage 43effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom memory_inspector.frontends import background_tasks 44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_HTTP_OK = '200 - OK' 47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_HTTP_GONE = '410 - Gone' 48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_HTTP_NOT_FOUND = '404 - Not Found' 49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_PERSISTENT_STORAGE_PATH = os.path.join( 50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) os.path.expanduser('~'), '.config', 'memory_inspector') 51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_CONTENT_DIR = os.path.abspath(os.path.join( 52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) os.path.dirname(__file__), 'www_content')) 53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_APP_PROCESS_RE = r'^[\w.:]+$' # Regex for matching app processes. 54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_STATS_HIST_SIZE = 120 # Keep at most 120 samples of stats per process. 55effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch_CACHE_LEN = 10 # Max length of |_cached_objs|. 56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 57effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch# |_cached_objs| keeps the state of short-lived objects that the client needs to 58effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch# _cached_objs subsequent AJAX calls. 59effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch_cached_objs = collections.OrderedDict() 60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_persistent_storage = file_storage.Storage(_PERSISTENT_STORAGE_PATH) 61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)_proc_stats_history = {} # /Android/device/PID -> deque([stats@T=0, stats@T=1]) 62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class UriHandler(object): 65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Base decorator used to automatically route /requests/by/path. 66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Each handler is called with the following args: 68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) args: a tuple of the matching regex groups. 69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_vars: a dictionary of request args (querystring for GET, body for POST). 70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) Each handler must return a tuple with the following elements: 71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) http_code: a string with the HTTP status code (e.g., '200 - OK') 72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) headers: a list of HTTP headers (e.g., [('Content-Type': 'foo/bar')]) 73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) body: the HTTP response body. 74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) _handlers = [] 76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __init__(self, path_regex, verb='GET', output_filter=None): 78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._path_regex = path_regex 79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._verb = verb 80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) default_output_filter = lambda *x: x # Just return the same args unchanged. 81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._output_filter = output_filter or default_output_filter 82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __call__(self, handler): 84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) UriHandler._handlers += [( 85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._verb, self._path_regex, self._output_filter, handler)] 86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @staticmethod 88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def Handle(method, path, req_vars): 89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Finds a matching handler and calls it (or returns a 404 - Not Found).""" 90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for (match_method, path_regex, output_filter, fn) in UriHandler._handlers: 91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if method != match_method: 92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) continue 93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) m = re.match(path_regex, path) 94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not m: 95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) continue 96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) (http_code, headers, body) = fn(m.groups(), req_vars) 97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return output_filter(http_code, headers, body) 98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return (_HTTP_NOT_FOUND, [], 'No AJAX handlers found') 99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class AjaxHandler(UriHandler): 102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Decorator for routing AJAX requests. 103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) This decorator essentially groups the JSON serialization and the cache headers 105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) which is shared by most of the handlers defined below. 106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __init__(self, path_regex, verb='GET'): 108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) super(AjaxHandler, self).__init__( 109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) path_regex, verb, AjaxHandler.AjaxOutputFilter) 110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @staticmethod 112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def AjaxOutputFilter(http_code, headers, body): 113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) serialized_content = json.dumps(body, cls=serialization.Encoder) 114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) extra_headers = [('Cache-Control', 'no-cache'), 115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ('Expires', 'Fri, 19 Sep 1986 05:00:00 GMT')] 116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return http_code, headers + extra_headers, serialized_content 117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler('/ajax/backends') 120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _ListBackends(args, req_vars): # pylint: disable=W0613 121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], [backend.name for backend in backends.ListBackends()] 122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler('/ajax/devices') 125a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _ListDevices(args, req_vars): # pylint: disable=W0613 126a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp = [] 127a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for device in backends.ListDevices(): 128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # The device settings must loaded at discovery time (i.e. here), not during 129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # startup, because it might have been plugged later. 130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for k, v in _persistent_storage.LoadSettings(device.id).iteritems(): 131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device.settings[k] = v 132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 133a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp += [{'backend': device.backend.name, 134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'id': device.id, 135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'name': device.name}] 136a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], resp 137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/dump/mmap/(\w+)/(\w+)/(\d+)') 140a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _DumpMmapsForProcess(args, req_vars): # pylint: disable=W0613 141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Dumps memory maps for a process. 142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) The response is formatted according to the Google Charts DataTable format. 144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 145a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) process = _GetProcess(args) 146a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not process: 147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found or process died' 148a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mmap = process.DumpMemoryMaps() 149effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch table = _ConvertMmapToGTable(mmap) 150a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 151effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Store the dump in the cache. The client might need it later for profiling. 152effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch cache_id = _CacheObject(mmap) 153effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], {'table': table, 'id': cache_id} 154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 155effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 156effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler('/ajax/initialize/(\w+)/(\w+)$', 'POST') 157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _InitializeDevice(args, req_vars): # pylint: disable=W0613 158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device.Initialize() 1620529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if req_vars['enableNativeTracing']: 1630529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch device.EnableNativeTracing(True) 164a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], { 1650529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 'isNativeTracingEnabled': device.IsNativeTracingEnabled()} 166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 168effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/profile/create', 'POST') 169effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _CreateProfile(args, req_vars): # pylint: disable=W0613 170effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Creates (and caches) a profile from a set of dumps. 171effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 172effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch The profiling data can be retrieved afterwards using the /profile/{PROFILE_ID} 173effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch endpoints (below). 174effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """ 175effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch classifier = None # A classifier module (/classification/*_classifier.py). 176effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dumps = {} # dump-time -> obj. to classify (e.g., |memory_map.Map|). 177effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for arg in 'type', 'source', 'ruleset': 178effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch assert(arg in req_vars), 'Expecting %s argument in POST data' % arg 179effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 180effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Step 1: collect the memory dumps, according to what the client specified in 181effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # the 'type' and 'source' POST arguments. 182effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 1830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Case 1a: The client requests to load data from an archive. 1840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if req_vars['source'] == 'archive': 1850529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch archive = _persistent_storage.OpenArchive(req_vars['archive']) 1860529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not archive: 1870529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] 1880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch first_timestamp = None 1890529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch for timestamp_str in req_vars['snapshots']: 1900529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch timestamp = dateutil.parser.parse(timestamp_str) 1910529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch first_timestamp = first_timestamp or timestamp 1920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch time_delta = int((timestamp - first_timestamp).total_seconds()) 1930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if req_vars['type'] == 'mmap': 194effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch dumps[time_delta] = archive.LoadMemMaps(timestamp) 1950529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch elif req_vars['type'] == 'nheap': 1960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch dumps[time_delta] = archive.LoadNativeHeap(timestamp) 197effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 1980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Case 1b: Use a dump recently cached (only mmap, via _DumpMmapsForProcess). 1990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch elif req_vars['source'] == 'cache': 2000529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch assert(req_vars['type'] == 'mmap'), 'Only cached mmap dumps are supported.' 2010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch dumps[0] = _GetCacheObject(req_vars['id']) 202effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 203effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not dumps: 204effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'No memory dumps could be retrieved' 205effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 2060529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Initialize the classifier (mmap or nheap) and prepare symbols for nheap. 2070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if req_vars['type'] == 'mmap': 2080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch classifier = mmap_classifier 2090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch elif req_vars['type'] == 'nheap': 2100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch classifier = native_heap_classifier 2110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not archive.HasSymbols(): 2120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'No symbols in archive %s' % req_vars['archive'] 2130529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch symbols = archive.LoadSymbols() 2140529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch for nheap in dumps.itervalues(): 2150529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch nheap.SymbolizeUsingSymbolDB(symbols) 2160529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 2170529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not classifier: 2180529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'Classifier %s not supported.' % req_vars['type'] 2190529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 2200529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Step 2: Load the rule-set specified by the client in the 'ruleset' POST arg. 2210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if req_vars['ruleset'] == 'heuristic': 2220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch assert(req_vars['type'] == 'nheap'), ( 2230529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 'heuristic rules are supported only for nheap') 2240529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch rules = native_heap_classifier.InferHeuristicRulesFromHeap(dumps[0]) 2250529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch else: 226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) rules_path = os.path.join(constants.CLASSIFICATION_RULES_PATH, 227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) req_vars['ruleset']) 2280529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not os.path.isfile(rules_path): 2290529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'Cannot find the rule-set %s' % rules_path 2300529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch with open(rules_path) as f: 2310529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch rules = classifier.LoadRules(f.read()) 2320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 2330529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Step 3: Aggregate the dump data using the classifier and generate the 2340529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # profile data (which will be kept cached here in the server). 235effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # The resulting profile will consist of 1+ snapshots (depending on the number 236effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # dumps the client has requested to process) and a number of 1+ metrics 237effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # (depending on the buckets' keys returned by the classifier). 238effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 239effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Converts the {time: dump_obj} dict into a {time: |AggregatedResult|} dict. 240effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # using the classifier. 241effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshots = collections.OrderedDict((time, classifier.Classify(dump, rules)) 242effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for time, dump in sorted(dumps.iteritems())) 243effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 244effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Add the profile to the cache (and eventually discard old items). 245effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # |profile_id| is the key that the client will use in subsequent requests 246effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # (to the /ajax/profile/{ID}/ endpoints) to refer to this particular profile. 247effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch profile_id = _CacheObject(snapshots) 248effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 249effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch first_snapshot = next(snapshots.itervalues()) 250effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], {'id': profile_id, 251effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'times': snapshots.keys(), 252effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'metrics': first_snapshot.keys, 253effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'rootBucket': first_snapshot.total.name + '/'} 254effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 255effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 256effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/profile/(\w+)/tree/(\d+)/(\d+)') 257effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetProfileTreeDataForSnapshot(args, req_vars): # pylint: disable=W0613 258effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Gets the data for the tree chart for a given time and metric. 259effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 260effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch The response is formatted according to the Google Charts DataTable format. 261effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """ 262effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshot_id = args[0] 263effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch metric_index = int(args[1]) 264effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch time = int(args[2]) 265effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshots = _GetCacheObject(snapshot_id) 266effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not snapshots: 267effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Cannot find the selected profile.' 268effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if time not in snapshots: 269effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Cannot find snapshot at T=%d.' % time 270effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshot = snapshots[time] 271effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if metric_index >= len(snapshot.keys): 272effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Invalid metric id %d' % metric_index 273effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 274effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp = {'cols': [{'label': 'bucket', 'type': 'string'}, 275effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'parent', 'type': 'string'}], 276effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'rows': []} 277effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 278effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def VisitBucketAndAddRows(bucket, parent_id=''): 279effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Recursively creates the (node, parent) visiting |ResultTree| in DFS.""" 280effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch node_id = parent_id + bucket.name + '/' 281effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch node_label = '<dl><dt>%s</dt><dd>%s</dd></dl>' % ( 282effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch bucket.name, _StrMem(bucket.values[metric_index])) 283effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp['rows'] += [{'c': [ 284effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': node_id, 'f': node_label}, 285effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': parent_id, 'f': None}, 286effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ]}] 287effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for child in bucket.children: 288effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch VisitBucketAndAddRows(child, node_id) 289effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 290effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch VisitBucketAndAddRows(snapshot.total) 291effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], resp 292effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 293effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 294effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/profile/(\w+)/time_serie/(\d+)/(.*)$') 295effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetTimeSerieForSnapshot(args, req_vars): # pylint: disable=W0613 296effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Gets the data for the area chart for a given metric and bucket. 297effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 298effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch The response is formatted according to the Google Charts DataTable format. 299effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """ 300effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshot_id = args[0] 301effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch metric_index = int(args[1]) 302effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch bucket_path = args[2] 303effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch snapshots = _GetCacheObject(snapshot_id) 304effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not snapshots: 305effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Cannot find the selected profile.' 306effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if metric_index >= len(next(snapshots.itervalues()).keys): 307effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Invalid metric id %d' % metric_index 308effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 309effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def FindBucketByPath(bucket, path, parent_path=''): # Essentially a DFS. 310effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch cur_path = parent_path + bucket.name + '/' 311effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if cur_path == path: 312effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return bucket 313effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for child in bucket.children: 314effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch res = FindBucketByPath(child, path, cur_path) 315effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if res: 316effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return res 317effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return None 318effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 319effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # The resulting data table will look like this (assuming len(metrics) == 2): 320effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Time Ashmem Dalvik Other 321effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # 0 (1024,0) (4096,1024) (0,0) 322effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # 30 (512,512) (1024,1024) (0,512) 323effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # 60 (0,512) (1024,0) (512,0) 324effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp = {'cols': [], 'rows': []} 325effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for time, aggregated_result in snapshots.iteritems(): 326effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch bucket = FindBucketByPath(aggregated_result.total, bucket_path) 327effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not bucket: 328effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Bucket %s not found' % bucket_path 329effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 330effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # If the user selected a non-leaf bucket, display the breakdown of its 331effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # direct children. Otherwise just the leaf bucket. 332effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch children_buckets = bucket.children if bucket.children else [bucket] 333effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 334effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch # Create the columns (form the buckets) when processing the first snapshot. 335effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not resp['cols']: 336effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp['cols'] += [{'label': 'Time', 'type': 'string'}] 337effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for child_bucket in children_buckets: 338effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp['cols'] += [{'label': child_bucket.name, 'type': 'number'}] 339effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 340effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch row = [{'v': str(time), 'f': None}] 341effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for child_bucket in children_buckets: 342effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch row += [{'v': child_bucket.values[metric_index] / 1024, 'f': None}] 343effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp['rows'] += [{'c': row}] 344effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 345effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], resp 346effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)@AjaxHandler(r'/ajax/profile/rules') 348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _ListProfilingRules(args, req_vars): # pylint: disable=W0613 349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) """Lists the classification rule files available for profiling.""" 350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) rules = glob.glob(constants.CLASSIFICATION_RULES_PATH + 351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) os.sep + '*' + os.sep + '*.py') 352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) rules = [x.replace(constants.CLASSIFICATION_RULES_PATH, '')[1:] # Strip /. 353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for x in rules] 354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) resp = {'mmap': filter(lambda x: 'mmap-' in x, rules), 355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 'nheap': filter(lambda x: 'nheap-' in x, rules)} 356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) resp['nheap'].insert(0, 'heuristic') 357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return _HTTP_OK, [], resp 358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 359effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 360a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/ps/(\w+)/(\w+)$') # /ajax/ps/Android/a0b1c2[?all=1] 361a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _ListProcesses(args, req_vars): # pylint: disable=W0613 362a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Lists processes and their CPU / mem stats. 363a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 364a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) The response is formatted according to the Google Charts DataTable format. 365a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 366a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 367a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 368a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 369a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp = { 370a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'cols': [ 371a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Pid', 'type':'number'}, 372a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Name', 'type':'string'}, 373a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Cpu %', 'type':'number'}, 374a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Mem RSS Kb', 'type':'number'}, 375a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': '# Threads', 'type':'number'}, 376a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ], 377a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'rows': []} 378a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for process in device.ListProcesses(): 379a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # Exclude system apps if the request didn't contain the ?all=1 arg. 380a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not req_vars.get('all') and not re.match(_APP_PROCESS_RE, process.name): 381a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) continue 382a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) stats = process.GetStats() 383a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp['rows'] += [{'c': [ 384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': process.pid, 'f': None}, 385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': process.name, 'f': None}, 386a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.cpu_usage, 'f': None}, 387a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.vm_rss, 'f': None}, 388a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.threads, 'f': None}, 389a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ]}] 390a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], resp 391a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 392a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 393a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/stats/(\w+)/(\w+)$') # /ajax/stats/Android/a0b1c2 394a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GetDeviceStats(args, req_vars): # pylint: disable=W0613 395a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Lists device CPU / mem stats. 396a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 397a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) The response is formatted according to the Google Charts DataTable format. 398a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 399a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 400a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 401a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 402a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device_stats = device.GetStats() 403a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 404a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cpu_stats = { 405a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'cols': [ 406a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'CPU', 'type':'string'}, 407a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Usr %', 'type':'number'}, 408a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Sys %', 'type':'number'}, 409a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Idle %', 'type':'number'}, 410a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ], 411a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'rows': []} 412a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 413a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for cpu_idx in xrange(len(device_stats.cpu_times)): 414a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cpu = device_stats.cpu_times[cpu_idx] 415a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cpu_stats['rows'] += [{'c': [ 416a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': '# %d' % cpu_idx, 'f': None}, 417a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': cpu['usr'], 'f': None}, 418a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': cpu['sys'], 'f': None}, 419a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': cpu['idle'], 'f': None}, 420a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ]}] 421a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 422a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mem_stats = { 423a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'cols': [ 424a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Section', 'type':'string'}, 425a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'MB', 'type':'number', 'pattern': ''}, 426a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ], 427a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'rows': []} 428a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 429a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for key, value in device_stats.memory_stats.iteritems(): 430a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mem_stats['rows'] += [{'c': [ 431a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': key, 'f': None}, 432a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': value / 1024, 'f': None} 433a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ]}] 434a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 435a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], {'cpu': cpu_stats, 'mem': mem_stats} 436a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 437a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 438a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/stats/(\w+)/(\w+)/(\d+)$') # /ajax/stats/Android/a0b1c2/42 439a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GetProcessStats(args, req_vars): # pylint: disable=W0613 440a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Lists CPU / mem stats for a given process (and keeps history). 441a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 442a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) The response is formatted according to the Google Charts DataTable format. 443a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 444a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) process = _GetProcess(args) 445a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not process: 446a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 447a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 448a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) proc_uri = '/'.join(args) 449a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cur_stats = process.GetStats() 450a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if proc_uri not in _proc_stats_history: 451a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) _proc_stats_history[proc_uri] = collections.deque(maxlen=_STATS_HIST_SIZE) 452a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) history = _proc_stats_history[proc_uri] 453a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) history.append(cur_stats) 454a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 455a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cpu_stats = { 456a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'cols': [ 457a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'T', 'type':'string'}, 458a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'CPU %', 'type':'number'}, 459a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': '# Threads', 'type':'number'}, 460a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ], 461a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'rows': [] 462a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 463a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 464a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mem_stats = { 465a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'cols': [ 466a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'T', 'type':'string'}, 467a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Mem RSS Kb', 'type':'number'}, 468a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'label': 'Page faults', 'type':'number'}, 469a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ], 470a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'rows': [] 471a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) } 472a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 473a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for stats in history: 474a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cpu_stats['rows'] += [{'c': [ 475a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': str(datetime.timedelta(seconds=stats.run_time)), 'f': None}, 476a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.cpu_usage, 'f': None}, 477a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.threads, 'f': None}, 478a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ]}] 479a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mem_stats['rows'] += [{'c': [ 480a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': str(datetime.timedelta(seconds=stats.run_time)), 'f': None}, 481a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.vm_rss, 'f': None}, 482a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) {'v': stats.page_faults, 'f': None}, 483a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ]}] 484a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 485a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], {'cpu': cpu_stats, 'mem': mem_stats} 486a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 487a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 488a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/settings/(\w+)/?(\w+)?$') # /ajax/settings/Android[/id] 489a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GetDeviceOrBackendSettings(args, req_vars): # pylint: disable=W0613 490a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) backend = backends.GetBackend(args[0]) 491a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not backend: 492a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Backend not found' 493a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if args[1]: 494a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 495a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 496a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 497a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) settings = device.settings 498a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) else: 499a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) settings = backend.settings 500a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 501a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) assert(isinstance(settings, backends.Settings)) 502a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp = {} 503a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for key in settings.expected_keys: 504a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) resp[key] = {'description': settings.expected_keys[key], 505a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'value': settings.values[key]} 506a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], resp 507a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 508a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 509a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@AjaxHandler(r'/ajax/settings/(\w+)/?(\w+)?$', 'POST') 510a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _SetDeviceOrBackendSettings(args, req_vars): # pylint: disable=W0613 511a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) backend = backends.GetBackend(args[0]) 512a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not backend: 513a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Backend not found' 514a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if args[1]: 515a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 516a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 517a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_GONE, [], 'Device not found' 518a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) settings = device.settings 519a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) storage_name = device.id 520a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) else: 521a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) settings = backend.settings 522a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) storage_name = backend.name 523a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 524a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for key in req_vars.iterkeys(): 525a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) settings[key] = req_vars[key] 526a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) _persistent_storage.StoreSettings(storage_name, settings.values) 527a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, [], '' 528a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 529a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 530effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/storage/list') 531effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _ListStorage(args, req_vars): # pylint: disable=W0613 532effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp = { 533effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'cols': [ 534effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Archive', 'type':'string'}, 535effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Snapshot', 'type':'string'}, 536effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Mem maps', 'type':'boolean'}, 537effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'N. Heap', 'type':'boolean'}, 538effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ], 539effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'rows': []} 540effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for archive_name in _persistent_storage.ListArchives(): 541effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch archive = _persistent_storage.OpenArchive(archive_name) 542effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch first_timestamp = None 543effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for timestamp in archive.ListSnapshots(): 544effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch first_timestamp = timestamp if not first_timestamp else first_timestamp 545effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch time_delta = '%d s.' % (timestamp - first_timestamp).total_seconds() 546effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch resp['rows'] += [{'c': [ 547effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': archive_name, 'f': None}, 548effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': timestamp.isoformat(), 'f': time_delta}, 549effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': archive.HasMemMaps(timestamp), 'f': None}, 550effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': archive.HasNativeHeap(timestamp), 'f': None}, 551effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ]}] 552effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], resp 553effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 554effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 555effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/storage/(.+)/(.+)/mmaps') 556effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _LoadMmapsFromStorage(args, req_vars): # pylint: disable=W0613 557effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch archive = _persistent_storage.OpenArchive(args[0]) 558effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not archive: 559effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] 560effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 561effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch timestamp = dateutil.parser.parse(args[1]) 562effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not archive.HasMemMaps(timestamp): 563effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'No mmaps for snapshot %s' % timestamp 564effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch mmap = archive.LoadMemMaps(timestamp) 565effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], {'table': _ConvertMmapToGTable(mmap)} 566effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 567effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 5680529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch@AjaxHandler(r'/ajax/storage/(.+)/(.+)/nheap') 5690529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochdef _LoadNheapFromStorage(args, req_vars): 5700529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch """Returns a Google Charts DataTable dictionary for the nheap.""" 5710529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch archive = _persistent_storage.OpenArchive(args[0]) 5720529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not archive: 5730529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] 5740529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 5750529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch timestamp = dateutil.parser.parse(args[1]) 5760529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch if not archive.HasNativeHeap(timestamp): 5770529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_GONE, [], 'No native heap dump for snapshot %s' % timestamp 5780529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 5790529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch nheap = archive.LoadNativeHeap(timestamp) 5800529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch symbols = archive.LoadSymbols() 5810529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch nheap.SymbolizeUsingSymbolDB(symbols) 5820529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 5830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch resp = { 5840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 'cols': [ 5851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'label': 'Allocated', 'type':'number'}, 5861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'label': 'Resident', 'type':'number'}, 5871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'label': 'Flags', 'type':'number'}, 5880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch {'label': 'Stack Trace', 'type':'string'}, 5890529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch ], 5900529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 'rows': []} 5910529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch for alloc in nheap.allocations: 5920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch strace = '<dl>' 5930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch for frame in alloc.stack_trace.frames: 5940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch # Use the fallback libname.so+0xaddr if symbol info is not available. 5950529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch symbol_name = frame.symbol.name if frame.symbol else '??' 5960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch source_info = (str(frame.symbol.source_info[0]) if 5970529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch frame.symbol and frame.symbol.source_info else frame.raw_address) 5980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch strace += '<dd title="%s">%s</dd><dt>%s</dt>' % ( 5990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch cgi.escape(source_info), 6001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci cgi.escape(posixpath.basename(source_info)), 6010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch cgi.escape(symbol_name)) 6020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch strace += '</dl>' 6030529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 6040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch resp['rows'] += [{'c': [ 6051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'v': alloc.size, 'f': _StrMem(alloc.size)}, 6061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'v': alloc.resident_size, 'f': _StrMem(alloc.resident_size)}, 6071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'v': alloc.flags, 'f': None}, 6080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch {'v': strace, 'f': None}, 6090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch ]}] 6100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch return _HTTP_OK, [], resp 6110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 6120529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch 613effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch# /ajax/tracer/start/Android/device-id/pid 614effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/tracer/start/(\w+)/(\w+)/(\d+)', 'POST') 615effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _StartTracer(args, req_vars): 616effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for arg in 'interval', 'count', 'traceNativeHeap': 617effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch assert(arg in req_vars), 'Expecting %s argument in POST data' % arg 618effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch process = _GetProcess(args) 619effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not process: 620effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Device not found or process died' 621effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch task_id = background_tasks.StartTracer( 622effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch storage_path=_PERSISTENT_STORAGE_PATH, 623effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch process=process, 624effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch interval=int(req_vars['interval']), 625effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch count=int(req_vars['count']), 626effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch trace_native_heap=req_vars['traceNativeHeap']) 627effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], task_id 628effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 629effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 630effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch@AjaxHandler(r'/ajax/tracer/status/(\d+)') # /ajax/tracer/status/{task_id} 631effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetTracerStatus(args, req_vars): # pylint: disable=W0613 632effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch task = background_tasks.Get(int(args[0])) 633effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if not task: 634effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_GONE, [], 'Task not found' 635effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _HTTP_OK, [], task.GetProgress() 636effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 637effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 638a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)@UriHandler(r'^(?!/ajax)/(.*)$') 639a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _StaticContent(args, req_vars): # pylint: disable=W0613 640a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # Give the browser a 1-day TTL cache to minimize the start-up time. 641a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) cache_headers = [('Cache-Control', 'max-age=86400, public')] 642a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_path = args[0] if args[0] else 'index.html' 643a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) file_path = os.path.abspath(os.path.join(_CONTENT_DIR, req_path)) 644a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (os.path.isfile(file_path) and 645a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) os.path.commonprefix([file_path, _CONTENT_DIR]) == _CONTENT_DIR): 646a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mtype = 'text/plain' 647a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) guessed_mime = mimetypes.guess_type(file_path) 648a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if guessed_mime and guessed_mime[0]: 649a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) mtype = guessed_mime[0] 650a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) with open(file_path, 'rb') as f: 651a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) body = f.read() 652a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_OK, cache_headers + [('Content-Type', mtype)], body 653a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return _HTTP_NOT_FOUND, cache_headers, file_path + ' not found' 654a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 655a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 656a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GetDevice(args): 657a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Returns a |backends.Device| instance from a /backend/device URI.""" 658a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) assert(len(args) >= 2), 'Malformed request. Expecting /backend/device' 659a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return backends.GetDevice(backend_name=args[0], device_id=args[1]) 660a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 661a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 662a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GetProcess(args): 663a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Returns a |backends.Process| instance from a /backend/device/pid URI.""" 664a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) assert(len(args) >= 3 and args[2].isdigit()), ( 665a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 'Malformed request. Expecting /backend/device/pid') 666a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) device = _GetDevice(args) 667a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not device: 668a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return None 669a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return device.GetProcess(int(args[2])) 670a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 671effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _ConvertMmapToGTable(mmap): 672effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Returns a Google Charts DataTable dictionary for the given mmap.""" 673effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch assert(isinstance(mmap, memory_map.Map)) 674effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch table = { 675effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'cols': [ 676effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Start', 'type':'string'}, 677effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'End', 'type':'string'}, 678effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Length Kb', 'type':'number'}, 679effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Prot', 'type':'string'}, 6801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'label': 'RSS Kb', 'type':'number'}, 681effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Priv. Dirty Kb', 'type':'number'}, 682effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Priv. Clean Kb', 'type':'number'}, 683effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Shared Dirty Kb', 'type':'number'}, 684effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Shared Clean Kb', 'type':'number'}, 685effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'File', 'type':'string'}, 686effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Offset', 'type':'number'}, 687effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'label': 'Resident Pages', 'type':'string'}, 688effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ], 689effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'rows': []} 690effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch for entry in mmap.entries: 691effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch table['rows'] += [{'c': [ 692effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': '%08x' % entry.start, 'f': None}, 693effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': '%08x' % entry.end, 'f': None}, 694effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.len / 1024, 'f': None}, 695effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.prot_flags, 'f': None}, 6961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci {'v': entry.rss_bytes / 1024, 'f': None}, 697effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.priv_dirty_bytes / 1024, 'f': None}, 698effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.priv_clean_bytes / 1024, 'f': None}, 699effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.shared_dirty_bytes / 1024, 'f': None}, 700effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.shared_clean_bytes / 1024, 'f': None}, 701effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.mapped_file, 'f': None}, 702effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': entry.mapped_offset, 'f': None}, 703effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch {'v': '[%s]' % (','.join(map(str, entry.resident_pages))), 'f': None}, 704effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch ]}] 705effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return table 706effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 707effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _CacheObject(obj_to_store): 708effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Stores an object in the server-side cache and returns its unique id.""" 709effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if len(_cached_objs) >= _CACHE_LEN: 710effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch _cached_objs.popitem(last=False) 711effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch obj_id = uuid.uuid4().hex 712effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch _cached_objs[obj_id] = obj_to_store 713effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return str(obj_id) 714effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 715effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 716effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetCacheObject(obj_id): 717effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Retrieves an object in the server-side cache by its id.""" 718effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return _cached_objs.get(obj_id) 719effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 720effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 721effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _StrMem(nbytes): 722effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Converts a number (of bytes) into a human readable string (kb, mb).""" 7231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci UNITS = ['B', 'K', 'M', 'G'] 7241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for unit in UNITS: 7251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if abs(nbytes) < 1024.0 or unit == UNITS[-1]: 7261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return ('%3.1f' % nbytes).replace('.0','') + ' ' + unit 7271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci nbytes /= 1024.0 728effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 729a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 730a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _HttpRequestHandler(environ, start_response): 731a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """Parses a single HTTP request and delegates the handling through UriHandler. 732a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 733a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) This essentially wires up wsgiref.simple_server with our @UriHandler(s). 734a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 735a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) path = environ['PATH_INFO'] 736a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) method = environ['REQUEST_METHOD'] 737a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if method == 'POST': 738a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_body_size = int(environ.get('CONTENT_LENGTH', 0)) 739a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_body = environ['wsgi.input'].read(req_body_size) 740a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_vars = json.loads(req_body) 741a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) else: 742a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) req_vars = urlparse.parse_qs(environ['QUERY_STRING']) 743a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) (http_code, headers, body) = UriHandler.Handle(method, path, req_vars) 744a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) start_response(http_code, headers) 745a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return [body] 746a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 747a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 748a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def Start(http_port): 749a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # Load the saved backends' settings (some of them might be needed to bootstrap 750a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) # as, for instance, the adb path for the Android backend). 751cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) memory_inspector.RegisterAllBackends() 752a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for backend in backends.ListBackends(): 753a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for k, v in _persistent_storage.LoadSettings(backend.name).iteritems(): 754a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) backend.settings[k] = v 755a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 756a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) httpd = wsgiref.simple_server.make_server('', http_port, _HttpRequestHandler) 757effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch try: 758effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch httpd.serve_forever() 759effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch except KeyboardInterrupt: 760effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch pass # Don't print useless stack traces when the user hits CTRL-C. 761effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch background_tasks.TerminateAll()