blob: 4391c41d2ccaead548faa98d1c0926d8b26296cf [file] [log] [blame]
Yuheng Long16d7a522013-07-19 16:29:13 -07001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Yuheng Longf20cffa2013-06-03 18:46:00 -07005"""A reproducing entity.
6
Yuheng Long49358b72013-07-10 14:45:29 -07007Part of the Chrome build flags optimization.
8
Yuheng Longf20cffa2013-06-03 18:46:00 -07009The Task class is used by different modules. Each module fills in the
10corresponding information into a Task instance. Class Task contains the bit set
11representing the flags selection. The builder module is responsible for filling
12the image and the checksum field of a Task. The executor module will put the
13execution output to the execution field.
14"""
15
16__author__ = 'yuhenglong@google.com (Yuheng Long)'
17
Yuheng Longb15d41c2013-07-25 10:02:36 -070018import os
19import subprocess
20import sys
21from uuid import uuid4
22
23BUILD_STAGE = 1
24TEST_STAGE = 2
25
26# Message indicating that the build or test failed.
27ERROR_STRING = 'error'
28
Yuheng Longf20cffa2013-06-03 18:46:00 -070029
30class Task(object):
31 """A single reproducing entity.
32
33 A single test of performance with a particular set of flags. It records the
34 flag set, the image, the check sum of the image and the cost.
35 """
36
Yuheng Longb15d41c2013-07-25 10:02:36 -070037 def __init__(self, flag_set, build_command, test_command, log_directory,
38 build_tries, test_tries):
Yuheng Longf20cffa2013-06-03 18:46:00 -070039 """Set up the optimization flag selection for this task.
40
Yuheng Longb15d41c2013-07-25 10:02:36 -070041 This framework is generic. It lets the client specify application specific
42 compile and test methods by passing different build_command and
43 test_command.
44
Yuheng Longf20cffa2013-06-03 18:46:00 -070045 Args:
Yuheng Longb15d41c2013-07-25 10:02:36 -070046 flag_set: The optimization flag set that is encapsulated by this task.
47 build_command: The command that will be used in the build stage to compile
48 this task.
49 test_command: The command that will be used in the test stage to test this
50 task.
51 log_directory: The directory to log the compilation and test results.
52 build_tries: The maximum number of tries a build can have. Some
53 compilations may fail due to unexpected environment circumstance. This
54 variable defines how many tries the build should attempt before giving
55 up.
56 test_tries: The maximum number of tries a build can have. Some tests may
57 fail due to unexpected environment circumstance. This variable defines
58 how many tries the test should attempt before giving up.
Yuheng Longf20cffa2013-06-03 18:46:00 -070059 """
Yuheng Longb15d41c2013-07-25 10:02:36 -070060
Yuheng Longf20cffa2013-06-03 18:46:00 -070061 self._flag_set = flag_set
Yuheng Longb15d41c2013-07-25 10:02:36 -070062 self._build_command = build_command
63 self._test_command = test_command
64 self._log_directory = log_directory
65 self._build_tries = build_tries
66 self._test_tries = test_tries
Yuheng Longf20cffa2013-06-03 18:46:00 -070067
Yuheng Longb15d41c2013-07-25 10:02:36 -070068 # A unique identifier that distinguishes this task from other tasks.
69 self.task_identifier = uuid4()
Yuheng Longf20cffa2013-06-03 18:46:00 -070070
Yuheng Longb15d41c2013-07-25 10:02:36 -070071 self._log_path = (self._log_directory, self.task_identifier)
72
73 # Initiate the hash value. The hash value is used so as not to recompute it
74 # every time the hash method is called.
75 self._hash_value = None
76
77 # Indicate that the task has not been compiled/tested.
78 self._build_cost = None
79 self._exe_cost = None
80 self._checksum = None
81 self._image = None
82
83 def __eq__(self, other):
84 """Test whether two tasks are equal.
85
86 Two tasks are equal if their flag_set are equal.
Yuheng Longf20cffa2013-06-03 18:46:00 -070087
88 Args:
Yuheng Longb15d41c2013-07-25 10:02:36 -070089 other: The other task with which this task is tested equality.
90 Returns:
91 True if the encapsulated flag sets are equal.
Yuheng Longf20cffa2013-06-03 18:46:00 -070092 """
Yuheng Longb15d41c2013-07-25 10:02:36 -070093 if isinstance(other, Task):
94 return self.GetFlags() == other.GetFlags()
95 return False
Yuheng Longf20cffa2013-06-03 18:46:00 -070096
Yuheng Longb15d41c2013-07-25 10:02:36 -070097 def __hash__(self):
98 if self._hash_value is None:
99 # Cache the hash value of the flags, so as not to recompute them.
100 self._hash_value = hash(self._flag_set)
101 return self._hash_value
102
103 def GetIdentifier(self, stage):
104 """Get the identifier of the task in the stage.
105
106 The flag set uniquely identifies a task in the build stage. The checksum of
107 the image of the task uniquely identifies the task in the test stage.
108
109 Args:
110 stage: The stage (build/test) in which this method is called.
111 Returns:
112 Return the flag set in build stage and return the checksum in test stage.
113 """
114
115 # Define the dictionary for different stage function lookup.
116 get_identifier_functions = {BUILD_STAGE: self.GetFlags,
117 TEST_STAGE: self.__GetCheckSum}
118
119 assert stage in get_identifier_functions
120 return get_identifier_functions[stage]()
121
122 def GetResult(self, stage):
123 """Get the performance results of the task in the stage.
124
125 Args:
126 stage: The stage (build/test) in which this method is called.
127 Returns:
128 Performance results.
129 """
130
131 # Define the dictionary for different stage function lookup.
132 get_result_functions = {BUILD_STAGE: self.__GetBuildResult,
133 TEST_STAGE: self.__GetTestResult}
134
135 assert stage in get_result_functions
136
137 return get_result_functions[stage]()
138
139 def SetResult(self, stage, result):
140 """Set the performance results of the task in the stage.
141
142 This method is called by the pipeling_worker to set the results for
143 duplicated tasks.
144
145 Args:
146 stage: The stage (build/test) in which this method is called.
147 result: The performance results of the stage.
148 """
149
150 # Define the dictionary for different stage function lookup.
151 set_result_functions = {BUILD_STAGE: self.__SetBuildResult,
152 TEST_STAGE: self.__SetTestResult}
153
154 assert stage in set_result_functions
155
156 set_result_functions[stage](result)
157
158 def Done(self, stage):
159 """Check whether the stage is done.
160
161 Args:
162 stage: The stage to be checked, build or test.
163 Returns:
164 True if the stage is done.
165 """
166
167 # Define the dictionary for different result string lookup.
168 done_string = {BUILD_STAGE: self._build_cost, TEST_STAGE: self._exe_cost}
169
170 assert stage in done_string
171
172 return done_string[stage] is not None
173
174 def Work(self, stage):
175 """Perform the task.
176
177 Args:
178 stage: The stage in which the task is performed, compile or test.
179 """
180
181 # Define the dictionary for different stage function lookup.
182 work_functions = {BUILD_STAGE: self.__Compile(), TEST_STAGE: self.__Test()}
183
184 assert stage in work_functions
185
186 work_functions[stage]()
187
188 def GetFlags(self):
189 """Get the optimization flag set of this task.
190
191 Returns:
192 The optimization flag set that is encapsulated by this task.
193 """
194 return str(self._flag_set.FormattedForUse())
195
196 def __GetCheckSum(self):
197 """Get the compilation image checksum of this task.
198
199 Returns:
200 The compilation image checksum of this task.
201 """
202
203 # The checksum should be computed before this method is called.
204 assert self._checksum is not None
205 return self._checksum
206
207 def __Compile(self):
Yuheng Longf20cffa2013-06-03 18:46:00 -0700208 """Run a compile.
209
210 This method compile an image using the present flags, get the image,
211 test the existent of the image and gathers monitoring information, and sets
212 the internal cost (fitness) for this set of flags.
213 """
Yuheng Longf20cffa2013-06-03 18:46:00 -0700214
Yuheng Longb15d41c2013-07-25 10:02:36 -0700215 # Format the flags as a string as input to compile command. The unique
216 # identifier is passed to the compile command. If concurrent processes are
217 # used to compile different tasks, these processes can use the identifier to
218 # write to different file.
219 flags = self._flag_set.FormattedForUse()
220 command = '%s %s %s' % (self._build_command, ' '.join(flags),
221 self.task_identifier)
Yuheng Longf20cffa2013-06-03 18:46:00 -0700222
Yuheng Longb15d41c2013-07-25 10:02:36 -0700223 # Try build_tries number of times before confirming that the build fails.
224 for _ in range(self._build_tries):
225 # Execute the command and get the execution status/results.
226 p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
227 stderr=subprocess.PIPE)
228 (out, err) = p.communicate()
Yuheng Longf20cffa2013-06-03 18:46:00 -0700229
Yuheng Longb15d41c2013-07-25 10:02:36 -0700230 if not err and out != ERROR_STRING:
231 # Each build results contains the checksum of the result image, the
232 # performance cost of the build, the compilation image, the length of
233 # the build, and the length of the text section of the build.
234 (checksum, cost, image, file_length, text_length) = out.split()
235 # Build successfully.
236 break
Yuheng Longf20cffa2013-06-03 18:46:00 -0700237
Yuheng Longb15d41c2013-07-25 10:02:36 -0700238 # Build failed.
239 cost = ERROR_STRING
Yuheng Longf20cffa2013-06-03 18:46:00 -0700240
Yuheng Longb15d41c2013-07-25 10:02:36 -0700241 # Convert the build cost from String to integer. The build cost is used to
242 # compare a task with another task. Set the build cost of the failing task
243 # to the max integer.
244 self._build_cost = sys.maxint if cost == ERROR_STRING else int(cost)
Yuheng Longf20cffa2013-06-03 18:46:00 -0700245
Yuheng Longb15d41c2013-07-25 10:02:36 -0700246 self._checksum = checksum
247 self._file_length = file_length
248 self._text_length = text_length
249 self._image = image
250
251 self.__LogBuildCost()
252
253 def __Test(self):
254 """__Test the task against benchmark(s) using the input test command."""
255
256 # Ensure that the task is compiled before being tested.
257 assert self._image is not None
258
259 # If the task does not compile, no need to test.
260 if self._image == ERROR_STRING:
261 self._exe_cost = ERROR_STRING
262 return
263
264 # The unique identifier is passed to the test command. If concurrent
265 # processes are used to compile different tasks, these processes can use the
266 # identifier to write to different file.
267 command = '%s %s %s' % (self._test_command, self._image,
268 self.task_identifier)
269
270 # Try build_tries number of times before confirming that the build fails.
271 for _ in range(self._test_tries):
272 p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
273 stderr=subprocess.PIPE)
274 (out, err) = p.communicate()
275
276 if not err and out != ERROR_STRING:
277 # The test results contains the performance cost of the test.
278 cost = out
279 # Test successfully.
280 break
281
282 # Test failed.
283 cost = ERROR_STRING
284
285 self._exe_cost = sys.maxint if (cost == ERROR_STRING) else int(cost)
286
287 self.__LogTestCost()
288
289 def __SetBuildResult(self, (checksum, build_cost, image, file_length,
290 text_length)):
291 self._checksum = checksum
292 self._build_cost = build_cost
293 self._image = image
294 self._file_length = file_length
295 self._text_length = text_length
296
297 def __GetBuildResult(self):
298 return (self._checksum, self._build_cost, self._image, self._file_length,
299 self._text_length)
300
301 def __GetTestResult(self):
302 return self._exe_cost
303
304 def __SetTestResult(self, exe_cost):
305 self._exe_cost = exe_cost
306
307 def __CreateDirectory(self, file_name):
308 """Create a directory/file if it does not already exist.
309
310 Args:
311 file_name: The path of the directory/file to be created.
312 """
313
314 d = os.path.dirname(file_name)
315 if not os.path.exists(d):
316 os.makedirs(d)
317
318 def LogSteeringCost(self):
319 """Log the performance results for the task.
320
321 This method is called by the steering stage and this method writes the
322 results out to a file. The results include the build and the test results.
323 """
324
325 steering_log = '%s/%s/steering.txt' % self._log_path
326
327 self.create_dir('%s/%s/' % steering_log)
328
329 with open(steering_log, 'w') as out_file:
330 # Include the build and the test results.
331 steering_result = (self._flag_set, self._checksum, self._build_cost,
332 self._image, self._file_length, self._text_length,
333 self._exe_cost)
334
335 # Write out the result in the comma-separated format (CSV).
336 out_file.write('%s,%s,%s,%s,%s,%s,%s\n' % steering_result)
337
338 def __LogBuildCost(self):
339 """Log the build results for the task.
340
341 The build results include the compilation time of the build, the result
342 image, the checksum, the file length and the text length of the image.
343 The file length of the image includes the length of the file of the image.
344 The text length only includes the length of the text section of the image.
345 """
346
347 build_log = '%s/%s/build.txt' % self._log_path
348
349 self.__CreateDirectory(build_log)
350
351 with open(build_log, 'w') as out_file:
352 build_result = (self._flag_set, self._build_cost, self._image,
353 self._checksum, self._file_length, self._text_length)
354
355 # Write out the result in the comma-separated format (CSV).
356 out_file.write('%s,%s,%s,%s,%s,%s\n' % build_result)
357
358 def __LogTestCost(self):
359 """Log the test results for the task.
360
361 The test results include the runtime execution time of the test.
362 """
363
364 test_log = '%s/%s/build.txt' % self._log_path
365
366 self.__CreateDirectory(test_log)
367
368 with open(test_log, 'w') as out_file:
369 test_result = (self._flag_set, self._checksum, self._exe_cost)
370
371 # Write out the result in the comma-separated format (CSV).
372 out_file.write('%s,%s,%s\n' % test_result)