aboutsummaryrefslogtreecommitdiff
path: root/bestflags
diff options
context:
space:
mode:
authorYuheng Long <yuhenglong@google.com>2013-07-25 10:02:36 -0700
committerChromeBot <chrome-bot@google.com>2013-07-30 17:08:54 -0700
commitb15d41c6d6324e343fbea19abaed3717417f3cde (patch)
treeafbdbad72db0734112a0860bc778de6192562924 /bestflags
parenta5712a2c71aa665dcca808963d152228890c8364 (diff)
downloadtoolchain-utils-b15d41c6d6324e343fbea19abaed3717417f3cde.tar.gz
Add the task module.
BUG=None TEST=unit testings for the pipeline stage, pipeline workers, generation, steering and task. Change-Id: I38987a12d7a48ec027d42465300a7226c760ea9d Reviewed-on: https://gerrit-int.chromium.org/41659 Reviewed-by: Simon Que <sque@google.com> Commit-Queue: Yuheng Long <yuhenglong@google.com> Tested-by: Yuheng Long <yuhenglong@google.com>
Diffstat (limited to 'bestflags')
-rw-r--r--bestflags/mock_task.py2
-rw-r--r--bestflags/steering.py3
-rw-r--r--bestflags/task.py346
-rw-r--r--bestflags/task_test.py173
4 files changed, 485 insertions, 39 deletions
diff --git a/bestflags/mock_task.py b/bestflags/mock_task.py
index d2c3726f..e1d91e29 100644
--- a/bestflags/mock_task.py
+++ b/bestflags/mock_task.py
@@ -67,7 +67,7 @@ class MockTask(object):
assert stage == self._stage
return '_cost' in self.__dict__
- def CacheSteeringCost(self):
+ def LogSteeringCost(self):
pass
diff --git a/bestflags/steering.py b/bestflags/steering.py
index 09c78387..2f889ca1 100644
--- a/bestflags/steering.py
+++ b/bestflags/steering.py
@@ -73,12 +73,13 @@ def Steering(cache, generations, input_queue, result_queue):
# If there is a task whose result is ready from the last stage of the
# feedback loop, there will be one less pending task.
+
task = input_queue.get()
# Store the result of this ready task. Intermediate results can be used to
# generate report for final result or be used to reboot from a crash from
# the failure of any module of the framework.
- task.CacheSteeringCost()
+ task.LogSteeringCost()
# Find out which pending generation this ready task belongs to. This pending
# generation will have one less pending task. The "next" expression iterates
diff --git a/bestflags/task.py b/bestflags/task.py
index b1319ca1..4391c41d 100644
--- a/bestflags/task.py
+++ b/bestflags/task.py
@@ -15,6 +15,17 @@ execution output to the execution field.
__author__ = 'yuhenglong@google.com (Yuheng Long)'
+import os
+import subprocess
+import sys
+from uuid import uuid4
+
+BUILD_STAGE = 1
+TEST_STAGE = 2
+
+# Message indicating that the build or test failed.
+ERROR_STRING = 'error'
+
class Task(object):
"""A single reproducing entity.
@@ -23,50 +34,339 @@ class Task(object):
flag set, the image, the check sum of the image and the cost.
"""
- def __init__(self, flag_set):
+ def __init__(self, flag_set, build_command, test_command, log_directory,
+ build_tries, test_tries):
"""Set up the optimization flag selection for this task.
+ This framework is generic. It lets the client specify application specific
+ compile and test methods by passing different build_command and
+ test_command.
+
Args:
- flag_set: the optimization flag set that is encapsulated by this task.
+ flag_set: The optimization flag set that is encapsulated by this task.
+ build_command: The command that will be used in the build stage to compile
+ this task.
+ test_command: The command that will be used in the test stage to test this
+ task.
+ log_directory: The directory to log the compilation and test results.
+ build_tries: The maximum number of tries a build can have. Some
+ compilations may fail due to unexpected environment circumstance. This
+ variable defines how many tries the build should attempt before giving
+ up.
+ test_tries: The maximum number of tries a build can have. Some tests may
+ fail due to unexpected environment circumstance. This variable defines
+ how many tries the test should attempt before giving up.
"""
+
self._flag_set = flag_set
+ self._build_command = build_command
+ self._test_command = test_command
+ self._log_directory = log_directory
+ self._build_tries = build_tries
+ self._test_tries = test_tries
+
+ # A unique identifier that distinguishes this task from other tasks.
+ self.task_identifier = uuid4()
+
+ self._log_path = (self._log_directory, self.task_identifier)
+
+ # Initiate the hash value. The hash value is used so as not to recompute it
+ # every time the hash method is called.
+ self._hash_value = None
+
+ # Indicate that the task has not been compiled/tested.
+ self._build_cost = None
+ self._exe_cost = None
+ self._checksum = None
+ self._image = None
+
+ def __eq__(self, other):
+ """Test whether two tasks are equal.
+
+ Two tasks are equal if their flag_set are equal.
+
+ Args:
+ other: The other task with which this task is tested equality.
+ Returns:
+ True if the encapsulated flag sets are equal.
+ """
+ if isinstance(other, Task):
+ return self.GetFlags() == other.GetFlags()
+ return False
+
+ def __hash__(self):
+ if self._hash_value is None:
+ # Cache the hash value of the flags, so as not to recompute them.
+ self._hash_value = hash(self._flag_set)
+ return self._hash_value
+
+ def GetIdentifier(self, stage):
+ """Get the identifier of the task in the stage.
+
+ The flag set uniquely identifies a task in the build stage. The checksum of
+ the image of the task uniquely identifies the task in the test stage.
+
+ Args:
+ stage: The stage (build/test) in which this method is called.
+ Returns:
+ Return the flag set in build stage and return the checksum in test stage.
+ """
+
+ # Define the dictionary for different stage function lookup.
+ get_identifier_functions = {BUILD_STAGE: self.GetFlags,
+ TEST_STAGE: self.__GetCheckSum}
- def ReproduceWith(self, other):
- """Create a new SolutionCandidate by reproduction with another.
+ assert stage in get_identifier_functions
+ return get_identifier_functions[stage]()
- Mix two Tasks together to form a new Task of the same class. This is one of
- the core functions of a GA.
+ def GetResult(self, stage):
+ """Get the performance results of the task in the stage.
Args:
- other: The other Task to reproduce with.
+ stage: The stage (build/test) in which this method is called.
+ Returns:
+ Performance results.
+ """
+
+ # Define the dictionary for different stage function lookup.
+ get_result_functions = {BUILD_STAGE: self.__GetBuildResult,
+ TEST_STAGE: self.__GetTestResult}
+
+ assert stage in get_result_functions
+
+ return get_result_functions[stage]()
+
+ def SetResult(self, stage, result):
+ """Set the performance results of the task in the stage.
+
+ This method is called by the pipeling_worker to set the results for
+ duplicated tasks.
+
+ Args:
+ stage: The stage (build/test) in which this method is called.
+ result: The performance results of the stage.
+ """
+
+ # Define the dictionary for different stage function lookup.
+ set_result_functions = {BUILD_STAGE: self.__SetBuildResult,
+ TEST_STAGE: self.__SetTestResult}
+
+ assert stage in set_result_functions
+
+ set_result_functions[stage](result)
+
+ def Done(self, stage):
+ """Check whether the stage is done.
+
+ Args:
+ stage: The stage to be checked, build or test.
+ Returns:
+ True if the stage is done.
+ """
+
+ # Define the dictionary for different result string lookup.
+ done_string = {BUILD_STAGE: self._build_cost, TEST_STAGE: self._exe_cost}
+
+ assert stage in done_string
+
+ return done_string[stage] is not None
+
+ def Work(self, stage):
+ """Perform the task.
+
+ Args:
+ stage: The stage in which the task is performed, compile or test.
+ """
+
+ # Define the dictionary for different stage function lookup.
+ work_functions = {BUILD_STAGE: self.__Compile(), TEST_STAGE: self.__Test()}
+
+ assert stage in work_functions
+
+ work_functions[stage]()
- Returns: A Task that is a mix between self and other.
+ def GetFlags(self):
+ """Get the optimization flag set of this task.
+
+ Returns:
+ The optimization flag set that is encapsulated by this task.
+ """
+ return str(self._flag_set.FormattedForUse())
+
+ def __GetCheckSum(self):
+ """Get the compilation image checksum of this task.
+
+ Returns:
+ The compilation image checksum of this task.
"""
- pass
- def Compile(self):
+ # The checksum should be computed before this method is called.
+ assert self._checksum is not None
+ return self._checksum
+
+ def __Compile(self):
"""Run a compile.
This method compile an image using the present flags, get the image,
test the existent of the image and gathers monitoring information, and sets
the internal cost (fitness) for this set of flags.
"""
- pass
- def GetFlags(self):
- pass
+ # Format the flags as a string as input to compile command. The unique
+ # identifier is passed to the compile command. If concurrent processes are
+ # used to compile different tasks, these processes can use the identifier to
+ # write to different file.
+ flags = self._flag_set.FormattedForUse()
+ command = '%s %s %s' % (self._build_command, ' '.join(flags),
+ self.task_identifier)
+
+ # Try build_tries number of times before confirming that the build fails.
+ for _ in range(self._build_tries):
+ # Execute the command and get the execution status/results.
+ p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (out, err) = p.communicate()
+
+ if not err and out != ERROR_STRING:
+ # Each build results contains the checksum of the result image, the
+ # performance cost of the build, the compilation image, the length of
+ # the build, and the length of the text section of the build.
+ (checksum, cost, image, file_length, text_length) = out.split()
+ # Build successfully.
+ break
+
+ # Build failed.
+ cost = ERROR_STRING
+
+ # Convert the build cost from String to integer. The build cost is used to
+ # compare a task with another task. Set the build cost of the failing task
+ # to the max integer.
+ self._build_cost = sys.maxint if cost == ERROR_STRING else int(cost)
+
+ self._checksum = checksum
+ self._file_length = file_length
+ self._text_length = text_length
+ self._image = image
+
+ self.__LogBuildCost()
+
+ def __Test(self):
+ """__Test the task against benchmark(s) using the input test command."""
+
+ # Ensure that the task is compiled before being tested.
+ assert self._image is not None
+
+ # If the task does not compile, no need to test.
+ if self._image == ERROR_STRING:
+ self._exe_cost = ERROR_STRING
+ return
+
+ # The unique identifier is passed to the test command. If concurrent
+ # processes are used to compile different tasks, these processes can use the
+ # identifier to write to different file.
+ command = '%s %s %s' % (self._test_command, self._image,
+ self.task_identifier)
+
+ # Try build_tries number of times before confirming that the build fails.
+ for _ in range(self._test_tries):
+ p = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (out, err) = p.communicate()
+
+ if not err and out != ERROR_STRING:
+ # The test results contains the performance cost of the test.
+ cost = out
+ # Test successfully.
+ break
+
+ # Test failed.
+ cost = ERROR_STRING
+
+ self._exe_cost = sys.maxint if (cost == ERROR_STRING) else int(cost)
+
+ self.__LogTestCost()
+
+ def __SetBuildResult(self, (checksum, build_cost, image, file_length,
+ text_length)):
+ self._checksum = checksum
+ self._build_cost = build_cost
+ self._image = image
+ self._file_length = file_length
+ self._text_length = text_length
+
+ def __GetBuildResult(self):
+ return (self._checksum, self._build_cost, self._image, self._file_length,
+ self._text_length)
- def SetFlags(self, flags):
- pass
+ def __GetTestResult(self):
+ return self._exe_cost
+
+ def __SetTestResult(self, exe_cost):
+ self._exe_cost = exe_cost
+
+ def __CreateDirectory(self, file_name):
+ """Create a directory/file if it does not already exist.
+
+ Args:
+ file_name: The path of the directory/file to be created.
+ """
+
+ d = os.path.dirname(file_name)
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ def LogSteeringCost(self):
+ """Log the performance results for the task.
+
+ This method is called by the steering stage and this method writes the
+ results out to a file. The results include the build and the test results.
+ """
+
+ steering_log = '%s/%s/steering.txt' % self._log_path
+
+ self.create_dir('%s/%s/' % steering_log)
+
+ with open(steering_log, 'w') as out_file:
+ # Include the build and the test results.
+ steering_result = (self._flag_set, self._checksum, self._build_cost,
+ self._image, self._file_length, self._text_length,
+ self._exe_cost)
+
+ # Write out the result in the comma-separated format (CSV).
+ out_file.write('%s,%s,%s,%s,%s,%s,%s\n' % steering_result)
+
+ def __LogBuildCost(self):
+ """Log the build results for the task.
+
+ The build results include the compilation time of the build, the result
+ image, the checksum, the file length and the text length of the image.
+ The file length of the image includes the length of the file of the image.
+ The text length only includes the length of the text section of the image.
+ """
+
+ build_log = '%s/%s/build.txt' % self._log_path
+
+ self.__CreateDirectory(build_log)
+
+ with open(build_log, 'w') as out_file:
+ build_result = (self._flag_set, self._build_cost, self._image,
+ self._checksum, self._file_length, self._text_length)
+
+ # Write out the result in the comma-separated format (CSV).
+ out_file.write('%s,%s,%s,%s,%s,%s\n' % build_result)
+
+ def __LogTestCost(self):
+ """Log the test results for the task.
+
+ The test results include the runtime execution time of the test.
+ """
- def GetChecksum(self):
- pass
+ test_log = '%s/%s/build.txt' % self._log_path
- def SetChecksum(self, checksum):
- pass
+ self.__CreateDirectory(test_log)
- def GetImage(self):
- pass
+ with open(test_log, 'w') as out_file:
+ test_result = (self._flag_set, self._checksum, self._exe_cost)
- def SetImage(self, image):
- pass
+ # Write out the result in the comma-separated format (CSV).
+ out_file.write('%s,%s,%s\n' % test_result)
diff --git a/bestflags/task_test.py b/bestflags/task_test.py
index ec3d5b6e..3777a097 100644
--- a/bestflags/task_test.py
+++ b/bestflags/task_test.py
@@ -9,31 +9,176 @@ Part of the Chrome build flags optimization.
__author__ = 'yuhenglong@google.com (Yuheng Long)'
+import random
+import sys
import unittest
import task
+from task import Task
+# The number of flags be tested.
+NUM_FLAGS = 20
+
+# The random build result values used to test get set result method.
+RANDOM_BUILD_RESULT = 100
+
+# The random test result values used to test get set result method.
+RANDOM_TESTRESULT = 100
+
+
+# Create task for unittestings that only uses the flag set field of the task.
+def _IdentifierTask(identifier):
+ return Task(identifier, None, None, None, None, None)
-class TaskTest(unittest.TestCase):
- """This class test the Task class.
- The getter and setter should function properly.
+class MockFlagSet(object):
+ """This class emulates a set of flags.
+
+ It returns the flags and hash value, when the FormattedForUse method and the
+ __hash__ method is called, respectively. These values are initialized when the
+ MockFlagSet instance is constructed.
"""
- def setUp(self):
- pass
+ def __init__(self, flags=0, hash_value=-1):
+ self._flags = flags
+ self._hash_value = hash_value
+
+ def __eq__(self, other):
+ assert isinstance(other, MockFlagSet)
+ return self._flags == other.FormattedForUse()
+
+ def FormattedForUse(self):
+ return self._flags
+
+ def __hash__(self):
+ return self._hash_value
+
+ def GetHash(self):
+ return self._hash_value
+
+
+class TaskTest(unittest.TestCase):
+ """This class test the Task class."""
+
+ def testEqual(self):
+ """Test the equal method of the task.
+
+ Two tasks are equal if and only if their encapsulated flag_sets are equal.
+ """
+
+ flags = range(NUM_FLAGS)
+
+ # Two tasks having the same flag set should be equivalent.
+ flag_sets = [MockFlagSet(flag) for flag in flags]
+ for flag_set in flag_sets:
+ task0 = _IdentifierTask(flag_set)
+ task1 = _IdentifierTask(flag_set)
+
+ assert task0 == task1
+
+ # Two tasks having different flag set should be different.
+ for flag_set in flag_sets:
+ task0 = _IdentifierTask(flag_set)
+ other_flag_sets = [flags for flags in flag_sets if flags != flag_set]
+ for flag_set1 in other_flag_sets:
+ task1 = _IdentifierTask(flag_set1)
+ assert task0 != task1
+
+ def testHash(self):
+ """Test the hash method of the task.
+
+ Two tasks are equal if and only if their encapsulated flag_sets are equal.
+ """
+
+ # Random identifier that is not relevant in this test.
+ identifier = random.randint(-sys.maxint - 1, -1)
+
+ flag_sets = [MockFlagSet(identifier, value) for value in range(NUM_FLAGS)]
+ for flag_set in flag_sets:
+ # The hash of a task is the same as the hash of its flag set.
+ hash_task = _IdentifierTask(flag_set)
+ h0 = hash(hash_task)
+ assert h0 == flag_set.GetHash()
+
+ # The hash of a task does not change.
+ h1 = hash(hash_task)
+ assert h0 == h1
+
+ def testGetIdentifier(self):
+ """Test the get identifier method of the task.
+
+ The get identifier method should returns the flag set in the build stage.
+ """
+
+ flag_sets = [MockFlagSet(flag) for flag in range(NUM_FLAGS)]
+ for flag_set in flag_sets:
+ identifier_task = _IdentifierTask(flag_set)
+
+ identifier = identifier_task.GetIdentifier(task.BUILD_STAGE)
+
+ # The task formats the flag set into a string.
+ assert identifier == str(flag_set.FormattedForUse())
+
+ def testGetSetResult(self):
+ """Test the get and set result methods of the task.
+
+ The get result method should return the same results as were set.
+ """
+
+ flag_sets = [MockFlagSet(flag) for flag in range(NUM_FLAGS)]
+ for flag_set in flag_sets:
+ result_task = _IdentifierTask(flag_set)
+
+ # The get result method should return the same results as were set, in
+ # build stage. Currently, the build result is a 5-element tuple containing
+ # the checksum of the result image, the performance cost of the build, the
+ # compilation image, the length of the build, and the length of the text
+ # section of the build.
+ result = tuple([random.randint(0, RANDOM_BUILD_RESULT) for _ in range(5)])
+ result_task.SetResult(task.BUILD_STAGE, result)
+ assert result == result_task.GetResult(task.BUILD_STAGE)
+
+ # The checksum is the identifier of the test stage.
+ identifier = result_task.GetIdentifier(task.TEST_STAGE)
+ # The first element of the result tuple is the checksum.
+ assert identifier == result[0]
+
+ # The get result method should return the same results as were set, in
+ # test stage.
+ random_test_result = random.randint(0, RANDOM_TESTRESULT)
+ result_task.SetResult(task.TEST_STAGE, random_test_result)
+ test_result = result_task.GetResult(task.TEST_STAGE)
+ assert test_result == random_test_result
+
+ def testDone(self):
+ """Test the done methods of the task.
+
+ The done method should return false is the task has not perform and return
+ true after the task is finished.
+ """
+
+ flags = range(NUM_FLAGS)
+
+ flag_sets = [MockFlagSet(flag) for flag in flags]
+ for flag_set in flag_sets:
+ work_task = _IdentifierTask(flag_set)
- def testFlag(self):
- """"Test proper access of the flags."""
- pass
+ # The task has not been compiled nor tested.
+ assert not work_task.Done(task.TEST_STAGE)
+ assert not work_task.Done(task.BUILD_STAGE)
- def testChecksum(self):
- """"Test proper access of the check sum."""
- pass
+ # After the task has been compiled, it should indicate finished in BUILD
+ # stage.
+ result = tuple([random.randint(0, RANDOM_BUILD_RESULT) for _ in range(5)])
+ work_task.SetResult(task.BUILD_STAGE, result)
+ assert not work_task.Done(task.TEST_STAGE)
+ assert work_task.Done(task.BUILD_STAGE)
- def testImage(self):
- """"Test proper access of the image."""
- pass
+ # After the task has been tested, it should indicate finished in TEST
+ # stage.
+ work_task.SetResult(task.TEST_STAGE, random.randint(0, RANDOM_TESTRESULT))
+ assert work_task.Done(task.TEST_STAGE)
+ assert work_task.Done(task.BUILD_STAGE)
if __name__ == '__main__':
unittest.main()