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

Side by Side Diff: client/tools/cost.py

Issue 1337923004: Improve cost.py and add fleet.py. (Closed) Base URL: git@github.com:luci/luci-py.git@1_rev
Patch Set: Created 5 years, 3 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
« no previous file with comments | « no previous file | client/tools/fleet.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Swarming Authors. All rights reserved. 2 # Copyright 2015 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 that 3 # Use of this source code is governed under the Apache License, Version 2.0 that
4 # can be found in the LICENSE file. 4 # can be found in the LICENSE file.
5 5
6 """Calculate statistics about tasks. 6 """Calculate statistics about tasks.
7 7
8 Saves the data fetched from the server into a json file to enable reprocessing 8 Saves the data fetched from the server into a json file to enable reprocessing
9 the data without having to always fetch from the server. 9 the data without having to always fetch from the server.
10 """ 10 """
11 11
12 import datetime 12 import datetime
13 import json 13 import json
14 import logging 14 import logging
15 import optparse 15 import optparse
16 import os 16 import os
17 import subprocess 17 import subprocess
18 import sys 18 import sys
19 import urllib 19 import urllib
20 20
21 21
22 CLIENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 CLIENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23 23
24 _EPOCH = datetime.datetime.utcfromtimestamp(0) 24 _EPOCH = datetime.datetime.utcfromtimestamp(0)
25 25
26 # Type of bucket to use.
27 MAJOR_OS, MAJOR_OS_ASAN, MINOR_OS, MINOR_OS_GPU = range(4)
26 28
27 def do_bucket(items, bucket_major_os, bucket_gpu): 29
30 def do_bucket(items, bucket_type):
31 """Categorizes the tasks based on one of the bucket type defined above."""
28 out = {} 32 out = {}
29 for task in items: 33 for task in items:
30 if 'heartbeat:1' in task['tags']: 34 if 'heartbeat:1' in task['tags']:
31 # Skip heartbeats. 35 # Skip heartbeats.
32 continue 36 continue
37
38 is_asan = 'asan:1' in task['tags']
33 os_tag = None 39 os_tag = None
34 gpu_tag = None 40 gpu_tag = None
35 for t in task['tags']: 41 for t in task['tags']:
36 if t.startswith('os:'): 42 if t.startswith('os:'):
37 os_tag = t[3:] 43 os_tag = t[3:]
38 if os_tag == 'Linux': 44 if os_tag == 'Linux':
39 # GPU tests still specify Linux. 45 # GPU tests still specify Linux.
40 # TODO(maruel): Fix the recipe. 46 # TODO(maruel): Fix the recipe.
41 os_tag = 'Ubuntu' 47 os_tag = 'Ubuntu'
42 if bucket_major_os:
43 os_tag = os_tag.split('-')[0]
44 elif t.startswith('gpu:'): 48 elif t.startswith('gpu:'):
45 gpu_tag = t[4:] 49 gpu_tag = t[4:]
50
51 if bucket_type in (MAJOR_OS, MAJOR_OS_ASAN):
52 os_tag = os_tag.split('-')[0]
46 tag = os_tag 53 tag = os_tag
47 if bucket_gpu and gpu_tag and gpu_tag != 'none': 54 if bucket_type == MINOR_OS_GPU and gpu_tag and gpu_tag != 'none':
48 tag += ' gpu:' + gpu_tag 55 tag += ' gpu:' + gpu_tag
56 if bucket_type == MAJOR_OS_ASAN and is_asan:
57 tag += ' ASan'
49 out.setdefault(tag, []).append(task) 58 out.setdefault(tag, []).append(task)
59
60 # Also create global buckets for ASan.
61 if bucket_type == MAJOR_OS_ASAN:
62 tag = '(any OS) ASan' if is_asan else '(any OS) Not ASan'
63 out.setdefault(tag, []).append(task)
50 return out 64 return out
51 65
52 66
53 def seconds_to_timedelta(seconds): 67 def seconds_to_timedelta(seconds):
54 """Converts seconds in datetime.timedelta, stripping sub-second precision. 68 """Converts seconds in datetime.timedelta, stripping sub-second precision.
55 69
56 This is for presentation, where subsecond values for summaries is not useful. 70 This is for presentation, where subsecond values for summaries is not useful.
57 """ 71 """
58 return datetime.timedelta(seconds=round(seconds)) 72 return datetime.timedelta(seconds=round(seconds))
59 73
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after
273 len(failures), percent_failures)) 287 len(failures), percent_failures))
274 print ( 288 print (
275 ' Retried: %-4d (%5.3f%%) (Upgraded an internal failure ' 289 ' Retried: %-4d (%5.3f%%) (Upgraded an internal failure '
276 'to a successful task)' % 290 'to a successful task)' %
277 (len(two_tries), percent_two_tries)) 291 (len(two_tries), percent_two_tries))
278 print ( 292 print (
279 ' Pending Total: %13s Avg: %7s Median: %7s P99%%: %7s' % ( 293 ' Pending Total: %13s Avg: %7s Median: %7s P99%%: %7s' % (
280 pending_total, pending_avg, pending_med, pending_p99)) 294 pending_total, pending_avg, pending_med, pending_p99))
281 295
282 296
283 def present_data(items, bucket_major_os, bucket_gpu, show_cost): 297 def present_task_types(items, bucket_type, show_cost):
284 cost = ' Usage Cost $USD' if show_cost else '' 298 cost = ' Usage Cost $USD' if show_cost else ''
285 print(' Nb of Tasks Total Duration%s' % cost) 299 print(' Nb of Tasks Total Duration%s' % cost)
286 buckets = do_bucket(items, bucket_major_os, bucket_gpu) 300 buckets = do_bucket(items, bucket_type)
287 for index, (bucket, tasks) in enumerate(sorted(buckets.iteritems())): 301 for index, (bucket, tasks) in enumerate(sorted(buckets.iteritems())):
288 if index: 302 if index:
289 print('') 303 print('')
290 print('%s:' % (bucket)) 304 print('%s:' % (bucket))
291 stats(tasks, show_cost) 305 stats(tasks, show_cost)
292 if buckets: 306 if buckets:
293 print('') 307 print('')
294 print('Global:') 308 print('Global:')
295 stats(items, show_cost) 309 stats(items, show_cost)
296 310
297 311
312 def present_users(items):
313 users = {}
314 for task in items:
315 user = ''
316 for tag in task['tags']:
317 if tag.startswith('user:'):
318 if tag[5:]:
319 user = tag[5:]
320 break
321 if tag == 'purpose:CI':
322 user = 'CI'
323 break
324 if tag == 'heartbeat:1':
325 user = 'heartbeat'
326 break
327 if user:
328 users.setdefault(user, 0)
329 users[user] += 1
330 maxlen = max(len(i) for i in users)
331 maxusers = 100
332 for index, (name, tasks) in enumerate(
333 sorted(users.iteritems(), key=lambda x: -x[1])):
334 if index == maxusers:
335 break
336 print('%3d %-*s: %d' % (index + 1, maxlen, name, tasks))
337
338
298 def main(): 339 def main():
299 parser = optparse.OptionParser(description=sys.modules['__main__'].__doc__) 340 parser = optparse.OptionParser(description=sys.modules['__main__'].__doc__)
300 parser.add_option( 341 parser.add_option(
301 '-S', '--swarming', 342 '-S', '--swarming',
302 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''), 343 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
303 help='Swarming server to use') 344 help='Swarming server to use')
304 parser.add_option( 345 parser.add_option(
305 '--start', help='Starting date in UTC; defaults to 25 hours ago') 346 '--start', help='Starting date in UTC; defaults to 25 hours ago')
306 parser.add_option( 347 parser.add_option(
307 '--end', help='End date in UTC; defaults to --start+1 day') 348 '--end', help='End date in UTC; defaults to --start+1 day')
308 parser.add_option( 349 parser.add_option(
309 '--broad', action='store_true', help='Strip OS version from buckets')
310 parser.add_option(
311 '--gpu', action='store_true', help='Buckets per GPU')
312 parser.add_option(
313 '--no-cost', action='store_false', dest='cost', default=True, 350 '--no-cost', action='store_false', dest='cost', default=True,
314 help='Strip $ from display') 351 help='Strip $ from display')
315 parser.add_option( 352 parser.add_option(
316 '--json', default='stats.json', 353 '--users', action='store_true', help='Display top users instead')
354 parser.add_option(
355 '--json', default='tasks.json',
317 help='File containing raw data; default: %default') 356 help='File containing raw data; default: %default')
318 parser.add_option('-v', '--verbose', action='count', default=0) 357 parser.add_option('-v', '--verbose', action='count', default=0)
358
359 group = optparse.OptionGroup(parser, 'Grouping')
360 group.add_option(
361 '--major-os', action='store_const',
362 dest='bucket', const=MAJOR_OS, default=MAJOR_OS,
363 help='Classify by OS type, independent of OS version (default)')
364 group.add_option(
365 '--minor-os', action='store_const',
366 dest='bucket', const=MINOR_OS,
367 help='Classify by minor OS version')
368 group.add_option(
369 '--gpu', action='store_const',
370 dest='bucket', const=MINOR_OS_GPU,
371 help='Classify by minor OS version and GPU type when requested')
372 group.add_option(
373 '--asan', action='store_const',
374 dest='bucket', const=MAJOR_OS_ASAN,
375 help='Classify by major OS version and ASAN')
376 parser.add_option_group(group)
377
319 options, args = parser.parse_args() 378 options, args = parser.parse_args()
320 379
321 if args: 380 if args:
322 parser.error('Unsupported argument %s' % args) 381 parser.error('Unsupported argument %s' % args)
323 logging.basicConfig(level=logging.DEBUG if options.verbose else logging.ERROR) 382 logging.basicConfig(level=logging.DEBUG if options.verbose else logging.ERROR)
324 if options.swarming: 383 if options.swarming:
325 fetch_data(options) 384 fetch_data(options)
326 elif not os.path.isfile(options.json): 385 elif not os.path.isfile(options.json):
327 parser.error('--swarming is required.') 386 parser.error('--swarming is required.')
328 387
329 with open(options.json, 'rb') as f: 388 with open(options.json, 'rb') as f:
330 items = json.load(f)['items'] 389 items = json.load(f)['items']
331 first = items[-1] 390 first = items[-1]
332 last = items[0] 391 last = items[0]
333 print( 392 print(
334 'From %s to %s (%s)' % ( 393 'From %s to %s (%s)' % (
335 first['created_ts'].split('.')[0], 394 first['created_ts'].split('.')[0],
336 last['created_ts'].split('.')[0], 395 last['created_ts'].split('.')[0],
337 parse_time(last['created_ts']) - parse_time(first['created_ts']) 396 parse_time(last['created_ts']) - parse_time(first['created_ts'])
338 )) 397 ))
339 print('') 398 print('')
340 present_data(items, options.broad, options.gpu, options.cost) 399
400 if options.users:
401 present_users(items)
402 else:
403 present_task_types(items, options.bucket, options.cost)
341 return 0 404 return 0
342 405
343 406
344 if __name__ == '__main__': 407 if __name__ == '__main__':
345 sys.exit(main()) 408 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | client/tools/fleet.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698