OLD | NEW |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Handler to serve a simple time series for all points in a series. | 5 """Handler to serve a simple time series for all points in a series. |
6 | 6 |
7 This is used to show the revision slider for a chart; it includes data | 7 This is used to show the revision slider for a chart; it includes data |
8 for all past points, including those that are not recent. Each entry | 8 for all past points, including those that are not recent. Each entry |
9 in the returned list is a 3-item list: [revision, value, timestamp]. | 9 in the returned list is a 3-item list: [revision, value, timestamp]. |
10 The revisions and values are used to plot a mini-chart, and the timestamps | 10 The revisions and values are used to plot a mini-chart, and the timestamps |
11 are used to label this mini-chart with dates. | 11 are used to label this mini-chart with dates. |
12 | 12 |
13 This list is cached, since querying all Row entities for a given test takes a | 13 This list is cached, since querying all Row entities for a given test takes a |
14 long time. This module also provides a function for updating the cache. | 14 long time. This module also provides a function for updating the cache. |
15 """ | 15 """ |
16 | 16 |
17 import bisect | 17 import bisect |
18 import json | 18 import json |
19 | 19 |
| 20 from google.appengine.ext import ndb |
| 21 |
20 from dashboard.common import datastore_hooks | 22 from dashboard.common import datastore_hooks |
21 from dashboard.common import namespaced_stored_object | 23 from dashboard.common import namespaced_stored_object |
22 from dashboard.common import request_handler | 24 from dashboard.common import request_handler |
23 from dashboard.common import utils | 25 from dashboard.common import utils |
24 from dashboard.models import graph_data | 26 from dashboard.models import graph_data |
25 | 27 |
26 _CACHE_KEY = 'num_revisions_%s' | 28 _CACHE_KEY = 'num_revisions_%s' |
27 | 29 |
28 | 30 |
29 class GraphRevisionsHandler(request_handler.RequestHandler): | 31 class GraphRevisionsHandler(request_handler.RequestHandler): |
30 """URL endpoint to list all the revisions for each test, for x-axis slider.""" | 32 """URL endpoint to list all the revisions for each test, for x-axis slider.""" |
31 | 33 |
32 def post(self): | 34 def post(self): |
33 """Fetches a list of revisions and values for a given test. | 35 """Fetches a list of revisions and values for a given test. |
34 | 36 |
35 Request parameters: | 37 Request parameters: |
36 test_path: Full test path for a TestMetadata entity. | 38 test_path: Full test path for a TestMetadata entity. |
37 | 39 |
38 Outputs: | 40 Outputs: |
39 A JSON list of 3-item lists [revision, value, timestamp]. | 41 A JSON list of 3-item lists [revision, value, timestamp]. |
40 """ | 42 """ |
41 test_path = self.request.get('test_path') | 43 test_path = self.request.get('test_path') |
42 rows = namespaced_stored_object.Get(_CACHE_KEY % test_path) | 44 rows = namespaced_stored_object.Get(_CACHE_KEY % test_path) |
43 if not rows: | 45 if not rows: |
44 rows = _UpdateCache(utils.TestKey(test_path)) | 46 rows = _UpdateCache(utils.TestKey(test_path)) |
45 self.response.out.write(json.dumps(rows)) | 47 self.response.out.write(json.dumps(rows)) |
46 | 48 |
47 | 49 |
| 50 @ndb.synctasklet |
48 def SetCache(test_path, rows): | 51 def SetCache(test_path, rows): |
49 """Sets the saved graph revisions data for a test. | 52 """Sets the saved graph revisions data for a test. |
50 | 53 |
51 Args: | 54 Args: |
52 test_path: A test path string. | 55 test_path: A test path string. |
53 rows: A list of [revision, value, timestamp] triplets. | 56 rows: A list of [revision, value, timestamp] triplets. |
54 """ | 57 """ |
| 58 yield SetCacheAsync(test_path, rows) |
| 59 |
| 60 |
| 61 @ndb.tasklet |
| 62 def SetCacheAsync(test_path, rows): |
55 # This first set generally only sets the internal-only cache. | 63 # This first set generally only sets the internal-only cache. |
56 namespaced_stored_object.Set(_CACHE_KEY % test_path, rows) | 64 futures = [namespaced_stored_object.SetAsync(_CACHE_KEY % test_path, rows)] |
57 | 65 |
58 # If this is an internal_only query for externally available data, | 66 # If this is an internal_only query for externally available data, |
59 # set the cache for that too. | 67 # set the cache for that too. |
60 if datastore_hooks.IsUnalteredQueryPermitted(): | 68 if datastore_hooks.IsUnalteredQueryPermitted(): |
61 test = utils.TestKey(test_path).get() | 69 test = utils.TestKey(test_path).get() |
62 if test and not test.internal_only: | 70 if test and not test.internal_only: |
63 namespaced_stored_object.SetExternal(_CACHE_KEY % test_path, rows) | 71 futures.append( |
| 72 namespaced_stored_object.SetExternalAsync( |
| 73 _CACHE_KEY % test_path, rows)) |
| 74 yield futures |
64 | 75 |
65 | 76 |
66 def DeleteCache(test_path): | 77 def DeleteCache(test_path): |
67 """Removes any saved data for the given path.""" | 78 """Removes any saved data for the given path.""" |
68 namespaced_stored_object.Delete(_CACHE_KEY % test_path) | 79 namespaced_stored_object.Delete(_CACHE_KEY % test_path) |
69 | 80 |
70 | 81 |
71 def _UpdateCache(test_key): | 82 def _UpdateCache(test_key): |
72 """Queries Rows for a test then updates the cache. | 83 """Queries Rows for a test then updates the cache. |
73 | 84 |
(...skipping 23 matching lines...) Expand all Loading... |
97 SetCache(utils.TestPath(test_key), rows) | 108 SetCache(utils.TestPath(test_key), rows) |
98 return rows | 109 return rows |
99 | 110 |
100 | 111 |
101 def _MakeTriplet(row): | 112 def _MakeTriplet(row): |
102 """Makes a 3-item list of revision, value and timestamp for a Row.""" | 113 """Makes a 3-item list of revision, value and timestamp for a Row.""" |
103 timestamp = utils.TimestampMilliseconds(row.timestamp) | 114 timestamp = utils.TimestampMilliseconds(row.timestamp) |
104 return [row.revision, row.value, timestamp] | 115 return [row.revision, row.value, timestamp] |
105 | 116 |
106 | 117 |
| 118 @ndb.synctasklet |
107 def AddRowsToCache(row_entities): | 119 def AddRowsToCache(row_entities): |
108 """Adds a list of rows to the cache, in revision order. | 120 """Adds a list of rows to the cache, in revision order. |
109 | 121 |
110 Updates multiple cache entries for different tests. | 122 Updates multiple cache entries for different tests. |
111 | 123 |
112 Args: | 124 Args: |
113 row_entities: List of Row entities. | 125 row_entities: List of Row entities. |
114 """ | 126 """ |
| 127 test_key_to_futures = {} |
| 128 for row in row_entities: |
| 129 test_key = row.parent_test |
| 130 if test_key in test_key_to_futures: |
| 131 continue |
| 132 |
| 133 test_path = utils.TestPath(test_key) |
| 134 test_key_to_futures[test_key] = namespaced_stored_object.GetAsync( |
| 135 _CACHE_KEY % test_path) |
| 136 |
| 137 yield test_key_to_futures.values() |
| 138 |
115 test_key_to_rows = {} | 139 test_key_to_rows = {} |
116 for row in row_entities: | 140 for row in row_entities: |
117 test_key = row.parent_test | 141 test_key = row.parent_test |
118 if test_key in test_key_to_rows: | 142 if test_key in test_key_to_rows: |
119 graph_rows = test_key_to_rows[test_key] | 143 graph_rows = test_key_to_rows[test_key] |
120 else: | 144 else: |
121 test_path = utils.TestPath(test_key) | 145 test_path = utils.TestPath(test_key) |
122 graph_rows = namespaced_stored_object.Get(_CACHE_KEY % test_path) | 146 graph_rows_future = test_key_to_futures.get(test_key) |
| 147 graph_rows = graph_rows_future.get_result() |
123 if not graph_rows: | 148 if not graph_rows: |
124 # We only want to update caches for tests that people have looked at. | 149 # We only want to update caches for tests that people have looked at. |
125 continue | 150 continue |
126 test_key_to_rows[test_key] = graph_rows | 151 test_key_to_rows[test_key] = graph_rows |
127 | 152 |
128 revisions = [r[0] for r in graph_rows] | 153 revisions = [r[0] for r in graph_rows] |
129 index = bisect.bisect_left(revisions, row.revision) | 154 index = bisect.bisect_left(revisions, row.revision) |
130 if index < len(revisions) - 1: | 155 if index < len(revisions) - 1: |
131 if revisions[index + 1] == row.revision: | 156 if revisions[index + 1] == row.revision: |
132 return # Already in cache. | 157 return # Already in cache. |
133 graph_rows.insert(index, _MakeTriplet(row)) | 158 graph_rows.insert(index, _MakeTriplet(row)) |
134 | 159 |
| 160 futures = [] |
135 for test_key in test_key_to_rows: | 161 for test_key in test_key_to_rows: |
136 graph_rows = test_key_to_rows[test_key] | 162 graph_rows = test_key_to_rows[test_key] |
137 SetCache(utils.TestPath(test_key), graph_rows) | 163 futures.append(SetCacheAsync(utils.TestPath(test_key), graph_rows)) |
| 164 yield futures |
OLD | NEW |