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

Side by Side Diff: client/tools/fleet.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 | « client/tools/cost.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
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
4 # can be found in the LICENSE file.
5
6 """Calculate statistics about tasks.
7
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.
10 """
11
12 import datetime
13 import json
14 import logging
15 import optparse
16 import os
17 import subprocess
18 import sys
19 import urllib
20
21
22 CLIENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23
24 _EPOCH = datetime.datetime.utcfromtimestamp(0)
25
26 # Type of bucket to use.
27 MAJOR_OS, MINOR_OS, MINOR_OS_GPU = range(3)
28
29
30 def seconds_to_timedelta(seconds):
31 """Converts seconds in datetime.timedelta, stripping sub-second precision.
32
33 This is for presentation, where subsecond values for summaries is not useful.
34 """
35 return datetime.timedelta(seconds=round(seconds))
36
37
38 def parse_time_option(value):
39 """Converts time as an option into a datetime.datetime.
40
41 Returns None if not specified.
42 """
43 if not value:
44 return None
45 try:
46 return _EPOCH + datetime.timedelta(seconds=int(value))
47 except ValueError:
48 pass
49 for fmt in (
50 '%Y-%m-%d',
51 '%Y-%m-%d %H:%M',
52 '%Y-%m-%dT%H:%M',
53 '%Y-%m-%d %H:%M:%S',
54 '%Y-%m-%dT%H:%M:%S',
55 '%Y-%m-%d %H:%M:%S.%f',
56 '%Y-%m-%dT%H:%M:%S.%f'):
57 try:
58 return datetime.datetime.strptime(value, fmt)
59 except ValueError:
60 pass
61 raise ValueError('Failed to parse %s' % value)
62
63
64 def parse_time(value):
65 """Converts serialized time from the API to datetime.datetime."""
66 for fmt in ('%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S'):
67 try:
68 return datetime.datetime.strptime(value, fmt)
69 except ValueError:
70 pass
71 raise ValueError('Failed to parse %s' % value)
72
73
74 def average(items):
75 if not items:
76 return 0.
77 return sum(items) / len(items)
78
79
80 def median(items):
81 return percentile(items, 50)
82
83
84 def percentile(items, percent):
85 """Uses NIST method."""
86 if not items:
87 return 0.
88 rank = percent * .01 * (len(items) + 1)
89 rank_int = int(rank)
90 rest = rank - rank_int
91 if rest and rank_int <= len(items) - 1:
92 return items[rank_int] + rest * (items[rank_int+1] - items[rank_int])
93 return items[min(rank_int, len(items) - 1)]
94
95
96 def sp(dividend, divisor):
97 """Returns the percentage for dividend/divisor, safely."""
98 if not divisor:
99 return 0.
100 return 100. * float(dividend) / float(divisor)
101
102
103 def fetch_data(options):
104 """Fetches data from options.swarming and writes it to options.json."""
105 cmd = [
106 sys.executable, os.path.join(CLIENT_DIR, 'swarming.py'),
107 'query',
108 '-S', options.swarming,
109 '--json', options.json,
110 # Start chocking at 1m bots. The chromium infrastructure is currently at
111 # around thousands range.
112 '--limit', '1000000',
113 '--progress',
114 'bots/list',
115 ]
116 if options.verbose:
117 cmd.append('--verbose')
118 cmd.append('--verbose')
119 cmd.append('--verbose')
120 logging.info('%s', ' '.join(cmd))
121 subprocess.check_call(cmd)
122 print('')
123
124
125 def present_data(bots, bucket_type, order_count):
126 buckets = do_bucket(bots, bucket_type)
127 maxlen = max(len(i) for i in buckets)
128 print('%-*s Alive Dead' % (maxlen, 'Type'))
129 counts = {
130 k: [len(v), sum(1 for i in v if i.get('is_dead'))]
131 for k, v in buckets.iteritems()}
132 key = (lambda x: -x[1][0]) if order_count else (lambda x: x)
133 for bucket, count in sorted(counts.iteritems(), key=key):
134 print('%-*s: %5d %5d' % (maxlen, bucket, count[0], count[1]))
135
136
137 def do_bucket(bots, bucket_type):
138 """Categorizes the bots based on one of the bucket type defined above."""
139 out = {}
140 for bot in bots:
141 # Convert dimensions from list of StringPairs to dict of list.
142 bot['dimensions'] = {i['key']: i['value'] for i in bot['dimensions']}
143 os_types = bot['dimensions']['os']
144 try:
145 os_types.remove('Linux')
146 except ValueError:
147 pass
148 if bucket_type == MAJOR_OS:
149 bucket = os_types[0]
150 else:
151 bucket = ' & '.join(os_types[1:])
152 if bucket_type == MINOR_OS_GPU:
153 gpu = bot['dimensions'].get('gpu', ['none'])[-1]
154 if gpu != 'none':
155 bucket += ' ' + gpu
156 out.setdefault(bucket, []).append(bot)
157 return out
158
159
160 def main():
161 parser = optparse.OptionParser(description=sys.modules['__main__'].__doc__)
162 parser.add_option(
163 '-S', '--swarming',
164 metavar='URL', default=os.environ.get('SWARMING_SERVER', ''),
165 help='Swarming server to use')
166 parser.add_option(
167 '--json', default='fleet.json',
168 help='File containing raw data; default: %default')
169 parser.add_option('-v', '--verbose', action='count', default=0)
170 parser.add_option('--count', action='store_true', help='Order by count')
171
172 group = optparse.OptionGroup(parser, 'Grouping')
173 group.add_option(
174 '--major-os', action='store_const',
175 dest='bucket', const=MAJOR_OS,
176 help='Classify by OS type, independent of OS version')
177 group.add_option(
178 '--minor-os', action='store_const',
179 dest='bucket', const=MINOR_OS,
180 help='Classify by minor OS version')
181 group.add_option(
182 '--gpu', action='store_const',
183 dest='bucket', const=MINOR_OS_GPU, default=MINOR_OS_GPU,
184 help='Classify by minor OS version and GPU type when requested (default)')
185 parser.add_option_group(group)
186
187 options, args = parser.parse_args()
188
189 if args:
190 parser.error('Unsupported argument %s' % args)
191 logging.basicConfig(level=logging.DEBUG if options.verbose else logging.ERROR)
192 if options.swarming:
193 fetch_data(options)
194 elif not os.path.isfile(options.json):
195 parser.error('--swarming is required.')
196
197 with open(options.json, 'rb') as f:
198 items = json.load(f)['items']
199 present_data(items, options.bucket, options.count)
200 return 0
201
202
203 if __name__ == '__main__':
204 sys.exit(main())
OLDNEW
« no previous file with comments | « client/tools/cost.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698