blob: f1ac417f2ead962e64f912e08251eae5793f3e5c [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 Longccfaf2f2013-08-02 14:27:45 -070029# The maximum number of tries a build can have. Some compilations may fail due
30# to unexpected environment circumstance. This variable defines how many tries
31# the build should attempt before giving up.
32BUILD_TRIES = 3
33
Yuheng Long2b514c22013-08-08 21:07:24 -070034# The maximum number of tries a test can have. Some tests may fail due to
Yuheng Longccfaf2f2013-08-02 14:27:45 -070035# unexpected environment circumstance. This variable defines how many tries the
36# test should attempt before giving up.
37TEST_TRIES = 3
38
39
40# Create the file/directory if it does not already exist.
41def _CreateDirectory(file_name):
42 directory = os.path.dirname(file_name)
43 if not os.path.exists(directory):
44 os.makedirs(directory)
45
Yuheng Longf20cffa2013-06-03 18:46:00 -070046
47class Task(object):
48 """A single reproducing entity.
49
50 A single test of performance with a particular set of flags. It records the
51 flag set, the image, the check sum of the image and the cost.
52 """
53
Yuheng Longccfaf2f2013-08-02 14:27:45 -070054 # The command that will be used in the build stage to compile the tasks.
55 BUILD_COMMAND = None
56 # The command that will be used in the test stage to test the tasks.
57 TEST_COMMAND = None
58 # The directory to log the compilation and test results.
59 LOG_DIRECTORY = None
60
61 @staticmethod
62 def InitLogCommand(build_command, test_command, log_directory):
63 """Set up the build and test command for the task and the log directory.
Yuheng Longf20cffa2013-06-03 18:46:00 -070064
Yuheng Longb15d41c2013-07-25 10:02:36 -070065 This framework is generic. It lets the client specify application specific
66 compile and test methods by passing different build_command and
67 test_command.
68
Yuheng Longf20cffa2013-06-03 18:46:00 -070069 Args:
Yuheng Longb15d41c2013-07-25 10:02:36 -070070 build_command: The command that will be used in the build stage to compile
71 this task.
72 test_command: The command that will be used in the test stage to test this
73 task.
74 log_directory: The directory to log the compilation and test results.
Yuheng Longccfaf2f2013-08-02 14:27:45 -070075 """
76
77 Task.BUILD_COMMAND = build_command
78 Task.TEST_COMMAND = test_command
79 Task.LOG_DIRECTORY = log_directory
80
81 def __init__(self, flag_set):
82 """Set up the optimization flag selection for this task.
83
84 Args:
85 flag_set: The optimization flag set that is encapsulated by this task.
Yuheng Longf20cffa2013-06-03 18:46:00 -070086 """
Yuheng Longb15d41c2013-07-25 10:02:36 -070087
Yuheng Longf20cffa2013-06-03 18:46:00 -070088 self._flag_set = flag_set
89
Yuheng Longb15d41c2013-07-25 10:02:36 -070090 # A unique identifier that distinguishes this task from other tasks.
Yuheng Longccfaf2f2013-08-02 14:27:45 -070091 self._task_identifier = uuid4()
Yuheng Longf20cffa2013-06-03 18:46:00 -070092
Yuheng Longccfaf2f2013-08-02 14:27:45 -070093 self._log_path = (Task.LOG_DIRECTORY, self._task_identifier)
Yuheng Longb15d41c2013-07-25 10:02:36 -070094
95 # Initiate the hash value. The hash value is used so as not to recompute it
96 # every time the hash method is called.
97 self._hash_value = None
98
99 # Indicate that the task has not been compiled/tested.
100 self._build_cost = None
101 self._exe_cost = None
102 self._checksum = None
103 self._image = None
104
105 def __eq__(self, other):
106 """Test whether two tasks are equal.
107
108 Two tasks are equal if their flag_set are equal.
Yuheng Longf20cffa2013-06-03 18:46:00 -0700109
110 Args:
Yuheng Longb15d41c2013-07-25 10:02:36 -0700111 other: The other task with which this task is tested equality.
112 Returns:
113 True if the encapsulated flag sets are equal.
Yuheng Longf20cffa2013-06-03 18:46:00 -0700114 """
Yuheng Longb15d41c2013-07-25 10:02:36 -0700115 if isinstance(other, Task):
116 return self.GetFlags() == other.GetFlags()
117 return False
Yuheng Longf20cffa2013-06-03 18:46:00 -0700118
Yuheng Longb15d41c2013-07-25 10:02:36 -0700119 def __hash__(self):
120 if self._hash_value is None:
121 # Cache the hash value of the flags, so as not to recompute them.
122 self._hash_value = hash(self._flag_set)
123 return self._hash_value
124
125 def GetIdentifier(self, stage):
126 """Get the identifier of the task in the stage.
127
128 The flag set uniquely identifies a task in the build stage. The checksum of
129 the image of the task uniquely identifies the task in the test stage.
130
131 Args:
132 stage: The stage (build/test) in which this method is called.
133 Returns:
134 Return the flag set in build stage and return the checksum in test stage.
135 """
136
137 # Define the dictionary for different stage function lookup.
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700138 get_identifier_functions = {BUILD_STAGE: self.FormattedFlags,
Yuheng Longb15d41c2013-07-25 10:02:36 -0700139 TEST_STAGE: self.__GetCheckSum}
140
141 assert stage in get_identifier_functions
142 return get_identifier_functions[stage]()
143
144 def GetResult(self, stage):
145 """Get the performance results of the task in the stage.
146
147 Args:
148 stage: The stage (build/test) in which this method is called.
149 Returns:
150 Performance results.
151 """
152
153 # Define the dictionary for different stage function lookup.
154 get_result_functions = {BUILD_STAGE: self.__GetBuildResult,
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700155 TEST_STAGE: self.GetTestResult}
Yuheng Longb15d41c2013-07-25 10:02:36 -0700156
157 assert stage in get_result_functions
158
159 return get_result_functions[stage]()
160
161 def SetResult(self, stage, result):
162 """Set the performance results of the task in the stage.
163
164 This method is called by the pipeling_worker to set the results for
165 duplicated tasks.
166
167 Args:
168 stage: The stage (build/test) in which this method is called.
169 result: The performance results of the stage.
170 """
171
172 # Define the dictionary for different stage function lookup.
173 set_result_functions = {BUILD_STAGE: self.__SetBuildResult,
174 TEST_STAGE: self.__SetTestResult}
175
176 assert stage in set_result_functions
177
178 set_result_functions[stage](result)
179
180 def Done(self, stage):
181 """Check whether the stage is done.
182
183 Args:
184 stage: The stage to be checked, build or test.
185 Returns:
186 True if the stage is done.
187 """
188
189 # Define the dictionary for different result string lookup.
190 done_string = {BUILD_STAGE: self._build_cost, TEST_STAGE: self._exe_cost}
191
192 assert stage in done_string
193
194 return done_string[stage] is not None
195
196 def Work(self, stage):
197 """Perform the task.
198
199 Args:
200 stage: The stage in which the task is performed, compile or test.
201 """
202
203 # Define the dictionary for different stage function lookup.
204 work_functions = {BUILD_STAGE: self.__Compile(), TEST_STAGE: self.__Test()}
205
206 assert stage in work_functions
207
208 work_functions[stage]()
209
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700210 def FormattedFlags(self):
211 """Format the optimization flag set of this task.
212
213 Returns:
214 The formatted optimization flag set that is encapsulated by this task.
215 """
216 return str(self._flag_set.FormattedForUse())
217
Yuheng Longb15d41c2013-07-25 10:02:36 -0700218 def GetFlags(self):
219 """Get the optimization flag set of this task.
220
221 Returns:
222 The optimization flag set that is encapsulated by this task.
223 """
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700224
225 return self._flag_set
Yuheng Longb15d41c2013-07-25 10:02:36 -0700226
227 def __GetCheckSum(self):
228 """Get the compilation image checksum of this task.
229
230 Returns:
231 The compilation image checksum of this task.
232 """
233
234 # The checksum should be computed before this method is called.
235 assert self._checksum is not None
236 return self._checksum
237
238 def __Compile(self):
Yuheng Longf20cffa2013-06-03 18:46:00 -0700239 """Run a compile.
240
241 This method compile an image using the present flags, get the image,
242 test the existent of the image and gathers monitoring information, and sets
243 the internal cost (fitness) for this set of flags.
244 """
Yuheng Longf20cffa2013-06-03 18:46:00 -0700245
Yuheng Longb15d41c2013-07-25 10:02:36 -0700246 # Format the flags as a string as input to compile command. The unique
247 # identifier is passed to the compile command. If concurrent processes are
248 # used to compile different tasks, these processes can use the identifier to
249 # write to different file.
250 flags = self._flag_set.FormattedForUse()
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700251 command = '%s %s %s' % (Task.BUILD_COMMAND, ' '.join(flags),
252 self._task_identifier)
Yuheng Longf20cffa2013-06-03 18:46:00 -0700253
Yuheng Longb15d41c2013-07-25 10:02:36 -0700254 # Try build_tries number of times before confirming that the build fails.
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700255 for _ in range(BUILD_TRIES):
Yuheng Longb15d41c2013-07-25 10:02:36 -0700256 # Execute the command and get the execution status/results.
257 p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
258 stderr=subprocess.PIPE)
259 (out, err) = p.communicate()
Yuheng Longf20cffa2013-06-03 18:46:00 -0700260
Yuheng Longb15d41c2013-07-25 10:02:36 -0700261 if not err and out != ERROR_STRING:
262 # Each build results contains the checksum of the result image, the
263 # performance cost of the build, the compilation image, the length of
264 # the build, and the length of the text section of the build.
265 (checksum, cost, image, file_length, text_length) = out.split()
266 # Build successfully.
267 break
Yuheng Longf20cffa2013-06-03 18:46:00 -0700268
Yuheng Longb15d41c2013-07-25 10:02:36 -0700269 # Build failed.
270 cost = ERROR_STRING
Yuheng Longf20cffa2013-06-03 18:46:00 -0700271
Yuheng Longb15d41c2013-07-25 10:02:36 -0700272 # Convert the build cost from String to integer. The build cost is used to
273 # compare a task with another task. Set the build cost of the failing task
274 # to the max integer.
275 self._build_cost = sys.maxint if cost == ERROR_STRING else int(cost)
Yuheng Longf20cffa2013-06-03 18:46:00 -0700276
Yuheng Longb15d41c2013-07-25 10:02:36 -0700277 self._checksum = checksum
278 self._file_length = file_length
279 self._text_length = text_length
280 self._image = image
281
282 self.__LogBuildCost()
283
284 def __Test(self):
285 """__Test the task against benchmark(s) using the input test command."""
286
287 # Ensure that the task is compiled before being tested.
288 assert self._image is not None
289
290 # If the task does not compile, no need to test.
291 if self._image == ERROR_STRING:
292 self._exe_cost = ERROR_STRING
293 return
294
295 # The unique identifier is passed to the test command. If concurrent
296 # processes are used to compile different tasks, these processes can use the
297 # identifier to write to different file.
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700298 command = '%s %s %s' % (Task.TEST_COMMAND, self._image,
299 self._task_identifier)
Yuheng Longb15d41c2013-07-25 10:02:36 -0700300
301 # Try build_tries number of times before confirming that the build fails.
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700302 for _ in range(TEST_TRIES):
Yuheng Longb15d41c2013-07-25 10:02:36 -0700303 p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
304 stderr=subprocess.PIPE)
305 (out, err) = p.communicate()
306
307 if not err and out != ERROR_STRING:
308 # The test results contains the performance cost of the test.
309 cost = out
310 # Test successfully.
311 break
312
313 # Test failed.
314 cost = ERROR_STRING
315
316 self._exe_cost = sys.maxint if (cost == ERROR_STRING) else int(cost)
317
318 self.__LogTestCost()
319
320 def __SetBuildResult(self, (checksum, build_cost, image, file_length,
321 text_length)):
322 self._checksum = checksum
323 self._build_cost = build_cost
324 self._image = image
325 self._file_length = file_length
326 self._text_length = text_length
327
328 def __GetBuildResult(self):
329 return (self._checksum, self._build_cost, self._image, self._file_length,
330 self._text_length)
331
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700332 def GetTestResult(self):
Yuheng Longb15d41c2013-07-25 10:02:36 -0700333 return self._exe_cost
334
335 def __SetTestResult(self, exe_cost):
336 self._exe_cost = exe_cost
337
Yuheng Longb15d41c2013-07-25 10:02:36 -0700338 def LogSteeringCost(self):
339 """Log the performance results for the task.
340
341 This method is called by the steering stage and this method writes the
342 results out to a file. The results include the build and the test results.
343 """
344
345 steering_log = '%s/%s/steering.txt' % self._log_path
346
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700347 _CreateDirectory(steering_log)
Yuheng Longb15d41c2013-07-25 10:02:36 -0700348
349 with open(steering_log, 'w') as out_file:
350 # Include the build and the test results.
351 steering_result = (self._flag_set, self._checksum, self._build_cost,
352 self._image, self._file_length, self._text_length,
353 self._exe_cost)
354
355 # Write out the result in the comma-separated format (CSV).
356 out_file.write('%s,%s,%s,%s,%s,%s,%s\n' % steering_result)
357
358 def __LogBuildCost(self):
359 """Log the build results for the task.
360
361 The build results include the compilation time of the build, the result
362 image, the checksum, the file length and the text length of the image.
363 The file length of the image includes the length of the file of the image.
364 The text length only includes the length of the text section of the image.
365 """
366
367 build_log = '%s/%s/build.txt' % self._log_path
368
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700369 _CreateDirectory(build_log)
Yuheng Longb15d41c2013-07-25 10:02:36 -0700370
371 with open(build_log, 'w') as out_file:
372 build_result = (self._flag_set, self._build_cost, self._image,
373 self._checksum, self._file_length, self._text_length)
374
375 # Write out the result in the comma-separated format (CSV).
376 out_file.write('%s,%s,%s,%s,%s,%s\n' % build_result)
377
378 def __LogTestCost(self):
379 """Log the test results for the task.
380
381 The test results include the runtime execution time of the test.
382 """
383
384 test_log = '%s/%s/build.txt' % self._log_path
385
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700386 _CreateDirectory(test_log)
Yuheng Longb15d41c2013-07-25 10:02:36 -0700387
388 with open(test_log, 'w') as out_file:
389 test_result = (self._flag_set, self._checksum, self._exe_cost)
390
391 # Write out the result in the comma-separated format (CSV).
392 out_file.write('%s,%s,%s\n' % test_result)
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700393
Yuheng Longc0123222013-08-11 12:24:32 -0700394 def IsImproved(self, other):
Yuheng Longccfaf2f2013-08-02 14:27:45 -0700395 """Compare the current task with another task.
396
397 Args:
398 other: The other task against which the current task is compared.
399
400 Returns:
401 True if this task has improvement upon the other task.
402 """
403
404 # The execution costs must have been initiated.
405 assert self._exe_cost is not None
406 assert other.GetTestResult() is not None
407
408 return self._exe_cost < other.GetTestResult()