Changeset 109190 in webkit
- Timestamp:
- Feb 28, 2012 10:06:55 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/ChangeLog
r109180 r109190 1 2012-02-28 Ryosuke Niwa <rniwa@webkit.org> 2 3 perf-o-matic: generate dashboard images using Google Chart Tools 4 https://bugs.webkit.org/show_bug.cgi?id=79838 5 6 Reviewed by Hajime Morita. 7 8 Rename RunsJSONGenerator to Runs and added an ability to generate parameters for Google chart tool. 9 Also added RunsChartHandler to make url-fetches these images and DashboardImageHandler to serve them. 10 The image is stored in DashboardImage model. 11 12 We can't enable flip the switch to use images yet because we don't create images on fly (they're 13 generated when runs are updated; i.e. bots upload new results). We should be able to flip the switch 14 once this patch lands and all perf bots cycle. 15 16 We probably make way too many calls to Google chart tool's server with this preliminary design but we 17 can easily move this task into the backend and run it via a cron job once we know it works. 18 19 * Websites/webkit-perf.appspot.com/controller.py: 20 (schedule_runs_update): 21 (RunsUpdateHandler.post): 22 (RunsChartHandler): 23 (RunsChartHandler.get): 24 (RunsChartHandler.post): 25 (DashboardImageHandler): 26 (DashboardImageHandler.get): 27 (schedule_report_process): 28 * Websites/webkit-perf.appspot.com/json_generators.py: 29 (ManifestJSONGenerator.value): 30 (Runs): 31 (Runs.__init__): 32 (Runs.value): 33 (Runs.chart_params): 34 * Websites/webkit-perf.appspot.com/json_generators_unittest.py: 35 (RunsTest): 36 (RunsTest._create_results): 37 (RunsTest.test_generate_runs): 38 (RunsTest.test_value_without_results): 39 (RunsTest.test_value_with_results): 40 (RunsTest.test_run_from_build_and_result): 41 (RunsTest.test_chart_params_with_value): 42 (RunsTest.test_chart_params_with_value.split_as_int): 43 * Websites/webkit-perf.appspot.com/main.py: 44 * Websites/webkit-perf.appspot.com/models.py: 45 (PersistentCache.get_cache): 46 (DashboardImage): 47 (DashboardImage.key_name): 48 1 49 2012-02-28 Dave Tu <dtu@chromium.org> 2 50 -
trunk/Websites/webkit-perf.appspot.com/controller.py
r108917 r109190 28 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 29 30 import urllib 30 31 import webapp2 31 32 from google.appengine.api import taskqueue … … 34 35 from json_generators import DashboardJSONGenerator 35 36 from json_generators import ManifestJSONGenerator 36 from json_generators import Runs JSONGenerator37 from json_generators import Runs 37 38 from models import Branch 39 from models import DashboardImage 38 40 from models import Platform 39 41 from models import Test … … 98 100 def schedule_runs_update(test_id, branch_id, platform_id): 99 101 taskqueue.add(url='/api/test/runs/update', params={'id': test_id, 'branchid': branch_id, 'platformid': platform_id}) 102 taskqueue.add(url='/api/test/runs/chart', params={'id': test_id, 'branchid': branch_id, 'platformid': platform_id}) 100 103 101 104 … … 126 129 assert test 127 130 128 cache_runs(test_id, branch_id, platform_id, Runs JSONGenerator(branch, platform, test.name).to_json())131 cache_runs(test_id, branch_id, platform_id, Runs(branch, platform, test.name).to_json()) 129 132 self.response.out.write('OK') 130 133 … … 142 145 143 146 147 class RunsChartHandler(webapp2.RequestHandler): 148 def get(self): 149 self.post() 150 151 def post(self): 152 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' 153 test_id, branch_id, platform_id = _get_test_branch_platform_ids(self) 154 155 branch = model_from_numeric_id(branch_id, Branch) 156 platform = model_from_numeric_id(platform_id, Platform) 157 test = model_from_numeric_id(test_id, Test) 158 display_days = int(self.request.get('displayDays')) 159 assert branch 160 assert platform 161 assert test 162 163 params = Runs(branch, platform, test.name).chart_params(display_days) 164 dashboard_chart_file = urllib.urlopen('http://chart.googleapis.com/chart', urllib.urlencode(params)) 165 166 DashboardImage(key_name=DashboardImage.key_name(branch.id, platform.id, test.id, display_days), 167 image=dashboard_chart_file.read()).put() 168 169 self.response.out.write('Fetched http://chart.googleapis.com/chart?%s' % urllib.urlencode(params)) 170 171 172 class DashboardImageHandler(webapp2.RequestHandler): 173 def get(self, test_id, branch_id, platform_id, display_days): 174 try: 175 branch_id = int(branch_id) 176 platform_id = int(platform_id) 177 test_id = int(test_id) 178 display_days = int(display_days) 179 except ValueError: 180 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' 181 self.response.out.write('Failed') 182 183 self.response.headers['Content-Type'] = 'image/png' 184 image = DashboardImage.get_by_key_name(DashboardImage.key_name(branch_id, platform_id, test_id, display_days)) 185 if image: 186 self.response.out.write(image.image) 187 188 144 189 def schedule_report_process(log): 190 self.response.headers['Content-Type'] = 'application/json' 145 191 taskqueue.add(url='/api/test/report/process', params={'id': log.key().id()}) -
trunk/Websites/webkit-perf.appspot.com/json_generators.py
r108399 r109190 29 29 30 30 import json 31 from datetime import datetime 32 from datetime import timedelta 31 33 from time import mktime 32 34 … … 119 121 120 122 121 class RunsJSONGenerator(JSONGeneratorBase): 122 def __init__(self, branch, platform, test): 123 self._test_runs = [] 124 self._averages = {} 125 values = [] 126 127 for build, result in RunsJSONGenerator._generate_runs(branch, platform, test): 128 self._test_runs.append(RunsJSONGenerator._entry_from_build_and_result(build, result)) 129 # FIXME: Calculate the average. In practice, we wouldn't have more than one value for a given revision. 130 self._averages[build.revision] = result.value 131 values.append(result.value) 132 133 self._min = min(values) if values else None 134 self._max = max(values) if values else None 123 # FIXME: This isn't a JSON generator anymore. We should move it elsewhere or rename the file. 124 class Runs(JSONGeneratorBase): 125 def __init__(self, branch, platform, test_name): 126 self._branch = branch 127 self._platform = platform 128 self._test_name = test_name 135 129 136 130 @staticmethod … … 168 162 169 163 def value(self): 164 _test_runs = [] 165 _averages = {} 166 values = [] 167 168 for build, result in Runs._generate_runs(self._branch, self._platform, self._test_name): 169 _test_runs.append(Runs._entry_from_build_and_result(build, result)) 170 # FIXME: Calculate the average. In practice, we wouldn't have more than one value for a given revision. 171 _averages[build.revision] = result.value 172 values.append(result.value) 173 174 _min = min(values) if values else None 175 _max = max(values) if values else None 176 170 177 return { 171 'test_runs': self._test_runs,172 'averages': self._averages,173 'min': self._min,174 'max': self._max,178 'test_runs': _test_runs, 179 'averages': _averages, 180 'min': _min, 181 'max': _max, 175 182 'date_range': None, # Never used by common.js. 176 183 'stat': 'ok'} 184 185 def chart_params(self, display_days, now=datetime.now()): 186 chart_data_x = [] 187 chart_data_y = [] 188 end_time = now 189 start_timestamp = mktime((end_time - timedelta(display_days)).timetuple()) 190 end_timestamp = mktime(end_time.timetuple()) 191 192 for build, result in self._generate_runs(self._branch, self._platform, self._test_name): 193 timestamp = mktime(build.timestamp.timetuple()) 194 if timestamp < start_timestamp or timestamp > end_timestamp: 195 continue 196 chart_data_x.append(timestamp) 197 chart_data_y.append(result.value) 198 199 dates = [end_time + timedelta(day - display_days) for day in range(0, display_days + 1)] 200 201 y_max = max(chart_data_y) * 1.1 202 y_grid_step = y_max / 5 203 y_axis_label_step = int(y_grid_step + 0.5) # This won't work for decimal numbers 204 205 return { 206 'cht': 'lxy', # Specify with X and Y coordinates 207 'chxt': 'x,y', # Display both X and Y axies 208 'chxl': '0:|' + '|'.join([date.strftime('%b %d') for date in dates]), # X-axis labels 209 'chxr': '1,0,%f,%f' % (int(y_max + 0.5), y_axis_label_step), # Y-axis range: min=0, max, step 210 'chds': '%f,%f,%f,%f' % (start_timestamp, end_timestamp, 0, y_max), # X, Y data range 211 'chxs': '1,676767,11.167,0,l,676767', # Y-axis label: 1,color,font-size,centerd on tick,axis line/no ticks, tick color 212 'chs': '360x240', # Image size: 360px by 240px 213 'chco': 'ff0000', # Plot line color 214 'chg': '%f,%f,0,0' % (100 / (len(dates) - 1), y_grid_step), # X, Y grid line step sizes - max for X is 100. 215 'chls': '3', # Line thickness 216 'chf': 'bg,s,eff6fd', # Transparent background 217 'chd': 't:' + ','.join([str(x) for x in chart_data_x]) + '|' + ','.join([str(y) for y in chart_data_y]), # X, Y data 218 } -
trunk/Websites/webkit-perf.appspot.com/json_generators_unittest.py
r108399 r109190 34 34 from google.appengine.ext import testbed 35 35 from datetime import datetime 36 from datetime import timedelta 36 37 from json_generators import JSONGeneratorBase 37 38 from json_generators import DashboardJSONGenerator 38 39 from json_generators import ManifestJSONGenerator 39 from json_generators import Runs JSONGenerator40 from json_generators import Runs 40 41 from models_unittest import DataStoreTestsBase 41 42 from models import Branch … … 186 187 187 188 188 class Runs JSONGeneratorTest(DataStoreTestsBase):189 def _create_results(self, branch, platform, builder, test_name, values ):189 class RunsTest(DataStoreTestsBase): 190 def _create_results(self, branch, platform, builder, test_name, values, timestamps=None): 190 191 results = [] 191 192 for i, value in enumerate(values): 192 193 build = Build(branch=branch, platform=platform, builder=builder, 193 buildNumber=i, revision=100 + i, timestamp= datetime.now())194 buildNumber=i, revision=100 + i, timestamp=timestamps[i] if timestamps else datetime.now()) 194 195 build.put() 195 196 result = TestResult(name=test_name, build=build, value=value) … … 205 206 results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0]) 206 207 last_i = 0 207 for i, (build, result) in enumerate(Runs JSONGenerator._generate_runs(some_branch, some_platform, "some-test")):208 for i, (build, result) in enumerate(Runs._generate_runs(some_branch, some_platform, "some-test")): 208 209 self.assertEqual(build.buildNumber, i) 209 210 self.assertEqual(build.revision, 100 + i) … … 218 219 self.assertThereIsNoInstanceOf(Test) 219 220 self.assertThereIsNoInstanceOf(TestResult) 220 self.assertEqual(Runs JSONGenerator(some_branch, some_platform, 'some-test').value(), {221 self.assertEqual(Runs(some_branch, some_platform, 'some-test').value(), { 221 222 'test_runs': [], 222 223 'averages': {}, … … 232 233 results = self._create_results(some_branch, some_platform, some_builder, 'some-test', [50.0, 51.0, 52.0, 49.0, 48.0]) 233 234 234 value = Runs JSONGenerator(some_branch, some_platform, 'some-test').value()235 value = Runs(some_branch, some_platform, 'some-test').value() 235 236 self.assertEqualUnorderedList(value.keys(), ['test_runs', 'averages', 'min', 'max', 'date_range', 'stat']) 236 237 self.assertEqual(value['stat'], 'ok') … … 275 276 result = TestResult(name=test_name, value=123.0, build=build) 276 277 result.put() 277 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 123.0)278 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 123.0) 278 279 279 280 build = create_build(2, 102) 280 281 result = TestResult(name=test_name, value=456.0, valueMedian=789.0, build=build) 281 282 result.put() 282 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 456.0)283 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0) 283 284 284 285 result.valueStdev = 7.0 285 286 result.put() 286 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 456.0)287 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0) 287 288 288 289 result.valueStdev = None … … 290 291 result.valueMax = 789.0 291 292 result.put() 292 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 456.0)293 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0) 293 294 294 295 result.valueStdev = 8.0 … … 296 297 result.valueMax = 789.0 297 298 result.put() 298 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 456.0,299 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0, 299 300 statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0}) 300 301 … … 304 305 result.valueMax = 789.0 305 306 result.put() 306 self._assert_entry(Runs JSONGenerator._entry_from_build_and_result(build, result), build, result, 456.0,307 self._assert_entry(Runs._entry_from_build_and_result(build, result), build, result, 456.0, 307 308 statistics={'stdev': 8.0, 'min': 123.0, 'max': 789.0}) 309 310 def test_chart_params_with_value(self): 311 some_branch = Branch.create_if_possible('some-branch', 'Some Branch') 312 some_platform = Platform.create_if_possible('some-platform', 'Some Platform') 313 some_builder = Builder.get(Builder.create('some-builder', 'Some Builder')) 314 315 start_time = datetime(2011, 2, 21, 12, 0, 0) 316 end_time = datetime(2011, 2, 28, 12, 0, 0) 317 results = self._create_results(some_branch, some_platform, some_builder, 'some-test', 318 [50.0, 51.0, 52.0, 49.0, 48.0, 51.9, 50.7, 51.1], 319 [start_time + timedelta(day) for day in range(0, 8)]) 320 321 # Use int despite of its impreciseness since tests may fail due to rounding errors otherwise. 322 def split_as_int(string): 323 return [int(float(value)) for value in string.split(',')] 324 325 params = Runs(some_branch, some_platform, 'some-test').chart_params(7, end_time) 326 self.assertEqual(params['chxl'], '0:|Feb 21|Feb 22|Feb 23|Feb 24|Feb 25|Feb 26|Feb 27|Feb 28') 327 self.assertEqual(split_as_int(params['chxr']), [1, 0, 57, int(52 * 1.1 / 5 + 0.5)]) 328 x_min, x_max, y_min, y_max = split_as_int(params['chds']) 329 self.assertEqual(datetime.fromtimestamp(x_min), start_time) 330 self.assertEqual(datetime.fromtimestamp(x_max), end_time) 331 self.assertEqual(y_min, 0) 332 self.assertEqual(y_max, int(52 * 1.1)) 333 self.assertEqual(split_as_int(params['chg']), [int(100 / 7), int(52 * 1.1 / 5), 0, 0]) 334 308 335 309 336 -
trunk/Websites/webkit-perf.appspot.com/main.py
r109057 r109190 27 27 from controller import CachedManifestHandler 28 28 from controller import CachedRunsHandler 29 from controller import DashboardImageHandler 29 30 from controller import DashboardUpdateHandler 30 31 from controller import ManifestUpdateHandler 32 from controller import RunsChartHandler 31 33 from controller import RunsUpdateHandler 32 34 from create_handler import CreateHandler … … 42 44 ('/admin/create/(.*)', CreateHandler), 43 45 (r'/admin/([A-Za-z\-]*)', AdminDashboardHandler), 46 44 47 ('/api/user/is-admin', IsAdminHandler), 45 48 ('/api/test/?', CachedManifestHandler), … … 49 52 ('/api/test/runs/?', CachedRunsHandler), 50 53 ('/api/test/runs/update', RunsUpdateHandler), 54 ('/api/test/runs/chart', RunsChartHandler), 51 55 ('/api/test/dashboard/?', CachedDashboardHandler), 52 56 ('/api/test/dashboard/update', DashboardUpdateHandler), 53 ] 57 58 ('/images/dashboard/flot-(\d+)-(\d+)-(\d+)_(\d+).png', DashboardImageHandler)] 54 59 55 60 -
trunk/Websites/webkit-perf.appspot.com/models.py
r109057 r109190 323 323 memcache.set(name, cache.value) 324 324 return cache.value 325 326 327 class DashboardImage(db.Model): 328 image = db.BlobProperty(required=True) 329 330 @staticmethod 331 def key_name(branch_id, platform_id, test_id, display_days): 332 return '%d:%d:%d:%d' % (branch_id, platform_id, test_id, display_days)
Note: See TracChangeset
for help on using the changeset viewer.