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