Skip to content

Commit 093a093

Browse files
committed
kci config: Implement pipeline job forecasting tool
Often we need to know, what builds and tests will be triggered on each checkout. We can add option to kci to "forecast" these builds, for example: ./kci config forecast -c ../kernelci-pipeline/config Signed-off-by: Denys Fedoryshchenko <[email protected]>
1 parent f5df64b commit 093a093

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

kernelci/cli/config.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
"""Tool to manage the KernelCI YAML pipeline configuration"""
77

88
import os
9+
import sys
910

1011
import click
1112
import yaml
1213

1314
import kernelci.config
15+
import kernelci.api.helper
1416
from . import Args, kci
1517

1618

@@ -75,3 +77,127 @@ def dump(section, config, indent, recursive):
7577
else:
7678
echo = click.echo_via_pager if recursive else click.echo
7779
echo(yaml.dump(data, indent=indent))
80+
81+
82+
def validate_rules(node, rules):
83+
helper = kernelci.api.helper.APIHelper(None)
84+
if helper.should_create_node(rules, node):
85+
return True
86+
else:
87+
return False
88+
89+
90+
def compare_builds(merged_data):
91+
"""
92+
Compare kbuilds and print builds with identical params
93+
"""
94+
r = ""
95+
jobs = merged_data.get("jobs")
96+
if not jobs:
97+
click.echo("No jobs found in the merged data, "
98+
"maybe you need to add parameter "
99+
"-c path/kernelci-pipeline/config?")
100+
sys.exit(1)
101+
kbuilds_list = []
102+
for job in jobs:
103+
if jobs[job].get("kind") == "kbuild":
104+
kbuilds_list.append(job)
105+
106+
kbuilds_dict = {}
107+
import json
108+
for kbuild in kbuilds_list:
109+
params = jobs[kbuild].get("params", {})
110+
# Convert params to a hashable type by serializing to JSON
111+
key = json.dumps(params, sort_keys=True)
112+
if key not in kbuilds_dict:
113+
kbuilds_dict[key] = []
114+
kbuilds_dict[key].append(kbuild)
115+
116+
# print builds with identical params
117+
for params, kbuild_list in kbuilds_dict.items():
118+
if len(kbuild_list) > 1:
119+
r += f"Params {params}: {kbuild_list},"
120+
121+
122+
def do_forecast(merged_data):
123+
"""
124+
We will simulate checkout event on each tree/branch
125+
and try to build list of builds/tests it will run
126+
"""
127+
checkouts = []
128+
build_configs = merged_data.get("build_configs", {})
129+
for bcfg in build_configs:
130+
data = build_configs[bcfg]
131+
if not data.get("architectures"):
132+
data["architectures"] = None
133+
checkouts.append(data)
134+
135+
# sort checkouts by tree and branch
136+
checkouts.sort(key=lambda x: (x.get("tree", ""), x.get("branch", "")))
137+
138+
# iterate over checkouts
139+
for checkout in checkouts:
140+
checkout["kbuilds"] = []
141+
# iterate over events (jobs)
142+
jobs = merged_data.get("scheduler", [])
143+
for job in jobs:
144+
kind = job.get("event", {}).get("kind")
145+
if kind != "checkout":
146+
continue
147+
job_name = job.get("job")
148+
job_kind = merged_data.get("jobs", {}).get(job_name, {}).get("kind")
149+
if job_kind == "kbuild":
150+
# check "params" "arch"
151+
job_params = merged_data.get("jobs", {}).get(job_name, {}).get("params", {})
152+
arch = job_params.get("arch")
153+
if checkout.get("architectures") and arch not in checkout.get("architectures"):
154+
continue
155+
scheduler_rules = job.get("rules", [])
156+
job = merged_data.get("jobs", {}).get(job_name, {})
157+
job_rules = job.get("rules", [])
158+
node = {
159+
"kind": "checkout",
160+
"data": {
161+
"kernel_revision": {
162+
"tree": checkout.get("tree"),
163+
"branch": checkout.get("branch"),
164+
"version": {
165+
"version": 6,
166+
"patchlevel": 16,
167+
"extra": "-rc3-973-gb7d1bbd97f77"
168+
},
169+
}
170+
},
171+
}
172+
if not validate_rules(node, job_rules):
173+
continue
174+
if not validate_rules(node, scheduler_rules):
175+
continue
176+
checkout["kbuilds"].append(job_name)
177+
checkout["kbuilds_identical"] = compare_builds(merged_data)
178+
179+
# print the results
180+
for checkout in checkouts:
181+
print(f"Checkout: {checkout.get('tree')}:{checkout.get('branch')}")
182+
if checkout.get("kbuilds_identical"):
183+
print(f" Identical builds: {checkout['kbuilds_identical']}")
184+
if checkout.get("kbuilds"):
185+
num_builds = len(checkout["kbuilds"])
186+
print(f" Number of builds: {num_builds}")
187+
print(" Builds:")
188+
for build in checkout["kbuilds"]:
189+
print(f" - {build}")
190+
else:
191+
print(" No builds found for this checkout")
192+
return
193+
194+
195+
@kci_config.command
196+
@Args.config
197+
def forecast(config):
198+
"""Dump entries from the SECTION of the pipeline YAML configuration"""
199+
config_paths = kernelci.config.get_config_paths(config)
200+
if not config_paths:
201+
return {}
202+
data = kernelci.config.load_yaml(config_paths)
203+
do_forecast(data)

0 commit comments

Comments
 (0)