variance-analysis/mach_perftest_notebook_dev/perftestnotebook/perftestnotebook.py (155 lines of code) (raw):
#!/usr/bin/env python3
import json
import os
import pathlib
import webbrowser
import yaml
import perftestnotebook.transformer as tfmr
from collections import OrderedDict
from flask import Flask, Response, request, send_file
from perftestnotebook.analyzer import NotebookAnalyzer
from perftestnotebook.constant import Constant
from perftestnotebook.logger import NotebookLogger
from perftestnotebook.notebookparser import parse_args
from perftestnotebook.task_processor import get_task_data_paths
logger = NotebookLogger()
class PerftestNotebook(object):
"""
Controller class for the Perftest-Notebook.
"""
def __init__(self, file_groups, config, custom_transform=None, sort_files=False):
"""
Initializes PerftestNotebook.
:param dict file_groups: A dict of file groupings. The value
of each of the dict entries is the name of the data that
will be produced.
:param str custom_transform: Path to a file containing custom
transformation logic. Must implement the Transformer
interface.
"""
self.fmt_data = {}
self.file_groups = file_groups
self.config = config
self.sort_files = sort_files
const = Constant()
plugin_path = os.getenv("NOTEBOOK_PLUGIN")
tfms_dict = const.predefined_transformers
if plugin_path:
tfms_dict.update(tfmr.get_transformers(plugin_path))
if custom_transform:
tfm_cls = tfms_dict.get(custom_transform)
if tfm_cls:
self.transformer = tfm_cls(files=[])
logger.info(f"Found {custom_transform} transformer")
else:
raise Exception(f"Could not get a {custom_transform} transformer.")
else:
self.transformer = tfmr.SimplePerfherderTransformer(files=[])
self.analyzer = NotebookAnalyzer(data=None)
def parse_file_grouping(self, file_grouping):
"""
Handles differences in the file_grouping definitions. It can either be a
path to a folder containing the files, a list of files, or it can contain
settings from an artifact_downloader instance.
:param file_grouping: A file grouping entry.
:return: A list of files to process.
"""
files = []
print(files)
if type(file_grouping) == list:
# A list of files was provided
files = file_grouping
elif type(file_grouping) == dict:
# A dictionary of settings from an artifact_downloader instance
# was provided here
files = get_task_data_paths(
file_grouping["task_group_id"],
file_grouping["path"],
artifact=file_grouping["artifact"],
artifact_dir=file_grouping.get("artifact_dir", None),
run_number=file_grouping["run_number"],
)
elif type(file_grouping) == str:
# Assume a path to files was given
filepath = files
newf = [f for f in pathlib.Path(filepath).rglob("*.json")]
if not newf:
# Couldn't find any JSON files, so take all the files
# in the directory
newf = [f for f in pathlib.Path(filepath).rglob("*")]
files = newf
else:
raise Exception(
"Unknown file grouping type provided here: %s" % file_grouping
)
if self.sort_files:
if type(files) == list:
files.sort()
else:
for _, file_list in files.items():
file_list.sort()
files = OrderedDict(sorted(files.items(), key=lambda entry: entry[0]))
if not files:
raise Exception(
"Could not find any files in this configuration: %s" % file_grouping
)
return files
def parse_output(self):
prefix = "output" if "prefix" not in self.config else self.config["prefix"]
filepath = "%s_fmt_data.json" % prefix
if "output" in self.config:
filepath = self.config["output"]
return filepath
def process(self, no_iodide=False):
"""
Process the file groups and return the results of the requested analyses.
:return: All the results in a dictionary. The field names are the Analyzer
funtions that were called.
"""
fmt_data = []
notebook_sections = ""
for name, files in self.file_groups.items():
files = self.parse_file_grouping(files)
if isinstance(files, dict):
for subtest, files in files.items():
self.transformer.files = files
trfm_data = self.transformer.process(name)
if type(trfm_data) == list:
for e in trfm_data:
if "subtest" not in e:
e["subtest"] = subtest
else:
e["subtest"] = "%s-%s" % (subtest, e["subtest"])
fmt_data.extend(trfm_data)
else:
if "subtest" not in trfm_data:
trfm_data["subtest"] = subtest
else:
trfm_data["subtest"] = "%s-%s" % (
subtest,
trfm_data["subtest"],
)
fmt_data.append(trfm_data)
else:
# Transform the data
self.transformer.files = files
trfm_data = self.transformer.process(name)
if type(trfm_data) == list:
fmt_data.extend(trfm_data)
else:
fmt_data.append(trfm_data)
self.fmt_data = fmt_data
# Write formatted data output to filepath
output_data_filepath = self.parse_output()
print("Writing results to %s" % output_data_filepath)
with open(output_data_filepath, "w") as f:
json.dump(self.fmt_data, f, indent=4, sort_keys=True)
# Gather config["analysis"] corresponding notebook sections
if "analysis" in self.config:
for func in self.config["analysis"]:
notebook_sections += self.analyzer.get_notebook_section(func)
# Post to Iodide server
if not no_iodide:
self.post_to_iodide(output_data_filepath, notebook_sections)
return self.fmt_data
def post_to_iodide(self, output_data_filepath, notebook_sections):
template_header_path = "testing/resources/notebook-sections/header"
with open(template_header_path, "r") as f:
template_content = f.read()
template_content = template_content + notebook_sections
with open("template_upload_file.html", "r") as f:
html = f.read()
html = html.replace("replace_me", repr(template_content))
with open("upload_file.html", "w+") as f:
f.write(html)
webbrowser.open_new_tab("upload_file.html")
# Set up local server data API.
# Iodide will visit localhost:5000/data
app = Flask(__name__)
app.config["DEBUG"] = False
@app.route("/data", methods=["GET"])
def return_data():
response = flask.make_response(send_file(output_data_filepath))
response.headers["Access-Control-Allow-Origin"] = "*"
return response
app.run()
def main():
args = parse_args()
NotebookLogger.debug = args.debug
config = None
with open(args.config, "r") as f:
logger.info("yaml_path: {}".format(args.config))
config = yaml.safe_load(f)
custom_transform = config.get("custom_transform", None)
ptnb = PerftestNotebook(
config["file_groups"],
config,
custom_transform=custom_transform,
sort_files=args.sort_files,
)
results = ptnb.process(args.no_iodide)
if __name__ == "__main__":
main()