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