Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(114)

Side by Side Diff: swarm_get_results.py

Issue 22980008: Merge all swarm_*.py scripts into swarming.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Improvement, still not done Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
6 """Retrieves all the output that the Swarm server has produced for requests with
7 that name.
8 """
9
10 import json
11 import logging
12 import optparse
13 import sys
14 import threading
15 import time
16 import urllib
17
18 #from common import find_depot_tools # pylint: disable=W0611
19 #
20 ## From the depot tools
21 #import fix_encoding
22
23 import run_isolated
24
25
26 # The default time to wait for a shard to finish running.
27 DEFAULT_SHARD_WAIT_TIME = 40 * 60.
28
29
30 class Failure(Exception):
31 """Generic failure."""
32 pass
33
34
35 def get_test_keys(swarm_base_url, test_name, _=None):
36 """Returns the Swarm test key for each shards of test_name."""
37 # TODO(maruel): Remove the parameter '_' once the
38 # build/scripts/slave/get_swarm_results.py stops passing it.
39 key_data = urllib.urlencode([('name', test_name)])
40 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
41
42 for i in range(run_isolated.URL_OPEN_MAX_ATTEMPTS):
43 response = run_isolated.url_open(url, retry_404=True)
44 if response is None:
45 raise Failure(
46 'Error: Unable to find any tests with the name, %s, on swarm server'
47 % test_name)
48
49 result = response.read()
50 # TODO(maruel): Compare exact string.
51 if 'No matching' in result:
52 logging.warning('Unable to find any tests with the name, %s, on swarm '
53 'server' % test_name)
54 if i != run_isolated.URL_OPEN_MAX_ATTEMPTS:
55 run_isolated.HttpService.sleep_before_retry(i, None)
56 continue
57 return json.loads(result)
58
59 raise Failure(
60 'Error: Unable to find any tests with the name, %s, on swarm server'
61 % test_name)
62
63
64 class Bit(object):
65 """Thread safe setable bit."""
66 _lock = threading.Lock()
67 _value = False
68
69 def get(self):
70 with self._lock:
71 return self._value
72
73 def set(self):
74 with self._lock:
75 self._value = True
76
77
78 def now():
79 """Exists so it can be mocked easily."""
80 return time.time()
81
82
83 def retrieve_results(base_url, test_key, timeout, should_stop):
84 """Retrieves results for a single test_key."""
85 assert isinstance(timeout, float)
86 params = [('r', test_key)]
87 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
88 start = now()
89 while True:
90 if timeout and (now() - start) >= timeout:
91 logging.error('retrieve_results(%s) timed out', base_url)
92 return {}
93 # Do retries ourselves.
94 response = run_isolated.url_open(
95 result_url, retry_404=False, retry_50x=False)
96 if response is None:
97 # Aggressively poll for results. Do not use retry_404 so
98 # should_stop is polled more often.
99 remaining = min(5, timeout - (now() - start)) if timeout else 5
100 if remaining > 0:
101 run_isolated.HttpService.sleep_before_retry(1, remaining)
102 else:
103 try:
104 data = json.load(response) or {}
105 except (ValueError, TypeError):
106 logging.warning(
107 'Received corrupted data for test_key %s. Retrying.', test_key)
108 else:
109 if data['output']:
110 return data
111 if should_stop.get():
112 return {}
113
114
115 def yield_results(swarm_base_url, test_keys, timeout, max_threads):
116 """Yields swarm test results from the swarm server as (index, result).
117
118 Duplicate shards are ignored, the first one to complete is returned.
119
120 max_threads is optional and is used to limit the number of parallel fetches
121 done. Since in general the number of test_keys is in the range <=10, it's not
122 worth normally to limit the number threads. Mostly used for testing purposes.
123 """
124 shards_remaining = range(len(test_keys))
125 number_threads = (
126 min(max_threads, len(test_keys)) if max_threads else len(test_keys))
127 should_stop = Bit()
128 results_remaining = len(test_keys)
129 with run_isolated.ThreadPool(number_threads, number_threads, 0) as pool:
130 try:
131 for test_key in test_keys:
132 pool.add_task(
133 0, retrieve_results, swarm_base_url, test_key, timeout, should_stop)
134 while shards_remaining and results_remaining:
135 result = pool.get_one_result()
136 results_remaining -= 1
137 if not result:
138 # Failed to retrieve one key.
139 logging.error('Failed to retrieve the results for a swarm key')
140 continue
141 shard_index = result['config_instance_index']
142 if shard_index in shards_remaining:
143 shards_remaining.remove(shard_index)
144 yield shard_index, result
145 else:
146 logging.warning('Ignoring duplicate shard index %d', shard_index)
147 # Pop the last entry, there's no such shard.
148 shards_remaining.pop()
149 finally:
150 # Done, kill the remaining threads.
151 should_stop.set()
152
153
154 def parse_args():
155 run_isolated.disable_buffering()
156 parser = optparse.OptionParser(
157 usage='%prog [options] test_name',
158 description=sys.modules[__name__].__doc__)
159 parser.add_option(
160 '-u', '--url', default='http://localhost:8080',
161 help='Specify the url of the Swarm server, defaults: %default')
162 parser.add_option(
163 '-v', '--verbose', action='store_true',
164 help='Print verbose logging')
165 parser.add_option(
166 '-t', '--timeout',
167 type='float',
168 default=DEFAULT_SHARD_WAIT_TIME,
169 help='Timeout to wait for result, set to 0 for no timeout; default: '
170 '%default s')
171 # TODO(maruel): Remove once the masters have been updated.
172 parser.add_option(
173 '-s', '--shards',
174 help=optparse.SUPPRESS_HELP)
175
176 (options, args) = parser.parse_args()
177 if not args:
178 parser.error('Must specify one test name.')
179 elif len(args) > 1:
180 parser.error('Must specify only one test name.')
181 options.url = options.url.rstrip('/')
182 logging.basicConfig(level=logging.DEBUG if options.verbose else logging.ERROR)
183 return parser, options, args[0]
184
185
186 def main():
187 parser, options, test_name = parse_args()
188 try:
189 test_keys = get_test_keys(options.url, test_name)
190 except Failure as e:
191 parser.error(e.args[0])
192 if not test_keys:
193 parser.error('No test keys to get results with.')
194
195 options.shards = len(test_keys) if options.shards == -1 else options.shards
196 exit_code = None
197 for _index, output in yield_results(
198 options.url, test_keys, options.timeout, None):
199 print(
200 '%s/%s: %s' % (
201 output['machine_id'], output['machine_tag'], output['exit_codes']))
202 print(''.join(' %s\n' % l for l in output['output'].splitlines()))
203 exit_code = max(exit_code, max(map(int, output['exit_codes'].split(','))))
204
205 return exit_code
206
207
208 if __name__ == '__main__':
209 #fix_encoding.fix_encoding()
210 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698