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()