chore: kernel selector in Kconfig

This commit is contained in:
Forsworns
2022-02-16 14:49:54 +08:00
parent aa27dea6e9
commit 60cd74e296
13 changed files with 621 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
#! /usr/bin/python3
import os
ubiquitous_dir = os.environ.get('UBIQUITOUS_ROOT')
bsp_dir = os.environ.get('BSP_ROOT')
kernel_names = []
def get_kernel_names():
for d in os.scandir(ubiquitous_dir):
if d.is_dir() and d.name!= "feature_yaml":
kernel_names.append(d.name)
def generate_features():
with open(f"{ubiquitous_dir}/Kselector_features","w") as f:
# f.write("")
pass
def generate_params():
with open(f"{ubiquitous_dir}/Kselector_params","w") as f:
# f.write("")
pass
def generate():
get_kernel_names()
generate_features()
generate_params()
template = r"""
mainmenu "Ubiquitous Kernel Selector"
config BSP_DIR
string
option env="BSP_ROOT"
default "."
config KERNEL_DIR
string
option env="KERNEL_ROOT"
default "../.."
config UBIQUITOUS_DIR
string
option env="UBIQUITOUS_ROOT"
default "../../.."
source "$UBIQUITOUS_DIR/Kselector_features"
source "$UBIQUITOUS_DIR/Kselector_params"
config MANUALLY_SELECT
bool "Manually select a kernel"
default n
if MANUALLY_SELECT
menu "Required Kernel"
choice
prompt "Select OS Kernel"
default SELECT_XIUOS
"""
for kernel_name in kernel_names:
template += f"\tconfig SELECT_{kernel_name.upper()}\n"
template += f'\t\tbool "select {kernel_name}"\n'
# the last `\n` is very important, otherwise, it cannot be recognized as valid Kconfig file
template += "endchoice\nendmenu\nendif\n"
with open(f"{bsp_dir}/Kselector", "w") as f:
f.write(template)
if __name__ == '__main__':
generate()
+159
View File
@@ -0,0 +1,159 @@
#! /usr/bin/python3
# this script builds a decision tree to select suitable kernels from developers' preferences
from __future__ import annotations
from typing import TypeVar, Generic, Tuple, Optional
import os
import yaml
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
ubiquitous_dir = os.environ.get('UBIQUITOUS_ROOT')
kernel_dir = os.environ.get('KERNEL_ROOT')
YAMLS_PATH = f"{ubiquitous_dir}/feature_yaml/"
REQUIREMENT_PATH = f"{kernel_dir}/requirement.yaml"
COMPONENT_KEY = "Feature"
PARAMETER_KEY = "Parameter"
MANUAL_KEY = "Kernel"
# tree node for kernel features
V = TypeVar("V")
class TreeNode(Generic[V]):
def __init__(self, depth: int, name: str, value: V):
self.depth = depth
self.name = name
self.value = value
self.children = dict()
def __repr__(self):
res = f"L{self.depth}: name {self.name} - value {self.value} - {len(self.children)} children\n"
for c in self.children:
# recursively build representation
res += self.children[c].__repr__()
return res
def node_number(self):
if len(self.children) == 0:
return 1
number = 1
for c in self.children:
number += self.children[c].node_number()
return number
# this class should be constructed from the yaml file
class Kernel:
def __init__(self, name, feature_yaml: dict):
self.name = name
self.features_root: Optional[TreeNode[bool]] = None
self.parameters_root: Optional[TreeNode[int]] = None
self.extract_components(feature_yaml)
self.extract_parameters(feature_yaml)
def __repr__(self):
return f"{self.name}\nFeature Tree:\n{self.features_root}Parameter Tree:\n{self.parameters_root}\n"
def extract_components(self, feature_yaml: dict):
self.features_root = TreeNode(0, COMPONENT_KEY, True)
for k,v in feature_yaml[COMPONENT_KEY].items():
node = TreeNode(1, k, v)
if k in feature_yaml:
self.build_sub_tree(2, node, feature_yaml)
self.features_root.children[k] = node
def build_sub_tree(self, depth, node: TreeNode[bool], feature_yaml: dict):
if feature_yaml[node.name] is None:
return
for k,v in feature_yaml[node.name].items():
child = TreeNode(depth, k, v)
if child.name in feature_yaml:
self.build_sub_tree(depth+1, child, feature_yaml)
node.children[k] = child
def extract_parameters(self, feature_yaml: dict):
self.parameters_root = TreeNode(0, PARAMETER_KEY, 0)
for k,v in feature_yaml[PARAMETER_KEY].items():
child = TreeNode(1, k, v)
self.parameters_root.children[k] = child
def check(self, requirements_root, root) -> Tuple[bool, int]:
distance = 0
q = list()
q.append((requirements_root, root))
while len(q) > 0:
(r_node, s_node) = q.pop()
for c in r_node.children:
if c in s_node.children and self.compare(c, s_node.children[c].value, r_node.children[c].value):
q.append((r_node.children[c], s_node.children[c]))
distance += (r_node.children[c].value - s_node.children[c].value)**2
else:
return False, distance
return True, distance
def compare(self, name, v1, v2):
if name.endswith("GREATER_THAN"):
return v1 >= v2
elif name.endswith("LESS_THAN"):
return v1 <= v2
else:
return v1 == v2
# load the requirements from the yaml file
def load_required_features()->dict:
with open(REQUIREMENT_PATH, 'r') as f:
content = f.read()
feature_yaml = yaml.load(content, Loader=Loader)
print(feature_yaml)
return feature_yaml
# load kernel yamls from ubiquitous directory and build Kernel class
def load_kernel_features()->list[Kernel]:
kernels = {}
for file_name in os.listdir(YAMLS_PATH):
kernel_name = file_name.rstrip(".yaml")
with open(os.path.join(YAMLS_PATH, file_name), 'r') as f:
content = f.read()
feature_yaml = yaml.load(content, Loader=Loader)
kernels[kernel_name] = Kernel(kernel_name, feature_yaml)
return kernels
# recommend a suitable kernel according to the requirement
def select_kernel(requirement: dict, kernels: dict[Kernel]) -> str:
selected = None
selected_node_number = 0
selected_distance = 0
requirement = Kernel("requirement", requirement)
for name, kernel in kernels.items():
# here should be a tree matching algorithm
# requirement trees (both features and parameters)
# should be subtrees of the recommended kernel
pass_features, _ = kernel.check(requirement.features_root, kernel.features_root)
pass_parameters, distance = kernel.check(requirement.parameters_root, kernel.parameters_root)
print(name, pass_features, pass_parameters, distance)
if pass_features and pass_parameters:
if selected is None:
selected = kernel
selected_node_number = kernel.features_root.node_number()
selected_node_number = distance
else:
node_number = kernel.features_root.node_number()
if selected_node_number > node_number or (selected_node_number == node_number and selected_distance > distance):
selected = kernel
selected_node_number = kernel.features_root.node_number()
selected_node_number = distance
if selected is None:
return "Cannot find suitable kernel"
else:
return selected.name
if __name__ == "__main__":
requirement = load_required_features()
if MANUAL_KEY in requirement:
selected = list(requirement[MANUAL_KEY].keys())[0][7:]
else:
kernels = load_kernel_features()
selected = select_kernel(requirement, kernels)
print(f"Selected kernel is: {selected}")
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# use this script to read the `.feature file` generated from `kconfig-mconf `, which further parse Kfeature to build the menu
# this script will genearte a more structured yaml file,
# the json file would further be sent to a decision tree for kernel selection.
VERSION=0.1.0
function generate_requirement_file()
{
local SELECTOR_NAME=${1}
# destination file using file descriptor 8
exec 8>${2}
echo -ne "version: ${VERSION}\n" >&8
EMPTY_LINE='true'
while read LN
do
LINE=`echo $LN | sed 's/[ \t\r\n]*$//g'`
if [ -z "$LINE" ]; then
continue
fi
if [ '#' = ${LINE:0:1} ]; then
if [ ${#LINE} -eq 1 ]; then
# empty line
if $EMPTY_LINE; then
continue
fi
echo >&8
EMPTY_LINE='true'
continue
fi
if echo -ne "$LINE" | egrep '^# Required' >/dev/null 2>/dev/null; then
# the key in yaml
echo -ne "${LINE:11}:\n" >&8
else
LINE=${LINE:1}
echo -ne "# ${LINE}\n" >&8
fi
EMPTY_LINE='false'
else
EMPTY_LINE='false'
OLD_IFS="$IFS"
IFS='='
REQUIREMENTS=($LINE)
IFS="$OLD_IFS"
if [ ${#REQUIREMENTS[@]} -ge 2 ]; then
if echo -ne "$REQUIREMENTS[0]" | egrep '^CONFIG_' >/dev/null 2>/dev/null; then
REQUIREMENTS[0]="${REQUIREMENTS[0]:7}"
fi
if [ "${REQUIREMENTS[1]}" = 'y' ]; then
echo -ne " ${REQUIREMENTS[0]}: true\n" >&8
else
echo -ne " ${REQUIREMENTS[0]}: ${LINE#*=}\n" >&8
fi
fi
fi
done < $SELECTOR_NAME
exec 8<&-
}
generate_requirement_file $1 $BSP_ROOT/requirement.yaml