You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
9.3 KiB
242 lines
9.3 KiB
# -*- coding: utf-8 -*-
|
|
# Copyright 2011 The ChromiumOS Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""The experiment file module. It manages the input file of crosperf."""
|
|
|
|
|
|
import os.path
|
|
import re
|
|
|
|
from settings_factory import SettingsFactory
|
|
|
|
|
|
class ExperimentFile(object):
|
|
"""Class for parsing the experiment file format.
|
|
|
|
The grammar for this format is:
|
|
|
|
experiment = { _FIELD_VALUE_RE | settings }
|
|
settings = _OPEN_SETTINGS_RE
|
|
{ _FIELD_VALUE_RE }
|
|
_CLOSE_SETTINGS_RE
|
|
|
|
Where the regexes are terminals defined below. This results in an format
|
|
which looks something like:
|
|
|
|
field_name: value
|
|
settings_type: settings_name {
|
|
field_name: value
|
|
field_name: value
|
|
}
|
|
"""
|
|
|
|
# Field regex, e.g. "iterations: 3"
|
|
_FIELD_VALUE_RE = re.compile(r"(\+)?\s*(\w+?)(?:\.(\S+))?\s*:\s*(.*)")
|
|
# Open settings regex, e.g. "label {"
|
|
_OPEN_SETTINGS_RE = re.compile(r"(?:([\w.-]+):)?\s*([\w.-]+)\s*{")
|
|
# Close settings regex.
|
|
_CLOSE_SETTINGS_RE = re.compile(r"}")
|
|
|
|
def __init__(self, experiment_file, overrides=None):
|
|
"""Construct object from file-like experiment_file.
|
|
|
|
Args:
|
|
experiment_file: file-like object with text description of experiment.
|
|
overrides: A settings object that will override fields in other settings.
|
|
|
|
Raises:
|
|
Exception: if invalid build type or description is invalid.
|
|
"""
|
|
self.all_settings = []
|
|
self.global_settings = SettingsFactory().GetSettings("global", "global")
|
|
self.all_settings.append(self.global_settings)
|
|
|
|
self._Parse(experiment_file)
|
|
|
|
for settings in self.all_settings:
|
|
settings.Inherit()
|
|
settings.Validate()
|
|
if overrides:
|
|
settings.Override(overrides)
|
|
|
|
def GetSettings(self, settings_type):
|
|
"""Return nested fields from the experiment file."""
|
|
res = []
|
|
for settings in self.all_settings:
|
|
if settings.settings_type == settings_type:
|
|
res.append(settings)
|
|
return res
|
|
|
|
def GetGlobalSettings(self):
|
|
"""Return the global fields from the experiment file."""
|
|
return self.global_settings
|
|
|
|
def _ParseField(self, reader):
|
|
"""Parse a key/value field."""
|
|
line = reader.CurrentLine().strip()
|
|
match = ExperimentFile._FIELD_VALUE_RE.match(line)
|
|
append, name, _, text_value = match.groups()
|
|
return (name, text_value, append)
|
|
|
|
def _ParseSettings(self, reader):
|
|
"""Parse a settings block."""
|
|
line = reader.CurrentLine().strip()
|
|
match = ExperimentFile._OPEN_SETTINGS_RE.match(line)
|
|
settings_type = match.group(1)
|
|
if settings_type is None:
|
|
settings_type = ""
|
|
settings_name = match.group(2)
|
|
settings = SettingsFactory().GetSettings(settings_name, settings_type)
|
|
settings.SetParentSettings(self.global_settings)
|
|
|
|
while reader.NextLine():
|
|
line = reader.CurrentLine().strip()
|
|
|
|
if not line:
|
|
continue
|
|
|
|
if ExperimentFile._FIELD_VALUE_RE.match(line):
|
|
field = self._ParseField(reader)
|
|
settings.SetField(field[0], field[1], field[2])
|
|
elif ExperimentFile._CLOSE_SETTINGS_RE.match(line):
|
|
return settings, settings_type
|
|
|
|
raise EOFError("Unexpected EOF while parsing settings block.")
|
|
|
|
def _Parse(self, experiment_file):
|
|
"""Parse experiment file and create settings."""
|
|
reader = ExperimentFileReader(experiment_file)
|
|
settings_names = {}
|
|
try:
|
|
while reader.NextLine():
|
|
line = reader.CurrentLine().strip()
|
|
|
|
if not line:
|
|
continue
|
|
|
|
if ExperimentFile._OPEN_SETTINGS_RE.match(line):
|
|
new_settings, settings_type = self._ParseSettings(reader)
|
|
# We will allow benchmarks with duplicated settings name for now.
|
|
# Further decision will be made when parsing benchmark details in
|
|
# ExperimentFactory.GetExperiment().
|
|
if settings_type != "benchmark":
|
|
if new_settings.name in settings_names:
|
|
raise SyntaxError(
|
|
"Duplicate settings name: '%s'."
|
|
% new_settings.name
|
|
)
|
|
settings_names[new_settings.name] = True
|
|
self.all_settings.append(new_settings)
|
|
elif ExperimentFile._FIELD_VALUE_RE.match(line):
|
|
field = self._ParseField(reader)
|
|
self.global_settings.SetField(field[0], field[1], field[2])
|
|
else:
|
|
raise IOError("Unexpected line.")
|
|
except Exception as err:
|
|
raise RuntimeError(
|
|
"Line %d: %s\n==> %s"
|
|
% (reader.LineNo(), str(err), reader.CurrentLine(False))
|
|
)
|
|
|
|
def Canonicalize(self):
|
|
"""Convert parsed experiment file back into an experiment file."""
|
|
res = ""
|
|
board = ""
|
|
for field_name in self.global_settings.fields:
|
|
field = self.global_settings.fields[field_name]
|
|
if field.assigned:
|
|
res += "%s: %s\n" % (field.name, field.GetString())
|
|
if field.name == "board":
|
|
board = field.GetString()
|
|
res += "\n"
|
|
|
|
for settings in self.all_settings:
|
|
if settings.settings_type != "global":
|
|
res += "%s: %s {\n" % (settings.settings_type, settings.name)
|
|
for field_name in settings.fields:
|
|
field = settings.fields[field_name]
|
|
if field.assigned:
|
|
res += "\t%s: %s\n" % (field.name, field.GetString())
|
|
if field.name == "chromeos_image":
|
|
real_file = os.path.realpath(
|
|
os.path.expanduser(field.GetString())
|
|
)
|
|
if real_file != field.GetString():
|
|
res += "\t#actual_image: %s\n" % real_file
|
|
if field.name == "build":
|
|
chromeos_root_field = settings.fields[
|
|
"chromeos_root"
|
|
]
|
|
if chromeos_root_field:
|
|
chromeos_root = chromeos_root_field.GetString()
|
|
value = field.GetString()
|
|
autotest_field = settings.fields["autotest_path"]
|
|
autotest_path = ""
|
|
if autotest_field.assigned:
|
|
autotest_path = autotest_field.GetString()
|
|
debug_field = settings.fields["debug_path"]
|
|
debug_path = ""
|
|
if debug_field.assigned:
|
|
debug_path = autotest_field.GetString()
|
|
# Do not download the debug symbols since this function is for
|
|
# canonicalizing experiment file.
|
|
downlad_debug = False
|
|
(
|
|
image_path,
|
|
autotest_path,
|
|
debug_path,
|
|
) = settings.GetXbuddyPath(
|
|
value,
|
|
autotest_path,
|
|
debug_path,
|
|
board,
|
|
chromeos_root,
|
|
"quiet",
|
|
downlad_debug,
|
|
)
|
|
res += "\t#actual_image: %s\n" % image_path
|
|
if not autotest_field.assigned:
|
|
res += (
|
|
"\t#actual_autotest_path: %s\n"
|
|
% autotest_path
|
|
)
|
|
if not debug_field.assigned:
|
|
res += "\t#actual_debug_path: %s\n" % debug_path
|
|
|
|
res += "}\n\n"
|
|
|
|
return res
|
|
|
|
|
|
class ExperimentFileReader(object):
|
|
"""Handle reading lines from an experiment file."""
|
|
|
|
def __init__(self, file_object):
|
|
self.file_object = file_object
|
|
self.current_line = None
|
|
self.current_line_no = 0
|
|
|
|
def CurrentLine(self, strip_comment=True):
|
|
"""Return the next line from the file, without advancing the iterator."""
|
|
if strip_comment:
|
|
return self._StripComment(self.current_line)
|
|
return self.current_line
|
|
|
|
def NextLine(self, strip_comment=True):
|
|
"""Advance the iterator and return the next line of the file."""
|
|
self.current_line_no += 1
|
|
self.current_line = self.file_object.readline()
|
|
return self.CurrentLine(strip_comment)
|
|
|
|
def _StripComment(self, line):
|
|
"""Strip comments starting with # from a line."""
|
|
if "#" in line:
|
|
line = line[: line.find("#")] + line[-1]
|
|
return line
|
|
|
|
def LineNo(self):
|
|
"""Return the current line number."""
|
|
return self.current_line_no
|