yocto-autobuilder-helper/scripts/generate-testresult-index.py
Richard Purdie 9dc1462c0b scripts/generate-testresult-index: Add a cache and locking
Add a cache to improve performance and allow old index entries to be reused.

Also add locking so only one copy of the script can run at once.

Fix the path to the index template to be more stable in different execution environments.

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2024-07-12 17:55:21 +01:00

189 lines
5.5 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright Linux Foundation, Richard Purdie
#
# SPDX-License-Identifier: GPL-2.0-only
#
import argparse
import os
import glob
import json
import re
import subprocess
import sys
import fcntl
import time
from jinja2 import Template
def parse_args(argv=None):
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description="Generate an html index for a directory of autobuilder results",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('path', help='path to directory to index')
return parser.parse_args(argv)
args = parse_args()
path = os.path.abspath(args.path)
entries = []
filter_items = dict()
build_types = set()
branch_list= set()
current_time = time.time()
# number of seconds in 2 days
days = 2 * 24 * 60 * 60
lockname = path + "/indexing.lock"
cachefile = path + "/indexing.json"
try:
lockfile = open(lockname, 'a+')
fileno = lockfile.fileno()
fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as e:
print("Couldn't obtain the lock %s: %s" % (lockname, str(e)))
sys.exit(1)
def get_build_branch(p):
for root, dirs, files in os.walk(p):
for name in files:
if name == "testresults.json":
f = os.path.join(root, name)
with open(f, "r") as filedata:
data = json.load(filedata)
for build in data:
try:
return data[build]['configuration']['LAYERS']['meta']['branch']
except KeyError:
continue
return ""
cache = {}
if os.path.exists(cachefile):
with open(cachefile, "r") as f:
cache = json.load(f)
# Pad so 20190601-1 becomes 20190601-000001 and sorts correctly
def keygen(k):
m = re.match(r"(\d+)-(\d+)", k)
if m:
k1, k2 = m.groups()
return k1 + "-" + k2.rjust(6, '0')
else:
return k
for build in sorted(os.listdir(path), key=keygen, reverse=True):
buildpath = os.path.join(path, build, "testresults")
if not os.path.exists(buildpath):
# No test results
continue
modified_time = os.stat(buildpath).st_mtime
# Show cached entries for older directories
if build in cache and (modified_time < (current_time - days)):
entries.append(cache[build])
continue
reldir = "./" + build + "/"
btype = "other"
files = os.listdir(buildpath)
if os.path.exists(buildpath + "/a-full-posttrigger") or \
os.path.exists(buildpath + "/a-full"):
btype = "full"
elif os.path.exists(buildpath + "/a-quick-posttrigger") or \
os.path.exists(buildpath + "/a-quick"):
btype = "quick"
elif len(files) == 1:
btype = files[0]
testreport = ""
if os.path.exists(buildpath + "/testresult-report.txt"):
testreport = reldir + "testresults/testresult-report.txt"
regressionreport = ""
if os.path.exists(buildpath + "/testresult-regressions-report.txt"):
regressionreport = reldir + "testresults/testresult-regressions-report.txt"
ptestlogs = []
ptestseen = []
for p in glob.glob(buildpath + "/*-ptest/*.log"):
if p.endswith("resulttool-done.log"):
continue
buildname = os.path.basename(os.path.dirname(p))
if buildname not in ptestseen:
ptestlogs.append((reldir + "testresults/" + buildname + "/", buildname.replace("-ptest","")))
ptestseen.append(buildname)
perfreports = []
for p in glob.glob(buildpath + "/buildperf*/*.html"):
perfname = os.path.basename(os.path.dirname(p))
perfreports.append((reldir + "testresults/" + perfname + "/" + os.path.basename(p), perfname.replace("buildperf-","")))
buildhistory = []
if os.path.exists(buildpath + "/qemux86-64/buildhistory.txt"):
buildhistory.append((reldir + "testresults/qemux86-64/buildhistory.txt", "qemux86-64"))
if os.path.exists(buildpath + "/qemuarm/buildhistory.txt"):
buildhistory.append((reldir + "testresults/qemuarm/buildhistory.txt", "qemuarm"))
hd = []
for p in glob.glob(buildpath + "/*/*/host_stats*summary.txt"):
n_split = p.split(build)
res = reldir[0:-1] + n_split[1]
n = os.path.basename(p).split("host_stats_")[-1]
if "failure" in n:
n = n.split("_summary.txt")[0]
elif "top" in n:
n = n.split("_top_summary.txt")[0]
hd.append((res, n))
branch = get_build_branch(buildpath)
build_types.add(btype)
if branch: branch_list.add(branch)
# Creates a dictionary of items to be filtered for build types and branch
filter_items["build_types"] = build_types
filter_items["branch_list"] = branch_list
entry = {
'build': build,
'btype': btype,
'reldir': reldir
}
if testreport:
entry['testreport'] = testreport
if branch:
entry['branch'] = branch
if buildhistory:
entry['buildhistory'] = buildhistory
if perfreports:
entry['perfreports'] = perfreports
if ptestlogs:
entry['ptestlogs'] = ptestlogs
if hd:
entry['hd'] = hd
if regressionreport:
entry['regressionreport'] = regressionreport
entries.append(entry)
cache[build] = entry
with open(sys.path[0] + "/index-table.html") as file_:
t = Template(file_.read())
with open(os.path.join(path, "index.html"), 'w') as f:
f.write(t.render(entries = entries, filter_items = filter_items))
with open(cachefile, "w") as f:
json.dump(cache, f)