| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 """Reduce the amount of data in ../test/data/*.json""" | |
| 6 | |
| 7 import json | |
| 8 import logging | |
| 9 import optparse | |
| 10 import os | |
| 11 import re | |
| 12 import sys | |
| 13 | |
| 14 | |
| 15 class Filterer(object): | |
| 16 def __init__(self): | |
| 17 self._deleted_builds = {} | |
| 18 self.max_cached_builds = 10 | |
| 19 self._allowed_builders = ('linux_clang', 'linux', 'linux_touch') | |
| 20 | |
| 21 def reset(self): | |
| 22 self._deleted_builds = {} | |
| 23 | |
| 24 def reduce_data(self, data): | |
| 25 """Reduces the amount of data sent from a server to simplify testing and the | |
| 26 amount of test data stored. | |
| 27 """ | |
| 28 self.reset() | |
| 29 for line in data[:]: | |
| 30 original_url, original_response = line | |
| 31 # Unpack and filter the response. | |
| 32 new_url, new_response = self.filter_response( | |
| 33 original_url, json.loads(original_response)) | |
| 34 if not new_url or new_response is None: | |
| 35 data.remove(line) | |
| 36 continue | |
| 37 # Repack the string. | |
| 38 line[1] = json.dumps(new_response, separators=(',',':')) | |
| 39 logging.info('%s length: %d -> %d' % ( | |
| 40 new_url, len(original_response), len(line[1]))) | |
| 41 if len(line[1]) > 20000: | |
| 42 logging.debug(line[1]) | |
| 43 return data | |
| 44 | |
| 45 def filter_response(self, url, response): | |
| 46 """Trims a single request. | |
| 47 | |
| 48 |response| must be decoded json. Decoded json will be returned. | |
| 49 """ | |
| 50 # Builders | |
| 51 match = re.match('.+/json/builders/(\w+)(|\?filter=1)$', url) | |
| 52 if match: | |
| 53 return url, self._filter_builder(match.group(1), response) | |
| 54 | |
| 55 match = re.match('.+/json/builders(|\?filter=1)$', url) | |
| 56 if match: | |
| 57 for builder in response.keys(): | |
| 58 value = self._filter_builder(builder, response[builder]) | |
| 59 if value is None: | |
| 60 del response[builder] | |
| 61 else: | |
| 62 response[builder] = value | |
| 63 return url, response | |
| 64 | |
| 65 # Pending | |
| 66 match = re.match('.+/json/builders/(\w+)/pendingBuilds(|\?filter=1)$', url) | |
| 67 if match: | |
| 68 assert match.group(1) in self._allowed_builders | |
| 69 return url, self._filter_pending(response) | |
| 70 | |
| 71 # Builds | |
| 72 match = re.match('.+/json/builders/(\w+)/builds/_all(|\?filter=1)$', url) | |
| 73 if match: | |
| 74 builder = match.group(1) | |
| 75 assert builder in self._allowed_builders, url | |
| 76 keys = response.keys() | |
| 77 keys = [int(k) for k in keys if int(k) not in self._deleted(builder)] | |
| 78 keys = sorted(keys)[:self.max_cached_builds] | |
| 79 response = dict((k, v) for k, v in response.iteritems() if int(k) in keys) | |
| 80 return url, response | |
| 81 | |
| 82 match = re.match('.+/json/builders/(\w+)/builds/\?(.+?)(|\&filter=1)$', url) | |
| 83 if match: | |
| 84 assert match.group(1) in self._allowed_builders | |
| 85 # Ignore the query, reconstruct it from what it kept. | |
| 86 for build in response.keys(): | |
| 87 value = self._filter_build(response[build]) | |
| 88 if value is None: | |
| 89 del response[build] | |
| 90 else: | |
| 91 response[build] = value | |
| 92 if not response: | |
| 93 return None, None | |
| 94 url = '%s?%s' % ( | |
| 95 url.split('?', 1)[0], | |
| 96 '&'.join('select=%s' % b for b in sorted(response))) | |
| 97 return url, response | |
| 98 | |
| 99 match = re.match('.+/json/builders/(\w+)/builds/(\d+)(|\?filter=1)$', url) | |
| 100 if match: | |
| 101 return url, self._filter_build(response) | |
| 102 | |
| 103 # Slaves | |
| 104 match = re.match('.+/json/slaves/([^/]+?)(|\?filter=1)$', url) | |
| 105 if match: | |
| 106 return url, self._filter_slave(match.group(1), response) | |
| 107 | |
| 108 match = re.match('.+/json/slaves(|\?filter=1)$', url) | |
| 109 if match: | |
| 110 for slave in response.keys(): | |
| 111 value = self._filter_slave(slave, response[slave]) | |
| 112 if value is None: | |
| 113 del response[slave] | |
| 114 else: | |
| 115 response[slave] = value | |
| 116 return url, response | |
| 117 | |
| 118 # Project | |
| 119 match = re.match('.+/json/project(|\?filter=1)$', url) | |
| 120 if match: | |
| 121 return url, response | |
| 122 | |
| 123 assert False, url | |
| 124 | |
| 125 @staticmethod | |
| 126 def _filter_pending(pending): | |
| 127 """Trim pendingBuilds.""" | |
| 128 return pending[:2] | |
| 129 | |
| 130 def _filter_builder(self, builder, response): | |
| 131 """Trims a builder. | |
| 132 | |
| 133 Reduces the number of cached builds. | |
| 134 """ | |
| 135 # TODO(maruel): Reduce the number of slaves. | |
| 136 if builder not in self._allowed_builders: | |
| 137 return None | |
| 138 builds_kept = response['cachedBuilds'][-self.max_cached_builds:] | |
| 139 builds_discarded = response['cachedBuilds'][:-self.max_cached_builds] | |
| 140 assert len(builds_kept) <= self.max_cached_builds | |
| 141 assert ( | |
| 142 len(builds_kept) + len(builds_discarded) == | |
| 143 len(response['cachedBuilds'])) | |
| 144 if builds_discarded: | |
| 145 assert min(builds_kept) > max(builds_discarded) | |
| 146 response['cachedBuilds'] = builds_kept | |
| 147 self._deleted(builder).union(int(b) for b in builds_discarded) | |
| 148 if response.get('currentBuilds'): | |
| 149 response['currentBuilds'] = [ | |
| 150 build for build in response['currentBuilds'] | |
| 151 if int(build) not in self._deleted(builder) | |
| 152 ] | |
| 153 if response.get('pendingBuilds', 0) > 2: | |
| 154 response['pendingBuilds'] = 2 | |
| 155 return response | |
| 156 | |
| 157 def _filter_build(self, response): | |
| 158 """Trims a build.""" | |
| 159 if response['builderName'] not in self._allowed_builders: | |
| 160 return None | |
| 161 if int(response['number']) in self._deleted(response['builderName']): | |
| 162 return None | |
| 163 # TODO(maruel): Fix StatusJson to not push that much logs data. | |
| 164 if 'logs' in response: | |
| 165 del response['logs'] | |
| 166 if response.get('currentStep') and response['currentStep'].get('logs'): | |
| 167 del response['currentStep']['logs'] | |
| 168 for step in response['steps']: | |
| 169 if 'logs' in step: | |
| 170 del step['logs'] | |
| 171 return response | |
| 172 | |
| 173 def _filter_slave(self, _slave, response): | |
| 174 """Trims a slave.""" | |
| 175 if response.get('builders'): | |
| 176 for builder in response['builders'].keys(): | |
| 177 if not builder in self._allowed_builders: | |
| 178 del response['builders'][builder] | |
| 179 if not response['builders']: | |
| 180 return None | |
| 181 if response.get('builderName'): | |
| 182 if not response['builderName'] in self._allowed_builders: | |
| 183 return None | |
| 184 if response.get('runningBuilds'): | |
| 185 for i, build in enumerate(response['runningBuilds'][:]): | |
| 186 value = self._filter_build(build) | |
| 187 if value is None: | |
| 188 response['runningBuilds'].remove(build) | |
| 189 else: | |
| 190 response['runningBuilds'][i] = value | |
| 191 return response | |
| 192 | |
| 193 def _deleted(self, builder): | |
| 194 return self._deleted_builds.get(builder, set()) | |
| 195 | |
| 196 | |
| 197 def main(): | |
| 198 parser = optparse.OptionParser( | |
| 199 description=sys.modules['__main__'].__doc__) | |
| 200 parser.add_option('-v', '--verbose', action='count', default=0) | |
| 201 parser.add_option('-d', '--dry-run', action='store_true') | |
| 202 options, args = parser.parse_args() | |
| 203 if args: | |
| 204 parser.error('Unsupported args: %s' % args) | |
| 205 logging.basicConfig( | |
| 206 level=[logging.WARNING, logging.INFO, logging.DEBUG][ | |
| 207 min(2, options.verbose)]) | |
| 208 | |
| 209 datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') | |
| 210 for filename in os.listdir(datadir): | |
| 211 if not filename.endswith('.json') or filename.endswith('_expected.json'): | |
| 212 continue | |
| 213 filepath = os.path.join(datadir, filename) | |
| 214 print 'Processing %s' % filename | |
| 215 data = json.load(open(filepath)) | |
| 216 data = Filterer().reduce_data(data) | |
| 217 if not options.dry_run: | |
| 218 json.dump(data, open(filepath, 'w'), separators=(',',':')) | |
| 219 return 0 | |
| 220 | |
| 221 | |
| 222 if __name__ == '__main__': | |
| 223 sys.exit(main()) | |
| OLD | NEW |