• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Handler to serve a simple time series for all points in a series.
6
7This is used to show the revision slider for a chart; it includes data
8for all past points, including those that are not recent. Each entry
9in the returned list is a 3-item list: [revision, value, timestamp].
10The revisions and values are used to plot a mini-chart, and the timestamps
11are used to label this mini-chart with dates.
12
13This list is cached, since querying all Row entities for a given test takes a
14long time. This module also provides a function for updating the cache.
15"""
16
17import bisect
18import json
19
20from dashboard import datastore_hooks
21from dashboard import namespaced_stored_object
22from dashboard import request_handler
23from dashboard import utils
24from dashboard.models import graph_data
25
26_CACHE_KEY = 'num_revisions_%s'
27
28
29class GraphRevisionsHandler(request_handler.RequestHandler):
30  """URL endpoint to list all the revisions for each test, for x-axis slider."""
31
32  def post(self):
33    """Fetches a list of revisions and values for a given test.
34
35    Request parameters:
36      test_path: Full test path (including master/bot) for one Test entity.
37
38    Outputs:
39      A JSON list of 3-item lists [revision, value, timestamp].
40    """
41    test_path = self.request.get('test_path')
42    rows = namespaced_stored_object.Get(_CACHE_KEY % test_path)
43    if not rows:
44      rows = _UpdateCache(utils.TestKey(test_path))
45    self.response.out.write(json.dumps(rows))
46
47
48def SetCache(test_path, rows):
49  """Sets the saved graph revisions data for a test.
50
51  Args:
52    test_path: A test path string.
53    rows: A list of [revision, value, timestamp] triplets.
54  """
55  # This first set generally only sets the internal-only cache.
56  namespaced_stored_object.Set(_CACHE_KEY % test_path, rows)
57
58  # If this is an internal_only query for externally available data,
59  # set the cache for that too.
60  if datastore_hooks.IsUnalteredQueryPermitted():
61    test = utils.TestKey(test_path).get()
62    if test and not test.internal_only:
63      namespaced_stored_object.SetExternal(_CACHE_KEY % test_path, rows)
64
65
66def DeleteCache(test_path):
67  """Removes any saved data for the given path."""
68  namespaced_stored_object.Delete(_CACHE_KEY % test_path)
69
70
71def _UpdateCache(test_key):
72  """Queries Rows for a test then updates the cache.
73
74  Args:
75    test_key: ndb.Key for a Test entity.
76
77  Returns:
78    The list of triplets that was just fetched and set in the cache.
79  """
80  test = test_key.get()
81  if not test:
82    return []
83  assert utils.IsInternalUser() or not test.internal_only
84  datastore_hooks.SetSinglePrivilegedRequest()
85
86  # A projection query queries just for the values of particular properties;
87  # this is faster than querying for whole entities.
88  query = graph_data.Row.query(projection=['revision', 'value', 'timestamp'])
89  query = query.filter(graph_data.Row.parent_test == test_key)
90
91  # Using a large batch_size speeds up queries with > 1000 Rows.
92  rows = map(_MakeTriplet, query.iter(batch_size=1000))
93  # Note: Unit tests do not call datastore_hooks with the above query, but
94  # it is called in production and with more recent SDK.
95  datastore_hooks.CancelSinglePrivilegedRequest()
96  SetCache(utils.TestPath(test_key), rows)
97  return rows
98
99
100def _MakeTriplet(row):
101  """Makes a 3-item list of revision, value and timestamp for a Row."""
102  timestamp = utils.TimestampMilliseconds(row.timestamp)
103  return [row.revision, row.value, timestamp]
104
105
106def AddRowsToCache(row_entities):
107  """Adds a list of rows to the cache, in revision order.
108
109  Updates multiple cache entries for different tests.
110
111  Args:
112    row_entities: List of Row entities.
113  """
114  test_key_to_rows = {}
115  for row in row_entities:
116    test_key = row.parent_test
117    if test_key in test_key_to_rows:
118      graph_rows = test_key_to_rows[test_key]
119    else:
120      test_path = utils.TestPath(test_key)
121      graph_rows = namespaced_stored_object.Get(_CACHE_KEY % test_path)
122      if not graph_rows:
123        # We only want to update caches for tests that people have looked at.
124        continue
125      test_key_to_rows[test_key] = graph_rows
126
127    revisions = [r[0] for r in graph_rows]
128    index = bisect.bisect_left(revisions, row.revision)
129    if index < len(revisions) - 1:
130      if revisions[index + 1] == row.revision:
131        return  # Already in cache.
132    graph_rows.insert(index, _MakeTriplet(row))
133
134  for test_key in test_key_to_rows:
135    graph_rows = test_key_to_rows[test_key]
136    SetCache(utils.TestPath(test_key), graph_rows)
137