Skip to content

Commit bb7a15a

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 bb7a15a

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+
return r
122+
123+
124+
# pylint: disable=too-many-branches disable=too-many-locals
125+
def do_forecast(merged_data):
126+
"""
127+
We will simulate checkout event on each tree/branch
128+
and try to build list of builds/tests it will run
129+
"""
130+
checkouts = []
131+
build_configs = merged_data.get("build_configs", {})
132+
for bcfg in build_configs:
133+
data = build_configs[bcfg]
134+
if not data.get("architectures"):
135+
data["architectures"] = None
136+
checkouts.append(data)
137+
138+
# sort checkouts by tree and branch
139+
checkouts.sort(key=lambda x: (x.get("tree", ""), x.get("branch", "")))
140+
141+
# iterate over checkouts
142+
for checkout in checkouts:
143+
checkout["kbuilds"] = []
144+
# iterate over events (jobs)
145+
jobs = merged_data.get("scheduler", [])
146+
for job in jobs:
147+
kind = job.get("event", {}).get("kind")
148+
if kind != "checkout":
149+
continue
150+
job_name = job.get("job")
151+
job_kind = merged_data.get("jobs", {}).get(job_name, {}).get("kind")
152+
if job_kind == "kbuild":
153+
# check "params" "arch"
154+
job_params = merged_data.get("jobs", {}).get(job_name, {}).get("params", {})
155+
arch = job_params.get("arch")
156+
if checkout.get("architectures") and arch not in checkout.get("architectures"):
157+
continue
158+
scheduler_rules = job.get("rules", [])
159+
job = merged_data.get("jobs", {}).get(job_name, {})
160+
job_rules = job.get("rules", [])
161+
node = {
162+
"kind": "checkout",
163+
"data": {
164+
"kernel_revision": {
165+
"tree": checkout.get("tree"),
166+
"branch": checkout.get("branch"),
167+
"version": {
168+
"version": 6,
169+
"patchlevel": 16,
170+
"extra": "-rc3-973-gb7d1bbd97f77"
171+
},
172+
}
173+
},
174+
}
175+
if not validate_rules(node, job_rules) or not validate_rules(node, scheduler_rules):
176+
continue
177+
checkout["kbuilds"].append(job_name)
178+
checkout["kbuilds_identical"] = compare_builds(merged_data)
179+
180+
# print the results
181+
for checkout in checkouts:
182+
print(f"Checkout: {checkout.get('tree')}:{checkout.get('branch')}")
183+
if checkout.get("kbuilds_identical"):
184+
print(f" Identical builds: {checkout['kbuilds_identical']}")
185+
if checkout.get("kbuilds"):
186+
num_builds = len(checkout["kbuilds"])
187+
print(f" Number of builds: {num_builds}")
188+
print(" Builds:")
189+
for build in checkout["kbuilds"]:
190+
print(f" - {build}")
191+
else:
192+
print(" No builds found for this checkout")
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)