Cloned from CL 57424 by 'g4 patch'.
Original change by raymes@raymes-crosstool-git5-11blah-git5 on 2011/12/14 11:43:10.
-Added the failure reason of jobs to the text report.
-Added some logging about cache hit/misses.
-Allowed checksums to be computed simultaneously
-Renamed action_runner to experiment_runner
-Moved storing results logic to experiment_runner
-Improvements to logging
Added the following fixes:
-Don't store run results when Ctrl-C is pressed in the middle of a run.
-Refactor caching code a bit to make it clearer.
-Fixed exception thrown when the cache was empty.
-Changed profile_type to be "" when it is invalid instead of "none".
-Fixed exception when using cache if no profile is present in the cache
file.
-Made ExecuteCommandInChroot() thread- and process-safe.
Added a DIFFBASE= for convenience.
PRESUBMIT=passed
R=raymes,bjanakiraman,shenhan
DELTA=412 (163 added, 183 deleted, 66 changed)
OCL=57454-p2
RCL=57583-p2
RDATE=2011/12/27 14:17:33
DIFFBASE=57424-p2
P4 change: 42662702
diff --git a/v14/crosperf/action_runner.py b/v14/crosperf/action_runner.py
deleted file mode 100644
index c77ebca..0000000
--- a/v14/crosperf/action_runner.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/python
-
-# Copyright 2011 Google Inc. All Rights Reserved.
-
-from experiment_status import ExperimentStatus
-from results_report import HTMLResultsReport
-from results_report import TextResultsReport
-from utils import logger
-from utils.email_sender import EmailSender
-
-
-class ActionRunner(object):
- def __init__(self, experiment):
- self._experiment = experiment
- self.l = logger.GetLogger()
-
- def Run(self, experiment):
- status = ExperimentStatus(experiment)
- experiment.start()
- try:
- while not experiment.complete:
- border = "=============================="
- self.l.LogOutput(border)
- self.l.LogOutput(status.GetProgressString())
- self.l.LogOutput(status.GetStatusString())
- logger.GetLogger().LogOutput(border)
- experiment.join(30)
- except KeyboardInterrupt:
- self.l.LogError("Ctrl-c pressed. Cleaning up...")
- experiment.terminate = True
-
- def PrintTable(self, experiment):
- if experiment.complete:
- self.l.LogOutput(TextResultsReport(experiment).GetReport())
-
- def Email(self, experiment):
- # Only email by default if a new run was completed.
- send_mail = False
- for benchmark_run in experiment.benchmark_runs:
- if not benchmark_run.cache_hit:
- send_mail = True
- break
- if not send_mail:
- return
-
- if experiment.complete:
- label_names = []
- for label in experiment.labels:
- label_names.append(label.name)
- subject = "%s: %s" % (experiment.name, " vs. ".join(label_names))
-
- text_report = TextResultsReport(experiment).GetReport()
- text_report = "<pre style='font-size: 13px'>%s</pre>" % text_report
- html_report = HTMLResultsReport(experiment).GetReport()
- attachment = EmailSender.Attachment("report.html", html_report)
- EmailSender().SendEmailToUser(subject,
- text_report,
- attachments=[attachment],
- msg_type="html")
-
- def StoreResults (self, experiment):
- experiment.StoreResults()
-
- def RunActions(self):
- self.Run(self._experiment)
- self.PrintTable(self._experiment)
- self.Email(self._experiment)
- self.StoreResults(self._experiment)
-
-
-class MockActionRunner(ActionRunner):
- def __init__(self, experiment):
- super(MockActionRunner, self).__init__(experiment)
-
- def Run(self, experiment):
- self.l.LogOutput("Would run the following experiment: '%s'." %
- experiment.name)
-
- def PrintTable(self, experiment):
- self.l.LogOutput("Would print the experiment table.")
-
- def Email(self, experiment):
- self.l.LogOutput("Would send result email.")
-
- def StoreResults(self, experiment):
- self.l.LogOutput("Would store the results.")
diff --git a/v14/crosperf/autotest_runner.py b/v14/crosperf/autotest_runner.py
index 2e9b25d..8958663 100644
--- a/v14/crosperf/autotest_runner.py
+++ b/v14/crosperf/autotest_runner.py
@@ -9,10 +9,11 @@
class AutotestRunner(object):
def __init__(self):
self._ce = command_executer.GetCommandExecuter()
+ self._ct = command_executer.CommandTerminator()
def Run(self, machine_name, chromeos_root, board, autotest_name,
autotest_args, profile_counters, profile_type):
- if profile_counters and profile_type != "none":
+ if profile_counters and profile_type:
profiler_args = "-e " + " -e ".join(profile_counters)
if profile_type == "record":
profiler_args += "-g"
@@ -25,7 +26,10 @@
options += " %s" % autotest_args
command = ("./run_remote_tests.sh --remote=%s %s %s" %
(machine_name, options, autotest_name))
- return utils.ExecuteCommandInChroot(chromeos_root, command, True)
+ return utils.ExecuteCommandInChroot(chromeos_root, command, True, self._ct)
+
+ def Terminate(self):
+ self._ct.Terminate()
class MockAutotestRunner(object):
diff --git a/v14/crosperf/benchmark_run.py b/v14/crosperf/benchmark_run.py
index 237944c..3d20d2e 100644
--- a/v14/crosperf/benchmark_run.py
+++ b/v14/crosperf/benchmark_run.py
@@ -10,7 +10,6 @@
import traceback
from results_cache import Result
from utils import logger
-from utils.file_utils import FileUtils
STATUS_FAILED = "FAILED"
STATUS_SUCCEEDED = "SUCCEEDED"
@@ -39,7 +38,7 @@
self.board = board
self.iteration = iteration
self.results = {}
- self.terminate = False
+ self.terminated = False
self.retval = None
self.status = STATUS_PENDING
self.run_completed = False
@@ -56,6 +55,7 @@
self.runs_complete = 0
self.cache_hit = False
self.perf_results = None
+ self.failure_reason = ""
def MeanExcludingOutliers(self, array, outlier_range):
"""Return the arithmetic mean excluding outliers."""
@@ -97,17 +97,19 @@
# Store the autotest output in the cache also.
if not cache_hit:
+ self.cache.StoreResult(result)
self.cache.StoreAutotestOutput(results_dir)
# Generate a perf report and cache it.
- if cache_hit:
- self.perf_results = self.cache.ReadPerfResults()
- else:
- self.perf_results = (self.perf_processor.
- GeneratePerfResults(results_dir,
- self.chromeos_root,
- self.board))
- self.cache.StorePerfResults(self.perf_results)
+ if self.profile_type:
+ if cache_hit:
+ self.perf_results = self.cache.ReadPerfResults()
+ else:
+ self.perf_results = (self.perf_processor.
+ GeneratePerfResults(results_dir,
+ self.chromeos_root,
+ self.board))
+ self.cache.StorePerfResults(self.perf_results)
# If there are valid results from perf stat, combine them with the
# autotest results.
@@ -115,21 +117,6 @@
stat_results = self.perf_processor.ParseStatResults(self.perf_results)
self.results = dict(self.results.items() + stat_results.items())
- def StoreResults(self, results_dir):
- # Store perf_report and autotest output locally.
- try:
- self.cache.ReadAutotestOutput(results_dir)
- except Exception, e:
- self._logger.LogError(e)
- try:
- if self.perf_results:
- FileUtils().WriteFile(os.path.join(results_dir, "perf.report"),
- self.perf_results.report)
- FileUtils().WriteFile(os.path.join(results_dir, "perf.out"),
- self.perf_results.output)
- except Exception, e:
- self._logger.LogError(e)
-
def _GetResultsDir(self, output):
mo = re.search("Results placed in (\S+)", output)
if mo:
@@ -155,34 +142,50 @@
self.cache_hit = (result is not None)
if result:
+ self._logger.LogOutput("%s: Cache hit." % self.name)
self._logger.LogOutput(result.out + "\n" + result.err)
else:
+ self._logger.LogOutput("%s: No cache hit." % self.name)
self.status = STATUS_WAITING
# Try to acquire a machine now.
self.machine = self.AcquireMachine()
self.cache.remote = self.machine.name
result = self.RunTest(self.machine)
+ if self.terminated:
+ return
+
if not result.retval:
self.status = STATUS_SUCCEEDED
else:
- self.status = STATUS_FAILED
+ if self.status != STATUS_FAILED:
+ self.status = STATUS_FAILED
+ self.failure_reason = "Return value of autotest was non-zero."
self.ProcessResults(result, self.cache_hit)
except Exception, e:
- self._logger.LogError("Benchmark run: '%s' failed: %s." % (self.name, e))
+ self._logger.LogError("Benchmark run: '%s' failed: %s" % (self.name, e))
traceback.print_exc()
- self.status = STATUS_FAILED
+ if self.status != STATUS_FAILED:
+ self.status = STATUS_FAILED
+ self.failure_reason = str(e)
finally:
if self.machine:
self._logger.LogOutput("Releasing machine: %s" % self.machine.name)
self.machine_manager.ReleaseMachine(self.machine)
self._logger.LogOutput("Released machine: %s" % self.machine.name)
+ def Terminate(self):
+ self.terminated = True
+ self.autotest_runner.Terminate()
+ if self.status != STATUS_FAILED:
+ self.status = STATUS_FAILED
+ self.failure_reason = "Thread terminated."
+
def AcquireMachine(self):
while True:
- if self.terminate:
+ if self.terminated:
raise Exception("Thread terminated while trying to acquire machine.")
machine = self.machine_manager.AcquireMachine(self.chromeos_image)
if machine:
@@ -212,7 +215,6 @@
self.run_completed = True
result = Result(out, err, retval)
- self.cache.StoreResult(result)
return result
def SetCacheConditions(self, cache_conditions):
diff --git a/v14/crosperf/column_chart.py b/v14/crosperf/column_chart.py
index f86ab1e..22a45c5 100644
--- a/v14/crosperf/column_chart.py
+++ b/v14/crosperf/column_chart.py
@@ -54,4 +54,4 @@
return res
def GetDiv(self):
- return "<div id='%s'></div>" % self.chart_div
+ return "<div id='%s' class='chart'></div>" % self.chart_div
diff --git a/v14/crosperf/crosperf.py b/v14/crosperf/crosperf.py
index 384d960..e00ae75 100755
--- a/v14/crosperf/crosperf.py
+++ b/v14/crosperf/crosperf.py
@@ -8,8 +8,8 @@
import optparse
import os
import sys
-from action_runner import ActionRunner
-from action_runner import MockActionRunner
+from experiment_runner import ExperimentRunner
+from experiment_runner import MockExperimentRunner
from experiment_factory import ExperimentFactory
from experiment_file import ExperimentFile
from help import Help
@@ -85,10 +85,10 @@
atexit.register(Cleanup, experiment)
if options.dry_run:
- runner = MockActionRunner(experiment)
+ runner = MockExperimentRunner(experiment)
else:
- runner = ActionRunner(experiment)
- runner.RunActions()
+ runner = ExperimentRunner(experiment)
+ runner.Run()
if __name__ == "__main__":
Main(sys.argv)
diff --git a/v14/crosperf/experiment.py b/v14/crosperf/experiment.py
index 6c24dcc..882af71 100644
--- a/v14/crosperf/experiment.py
+++ b/v14/crosperf/experiment.py
@@ -3,7 +3,6 @@
# Copyright 2011 Google Inc. All Rights Reserved.
import os
-import threading
import time
from autotest_runner import AutotestRunner
from benchmark_run import BenchmarkRun
@@ -15,21 +14,18 @@
from utils.file_utils import FileUtils
-class Experiment(threading.Thread):
+class Experiment(object):
"""Class representing an Experiment to be run."""
def __init__(self, name, remote, rerun_if_failed, working_directory,
chromeos_root, cache_conditions, labels, benchmarks,
experiment_file):
- threading.Thread.__init__(self)
self.name = name
self.rerun_if_failed = rerun_if_failed
self.working_directory = working_directory
self.remote = remote
self.chromeos_root = chromeos_root
self.cache_conditions = cache_conditions
- self.complete = False
- self.terminate = False
self.experiment_file = experiment_file
self.results_directory = os.path.join(self.working_directory,
self.name + "_results")
@@ -94,60 +90,27 @@
for t in self.benchmark_runs:
if t.isAlive():
self.l.LogError("Terminating run: '%s'." % t.name)
- t.terminate = True
+ t.Terminate()
- def RunAutotestRunsInParallel(self):
- active_threads = []
+ def IsComplete(self):
+ if self.active_threads:
+ for t in self.active_threads:
+ if t.isAlive():
+ t.join(0)
+ if not t.isAlive():
+ self.num_complete += 1
+ self.active_threads.remove(t)
+ return False
+ return True
+
+ def Run(self):
+ self.start_time = time.time()
+ self.active_threads = []
for benchmark_run in self.benchmark_runs:
# Set threads to daemon so program exits when ctrl-c is pressed.
benchmark_run.daemon = True
benchmark_run.start()
- active_threads.append(benchmark_run)
-
- try:
- while active_threads:
- if self.terminate:
- self.Terminate()
- return
-
- for t in active_threads:
- if t.isAlive():
- t.join(1)
- if not t.isAlive():
- self.num_complete += 1
- active_threads.remove(t)
- except KeyboardInterrupt:
- self.Terminate()
- return
- finally:
- self.complete = True
-
- self.l.LogOutput("Benchmark runs complete. Final status:")
- for benchmark_run in self.benchmark_runs:
- self.l.LogOutput("'%s'\t\t%s" % (benchmark_run.name,
- benchmark_run.status))
-
- def run(self):
- self.start_time = time.time()
- self.RunAutotestRunsInParallel()
-
- def StoreResults(self):
- FileUtils().RmDir(self.results_directory)
- FileUtils().MkDirP(self.results_directory)
- experiment_file_path = os.path.join(self.results_directory,
- "experiment.exp")
- FileUtils().WriteFile(experiment_file_path, self.experiment_file)
-
- results_table_path = os.path.join(self.results_directory, "results.html")
- report = HTMLResultsReport(self).GetReport()
- FileUtils().WriteFile(results_table_path, report)
-
- for benchmark_run in self.benchmark_runs:
- benchmark_run_name = filter(str.isalnum, benchmark_run.name)
- benchmark_run_path = os.path.join(self.results_directory,
- benchmark_run_name)
- FileUtils().MkDirP(benchmark_run_path)
- benchmark_run.StoreResults(benchmark_run_path)
+ self.active_threads.append(benchmark_run)
def SetCacheConditions(self, cache_conditions):
for benchmark_run in self.benchmark_runs:
diff --git a/v14/crosperf/field.py b/v14/crosperf/field.py
index e433324..b3cdaa2 100644
--- a/v14/crosperf/field.py
+++ b/v14/crosperf/field.py
@@ -53,7 +53,12 @@
description)
def _Parse(self, value):
- return bool(value)
+ if value.lower() == "true":
+ return True
+ elif value.lower() == "false":
+ return False
+ raise Exception("Invalid value for '%s'. Must be true or false." %
+ self.name)
class IntegerField(Field):
diff --git a/v14/crosperf/image_checksummer.py b/v14/crosperf/image_checksummer.py
index 38a4d7e..f75dc94 100644
--- a/v14/crosperf/image_checksummer.py
+++ b/v14/crosperf/image_checksummer.py
@@ -8,9 +8,24 @@
class ImageChecksummer(object):
+ class PerImageChecksummer(object):
+ def __init__(self, filename):
+ self._lock = threading.Lock()
+ self.filename = filename
+ self._checksum = None
+
+ def Checksum(self):
+ with self._lock:
+ if not self._checksum:
+ logger.GetLogger().LogOutput("Computing checksum for '%s'." %
+ self.filename)
+ self._checksum = FileUtils().Md5File(self.filename)
+ logger.GetLogger().LogOutput("Checksum is: %s" % self._checksum)
+ return self._checksum
+
_instance = None
_lock = threading.Lock()
- _checksums = {}
+ _per_image_checksummers = {}
def __new__(cls, *args, **kwargs):
with cls._lock:
@@ -21,15 +36,14 @@
def Checksum(self, filename):
with self._lock:
- if filename in self._checksums:
- return self._checksums[filename]
- try:
- logger.GetLogger().LogOutput("Computing checksum for '%s'." % filename)
- checksum = FileUtils().Md5File(filename)
- self._checksums[filename] = checksum
- return checksum
+ if filename not in self._per_image_checksummers:
+ self._per_image_checksummers[filename] = (ImageChecksummer.
+ PerImageChecksummer(filename))
+ checksummer = self._per_image_checksummers[filename]
- except Exception, e:
- logger.GetLogger().LogError("Could not compute checksum of file '%s'."
- % filename)
- raise e
+ try:
+ return checksummer.Checksum()
+ except Exception, e:
+ logger.GetLogger().LogError("Could not compute checksum of file '%s'."
+ % filename)
+ raise e
diff --git a/v14/crosperf/machine_manager.py b/v14/crosperf/machine_manager.py
index a267a0b..5beaf3b 100644
--- a/v14/crosperf/machine_manager.py
+++ b/v14/crosperf/machine_manager.py
@@ -102,6 +102,9 @@
for m in self._all_machines:
self._TryToLockMachine(m)
self.initialized = True
+ for m in self._all_machines:
+ m.released_time = time.time()
+
if not self._machines:
machine_names = []
for machine in self._all_machines:
@@ -123,6 +126,12 @@
m.locked = True
m.autotest_run = threading.current_thread()
return m
+ # This logic ensures that threads waiting on a machine will get a machine
+ # with a checksum equal to their image over other threads. This saves time
+ # when crosperf initially assigns the machines to threads by minimizing
+ # the number of re-images.
+ # TODO(asharif): If we centralize the thread-scheduler, we wont need this
+ # code and can implement minimal reimaging code more cleanly.
for m in [machine for machine in self._machines if not machine.locked]:
if time.time() - m.released_time > 20:
m.locked = True
@@ -208,4 +217,3 @@
def GetMachines(self):
return self.machines
-
diff --git a/v14/crosperf/results_cache.py b/v14/crosperf/results_cache.py
index 8868ae2..f4a41c6 100644
--- a/v14/crosperf/results_cache.py
+++ b/v14/crosperf/results_cache.py
@@ -62,7 +62,29 @@
self._logger = logger.GetLogger()
self._ce = command_executer.GetCommandExecuter(self._logger)
- def _GetCacheDir(self, read=False):
+ def _GetCacheDirForRead(self):
+ glob_path = self._FormCacheDir(self._GetCacheKeyList(True))
+ matching_dirs = glob.glob(glob_path)
+
+ if matching_dirs:
+ # Cache file found.
+ if len(matching_dirs) > 1:
+ self._logger.LogError("Multiple compatible cache files: %s." %
+ " ".join(matching_dirs))
+ return matching_dirs[0]
+ else:
+ return None
+
+ def _GetCacheDirForWrite(self):
+ return self._FormCacheDir(self._GetCacheKeyList(False))
+
+ def _FormCacheDir(self, list_of_strings):
+ cache_key = " ".join(list_of_strings)
+ cache_dir = self._ConvertToFilename(cache_key)
+ cache_path = os.path.join(SCRATCH_DIR, cache_dir)
+ return cache_path
+
+ def _GetCacheKeyList(self, read):
if read and CacheConditions.REMOTES_MATCH not in self.cache_conditions:
remote = "*"
else:
@@ -71,27 +93,21 @@
checksum = "*"
else:
checksum = ImageChecksummer().Checksum(self.chromeos_image)
- ret = ("%s %s %s %s %s %s" %
- (hashlib.md5(self.chromeos_image).hexdigest(),
- self.autotest_name, self.iteration, ",".join(self.autotest_args),
- checksum, remote))
-
- return os.path.join(SCRATCH_DIR, self._ConvertToFilename(ret))
+ return (hashlib.md5(self.chromeos_image).hexdigest(),
+ self.autotest_name, str(self.iteration),
+ ",".join(self.autotest_args),
+ checksum, remote)
def ReadResult(self):
if CacheConditions.FALSE in self.cache_conditions:
- self._logger.LogOutput("Cache condition FALSE passed. Not using cache.")
return None
- cache_dir = self._GetCacheDir(True)
- matching_dirs = glob.glob(cache_dir)
+ cache_dir = self._GetCacheDirForRead()
- if matching_dirs:
- # Cache file found.
- if len(matching_dirs) > 1:
- self._logger.LogError("Multiple compatible cache files: %s." %
- " ".join(matching_dirs))
- matching_dir = matching_dirs[0]
- cache_file = os.path.join(matching_dir, RESULTS_FILE)
+ if not cache_dir:
+ return None
+
+ try:
+ cache_file = os.path.join(cache_dir, RESULTS_FILE)
self._logger.LogOutput("Trying to read from cache file: %s" % cache_file)
@@ -104,16 +120,16 @@
CacheConditions.RUN_SUCCEEDED not in self.cache_conditions):
return Result(out, err, retval)
- else:
+ except Exception, e:
if CacheConditions.CACHE_FILE_EXISTS not in self.cache_conditions:
# Cache file not found but just return a failure.
return Result("", "", 1)
- return None
+ raise e
def StoreResult(self, result):
- cache_dir = self._GetCacheDir()
+ cache_dir = self._GetCacheDirForWrite()
cache_file = os.path.join(cache_dir, RESULTS_FILE)
- command = "mkdir -p %s" % os.path.dirname(cache_file)
+ command = "mkdir -p %s" % cache_dir
ret = self._ce.RunCommand(command)
assert ret == 0, "Couldn't create cache dir"
with open(cache_file, "wb") as f:
@@ -124,27 +140,32 @@
def StoreAutotestOutput(self, results_dir):
host_results_dir = os.path.join(self.chromeos_root, "chroot",
results_dir[1:])
- tarball = os.path.join(self._GetCacheDir(), AUTOTEST_TARBALL)
+ tarball = os.path.join(self._GetCacheDirForWrite(), AUTOTEST_TARBALL)
command = ("cd %s && tar cjf %s ." % (host_results_dir, tarball))
ret = self._ce.RunCommand(command)
if ret:
raise Exception("Couldn't store autotest output directory.")
def ReadAutotestOutput(self, destination):
- tarball = os.path.join(self._GetCacheDir(True), AUTOTEST_TARBALL)
+ cache_dir = self._GetCacheDirForWrite()
+ tarball = os.path.join(cache_dir, AUTOTEST_TARBALL)
+ if not os.path.exists(tarball):
+ raise Exception("Cached autotest tarball does not exist at '%s'." %
+ tarball)
command = ("cd %s && tar xjf %s ." % (destination, tarball))
ret = self._ce.RunCommand(command)
if ret:
raise Exception("Couldn't read autotest output directory.")
def StorePerfResults(self, perf):
- perf_path = os.path.join(self._GetCacheDir(), PERF_RESULTS_FILE)
+ perf_path = os.path.join(self._GetCacheDirForWrite(), PERF_RESULTS_FILE)
with open(perf_path, "wb") as f:
pickle.dump(perf.report, f)
pickle.dump(perf.output, f)
def ReadPerfResults(self):
- perf_path = os.path.join(self._GetCacheDir(), PERF_RESULTS_FILE)
+ cache_dir = self._GetCacheDirForRead()
+ perf_path = os.path.join(cache_dir, PERF_RESULTS_FILE)
with open(perf_path, "rb") as f:
report = pickle.load(f)
output = pickle.load(f)
diff --git a/v14/crosperf/results_columns.py b/v14/crosperf/results_columns.py
index a9190b2..06f9c99 100644
--- a/v14/crosperf/results_columns.py
+++ b/v14/crosperf/results_columns.py
@@ -13,7 +13,7 @@
for result in results:
if isinstance(result, str):
return True
- return False
+ return False
def _StripNone(self, results):
res = []
@@ -27,20 +27,26 @@
def Compute(self, results, baseline_results):
if self._ContainsString(results):
return "-"
- return min(self._StripNone(results))
+ results = self._StripNone(results)
+ if not results:
+ return "-"
+ return min(results)
class MaxColumn(Column):
def Compute(self, results, baseline_results):
if self._ContainsString(results):
return "-"
- return max(self._StripNone(results))
+ results = self._StripNone(results)
+ if not results:
+ return "-"
+ return max(results)
class MeanColumn(Column):
def Compute(self, results, baseline_results):
all_pass = True
- all_fail = False
+ all_fail = True
if self._ContainsString(results):
for result in results:
if result != "PASSED":
@@ -53,9 +59,11 @@
elif all_fail:
return "ALL FAIL"
else:
- return "SOME FAIL"
+ return "-"
results = self._StripNone(results)
+ if not results:
+ return "-"
return float(sum(results)) / len(results)
@@ -68,6 +76,8 @@
return "-"
results = self._StripNone(results)
+ if not results:
+ return "-"
n = len(results)
average = sum(results) / n
total = 0
@@ -87,6 +97,8 @@
results = self._StripNone(results)
baseline_results = self._StripNone(baseline_results)
+ if not results or not baseline_results:
+ return "-"
result_mean = sum(results) / len(results)
baseline_mean = sum(baseline_results) / len(baseline_results)
@@ -103,6 +115,8 @@
results = self._StripNone(results)
baseline_results = self._StripNone(baseline_results)
+ if not results or not baseline_results:
+ return "-"
result_mean = sum(results) / len(results)
baseline_mean = sum(baseline_results) / len(baseline_results)
diff --git a/v14/crosperf/results_report.py b/v14/crosperf/results_report.py
index bd369e8..ec6d7df 100644
--- a/v14/crosperf/results_report.py
+++ b/v14/crosperf/results_report.py
@@ -118,6 +118,13 @@
===========================================
-------------------------------------------
+Benchmark Run Status
+-------------------------------------------
+%s
+
+Number re-images: %s
+
+-------------------------------------------
Summary
-------------------------------------------
%s
@@ -137,8 +144,18 @@
def __init__(self, experiment):
super(TextResultsReport, self).__init__(experiment)
+ def GetStatusTable(self):
+ status_table = Table("status")
+ for benchmark_run in self.benchmark_runs:
+ status_table.AddRow([Table.Cell(benchmark_run.name),
+ Table.Cell(benchmark_run.status),
+ Table.Cell(benchmark_run.failure_reason)])
+ return status_table
+
def GetReport(self):
return self.TEXT % (self.experiment.name,
+ self.GetStatusTable().ToText(),
+ self.experiment.machine_manager.num_reimages,
self.GetSummaryTable().ToText(30),
self.GetFullTable().ToText(30),
self.experiment.experiment_file)
@@ -161,6 +178,10 @@
font-size: 14px;
}
+.chart {
+ display: inline;
+}
+
.hidden {
visibility: hidden;
}
diff --git a/v14/crosperf/settings_factory.py b/v14/crosperf/settings_factory.py
index 38b5dd5..3f75283 100644
--- a/v14/crosperf/settings_factory.py
+++ b/v14/crosperf/settings_factory.py
@@ -31,9 +31,9 @@
"collect."))
self.AddField(EnumField("profile_type",
description="The type of profile to collect. "
- "Either 'stat', 'record' or 'none'.",
- options=["stat", "record", "none"],
- default="none"))
+ "Either 'stat', 'record' or ''.",
+ options=["stat", "record", ""],
+ default=""))
class LabelSettings(Settings):
@@ -84,8 +84,8 @@
"collect."))
self.AddField(EnumField("profile_type",
description="The type of profile to collect. "
- "Either 'stat', 'record' or 'none'.",
- options=["stat", "record", "none"]))
+ "Either 'stat', 'record' or ''.",
+ options=["stat", "record", ""]))
class SettingsFactory(object):
diff --git a/v14/utils/command_executer.py b/v14/utils/command_executer.py
index 3373548..37d6e81 100644
--- a/v14/utils/command_executer.py
+++ b/v14/utils/command_executer.py
@@ -44,7 +44,10 @@
self.logger.LogCmd(cmd, machine, username)
if command_terminator and command_terminator.IsTerminated():
self.logger.LogError("Command was terminated!")
- return 1
+ if return_output:
+ return [1, "", ""]
+ else:
+ return 1
if machine is not None:
user = ""
@@ -52,7 +55,6 @@
user = username + "@"
cmd = "ssh -t -t %s%s -- '%s'" % (user, machine, cmd)
-
pty_fds = pty.openpty()
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -74,7 +76,10 @@
self.RunCommand("sudo kill -9 " + str(p.pid))
wait = p.wait()
self.logger.LogError("Command was terminated!")
- return wait
+ if return_output:
+ return (p.wait, full_stdout, full_stderr)
+ else:
+ return wait
for fd in fds[0]:
if fd == p.stdout:
out = os.read(p.stdout.fileno(), 16384)
diff --git a/v14/utils/utils.py b/v14/utils/utils.py
index a6f1553..d82b243 100755
--- a/v14/utils/utils.py
+++ b/v14/utils/utils.py
@@ -12,6 +12,7 @@
import stat
import command_executer
import logger
+import tempfile
from contextlib import contextmanager
@@ -67,17 +68,26 @@
return "./setup_board --board=%s %s" % (board, " ".join(options))
-def ExecuteCommandInChroot(chromeos_root, command, return_output=False):
+def ExecuteCommandInChroot(chromeos_root, command, return_output=False,
+ command_terminator=None):
ce = command_executer.GetCommandExecuter()
- command_file = "in_chroot_cmd.sh"
- command_file_path = os.path.join(chromeos_root, "src/scripts", command_file)
- with open(command_file_path, "w") as f:
+ handle, command_file = tempfile.mkstemp(dir=os.path.join(chromeos_root,
+ "src/scripts"),
+ suffix=".sh",
+ prefix="in_chroot_cmd")
+ # Without this, the handle remains open and we get "file busy" when executing
+ # cros_sdk -- ./<file>.
+ os.close(handle)
+ with open(command_file, "w") as f:
print >> f, "#!/bin/bash"
print >> f, command
- os.chmod(command_file_path, 0777)
+ os.chmod(command_file, 0777)
with WorkingDirectory(chromeos_root):
- command = "cros_sdk -- ./%s" % command_file
- return ce.RunCommand(command, return_output)
+ command = "cros_sdk -- ./%s" % os.path.basename(command_file)
+ ret = ce.RunCommand(command, return_output,
+ command_terminator=command_terminator)
+ os.remove(command_file)
+ return ret
def CanonicalizePath(path):
@@ -111,4 +121,3 @@
msg = "cd %s" % old_dir
logger.GetLogger().LogCmd(msg)
os.chdir(old_dir)
-