mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00
scripts/get_abi.py: add support for undefined ABIs
The undefined logic is complex and has lots of magic on it. Implement it, using the same algorithm we have at get_abi.pl. Yet, some tweaks to optimize performance and to make the code simpler were added here: - at the perl version, the tree graph had loops, so we had to use BFS to traverse it. On this version, the graph is a tree, so, it simplifies the what group for sysfs aliases; - the logic which splits regular expressions into subgroups was re-written to make it faster; - it may optionally use multiple processes to search for symbol matches; - it has some additional debug levels. Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> Signed-off-by: Jonathan Corbet <corbet@lwn.net> Link: https://lore.kernel.org/r/1529c255845d117696d5af57d8dc05554663afdf.1739182025.git.mchehab+huawei@kernel.org
This commit is contained in:
parent
6649b42170
commit
0d5fd96880
@ -20,7 +20,9 @@ SRC_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
|
||||
|
||||
from abi_parser import AbiParser # pylint: disable=C0413
|
||||
from abi_regex import AbiRegex # pylint: disable=C0413
|
||||
from helpers import ABI_DIR, DEBUG_HELP # pylint: disable=C0413
|
||||
from system_symbols import SystemSymbols # pylint: disable=C0413
|
||||
|
||||
# Command line classes
|
||||
|
||||
@ -111,6 +113,71 @@ class AbiSearch:
|
||||
parser.parse_abi()
|
||||
parser.search_symbols(args.expression)
|
||||
|
||||
UNDEFINED_DESC="""
|
||||
Check undefined ABIs on local machine.
|
||||
|
||||
Read sysfs devnodes and check if the devnodes there are defined inside
|
||||
ABI documentation.
|
||||
|
||||
The search logic tries to minimize the number of regular expressions to
|
||||
search per each symbol.
|
||||
|
||||
By default, it runs on a single CPU, as Python support for CPU threads
|
||||
is still experimental, and multi-process runs on Python is very slow.
|
||||
|
||||
On experimental tests, if the number of ABI symbols to search per devnode
|
||||
is contained on a limit of ~150 regular expressions, using a single CPU
|
||||
is a lot faster than using multiple processes. However, if the number of
|
||||
regular expressions to check is at the order of ~30000, using multiple
|
||||
CPUs speeds up the check.
|
||||
"""
|
||||
|
||||
class AbiUndefined:
|
||||
"""
|
||||
Initialize an argparse subparser for logic to check undefined ABI at
|
||||
the current machine's sysfs
|
||||
"""
|
||||
|
||||
def __init__(self, subparsers):
|
||||
"""Initialize argparse subparsers"""
|
||||
|
||||
parser = subparsers.add_parser("undefined",
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
description=UNDEFINED_DESC)
|
||||
|
||||
parser.add_argument("-S", "--sysfs-dir", default="/sys",
|
||||
help="directory where sysfs is mounted")
|
||||
parser.add_argument("-s", "--search-string",
|
||||
help="search string regular expression to limit symbol search")
|
||||
parser.add_argument("-H", "--show-hints", action="store_true",
|
||||
help="Hints about definitions for missing ABI symbols.")
|
||||
parser.add_argument("-j", "--jobs", "--max-workers", type=int, default=1,
|
||||
help="If bigger than one, enables multiprocessing.")
|
||||
parser.add_argument("-c", "--max-chunk-size", type=int, default=50,
|
||||
help="Maximum number of chunk size")
|
||||
parser.add_argument("-f", "--found", action="store_true",
|
||||
help="Also show found items. "
|
||||
"Helpful to debug the parser."),
|
||||
parser.add_argument("-d", "--dry-run", action="store_true",
|
||||
help="Don't actually search for undefined. "
|
||||
"Helpful to debug the parser."),
|
||||
|
||||
parser.set_defaults(func=self.run)
|
||||
|
||||
def run(self, args):
|
||||
"""Run subparser"""
|
||||
|
||||
abi = AbiRegex(args.dir, debug=args.debug,
|
||||
search_string=args.search_string)
|
||||
|
||||
abi_symbols = SystemSymbols(abi=abi, hints=args.show_hints,
|
||||
sysfs=args.sysfs_dir)
|
||||
|
||||
abi_symbols.check_undefined_symbols(dry_run=args.dry_run,
|
||||
found=args.found,
|
||||
max_workers=args.jobs,
|
||||
chunk_size=args.max_chunk_size)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main program"""
|
||||
@ -125,6 +192,7 @@ def main():
|
||||
AbiRest(subparsers)
|
||||
AbiValidate(subparsers)
|
||||
AbiSearch(subparsers)
|
||||
AbiUndefined(subparsers)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
234
scripts/lib/abi/abi_regex.py
Normal file
234
scripts/lib/abi/abi_regex.py
Normal file
@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
# xxpylint: disable=R0903
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
Convert ABI what into regular expressions
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pprint import pformat
|
||||
|
||||
from abi_parser import AbiParser
|
||||
from helpers import AbiDebug
|
||||
|
||||
class AbiRegex(AbiParser):
|
||||
"""Extends AbiParser to search ABI nodes with regular expressions"""
|
||||
|
||||
# Escape only ASCII visible characters
|
||||
escape_symbols = r"([\x21-\x29\x2b-\x2d\x3a-\x40\x5c\x60\x7b-\x7e])"
|
||||
leave_others = "others"
|
||||
|
||||
# Tuples with regular expressions to be compiled and replacement data
|
||||
re_whats = [
|
||||
# Drop escape characters that might exist
|
||||
(re.compile("\\\\"), ""),
|
||||
|
||||
# Temporarily escape dot characters
|
||||
(re.compile(r"\."), "\xf6"),
|
||||
|
||||
# Temporarily change [0-9]+ type of patterns
|
||||
(re.compile(r"\[0\-9\]\+"), "\xff"),
|
||||
|
||||
# Temporarily change [\d+-\d+] type of patterns
|
||||
(re.compile(r"\[0\-\d+\]"), "\xff"),
|
||||
(re.compile(r"\[0:\d+\]"), "\xff"),
|
||||
(re.compile(r"\[(\d+)\]"), "\xf4\\\\d+\xf5"),
|
||||
|
||||
# Temporarily change [0-9] type of patterns
|
||||
(re.compile(r"\[(\d)\-(\d)\]"), "\xf4\1-\2\xf5"),
|
||||
|
||||
# Handle multiple option patterns
|
||||
(re.compile(r"[\{\<\[]([\w_]+)(?:[,|]+([\w_]+)){1,}[\}\>\]]"), r"(\1|\2)"),
|
||||
|
||||
# Handle wildcards
|
||||
(re.compile(r"([^\/])\*"), "\\1\\\\w\xf7"),
|
||||
(re.compile(r"/\*/"), "/.*/"),
|
||||
(re.compile(r"/\xf6\xf6\xf6"), "/.*"),
|
||||
(re.compile(r"\<[^\>]+\>"), "\\\\w\xf7"),
|
||||
(re.compile(r"\{[^\}]+\}"), "\\\\w\xf7"),
|
||||
(re.compile(r"\[[^\]]+\]"), "\\\\w\xf7"),
|
||||
|
||||
(re.compile(r"XX+"), "\\\\w\xf7"),
|
||||
(re.compile(r"([^A-Z])[XYZ]([^A-Z])"), "\\1\\\\w\xf7\\2"),
|
||||
(re.compile(r"([^A-Z])[XYZ]$"), "\\1\\\\w\xf7"),
|
||||
(re.compile(r"_[AB]_"), "_\\\\w\xf7_"),
|
||||
|
||||
# Recover [0-9] type of patterns
|
||||
(re.compile(r"\xf4"), "["),
|
||||
(re.compile(r"\xf5"), "]"),
|
||||
|
||||
# Remove duplicated spaces
|
||||
(re.compile(r"\s+"), r" "),
|
||||
|
||||
# Special case: drop comparison as in:
|
||||
# What: foo = <something>
|
||||
# (this happens on a few IIO definitions)
|
||||
(re.compile(r"\s*\=.*$"), ""),
|
||||
|
||||
# Escape all other symbols
|
||||
(re.compile(escape_symbols), r"\\\1"),
|
||||
(re.compile(r"\\\\"), r"\\"),
|
||||
(re.compile(r"\\([\[\]\(\)\|])"), r"\1"),
|
||||
(re.compile(r"(\d+)\\(-\d+)"), r"\1\2"),
|
||||
|
||||
(re.compile(r"\xff"), r"\\d+"),
|
||||
|
||||
# Special case: IIO ABI which a parenthesis.
|
||||
(re.compile(r"sqrt(.*)"), r"sqrt(.*)"),
|
||||
|
||||
# Simplify regexes with multiple .*
|
||||
(re.compile(r"(?:\.\*){2,}"), ""),
|
||||
|
||||
# Recover dot characters
|
||||
(re.compile(r"\xf6"), "\\."),
|
||||
# Recover plus characters
|
||||
(re.compile(r"\xf7"), "+"),
|
||||
]
|
||||
re_has_num = re.compile(r"\\d")
|
||||
|
||||
# Symbol name after escape_chars that are considered a devnode basename
|
||||
re_symbol_name = re.compile(r"(\w|\\[\.\-\:])+$")
|
||||
|
||||
# List of popular group names to be skipped to minimize regex group size
|
||||
# Use AbiDebug.SUBGROUP_SIZE to detect those
|
||||
skip_names = set(["devices", "hwmon"])
|
||||
|
||||
def regex_append(self, what, new):
|
||||
"""
|
||||
Get a search group for a subset of regular expressions.
|
||||
|
||||
As ABI may have thousands of symbols, using a for to search all
|
||||
regular expressions is at least O(n^2). When there are wildcards,
|
||||
the complexity increases substantially, eventually becoming exponential.
|
||||
|
||||
To avoid spending too much time on them, use a logic to split
|
||||
them into groups. The smaller the group, the better, as it would
|
||||
mean that searches will be confined to a small number of regular
|
||||
expressions.
|
||||
|
||||
The conversion to a regex subset is tricky, as we need something
|
||||
that can be easily obtained from the sysfs symbol and from the
|
||||
regular expression. So, we need to discard nodes that have
|
||||
wildcards.
|
||||
|
||||
If it can't obtain a subgroup, place the regular expression inside
|
||||
a special group (self.leave_others).
|
||||
"""
|
||||
|
||||
search_group = None
|
||||
|
||||
for search_group in reversed(new.split("/")):
|
||||
if not search_group or search_group in self.skip_names:
|
||||
continue
|
||||
if self.re_symbol_name.match(search_group):
|
||||
break
|
||||
|
||||
if not search_group:
|
||||
search_group = self.leave_others
|
||||
|
||||
if self.debug & AbiDebug.SUBGROUP_MAP:
|
||||
self.log.debug("%s: mapped as %s", what, search_group)
|
||||
|
||||
try:
|
||||
if search_group not in self.regex_group:
|
||||
self.regex_group[search_group] = []
|
||||
|
||||
self.regex_group[search_group].append(re.compile(new))
|
||||
if self.search_string:
|
||||
if what.find(self.search_string) >= 0:
|
||||
print(f"What: {what}")
|
||||
except re.PatternError:
|
||||
self.log.warning("Ignoring '%s' as it produced an invalid regex:\n"
|
||||
" '%s'", what, new)
|
||||
|
||||
def get_regexes(self, what):
|
||||
"""
|
||||
Given an ABI devnode, return a list of all regular expressions that
|
||||
may match it, based on the sub-groups created by regex_append()
|
||||
"""
|
||||
|
||||
re_list = []
|
||||
|
||||
patches = what.split("/")
|
||||
patches.reverse()
|
||||
patches.append(self.leave_others)
|
||||
|
||||
for search_group in patches:
|
||||
if search_group in self.regex_group:
|
||||
re_list += self.regex_group[search_group]
|
||||
|
||||
return re_list
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Override init method to get verbose argument
|
||||
"""
|
||||
|
||||
self.regex_group = None
|
||||
self.search_string = None
|
||||
self.re_string = None
|
||||
|
||||
if "search_string" in kwargs:
|
||||
self.search_string = kwargs.get("search_string")
|
||||
del kwargs["search_string"]
|
||||
|
||||
if self.search_string:
|
||||
|
||||
try:
|
||||
self.re_string = re.compile(self.search_string)
|
||||
except re.PatternError as e:
|
||||
msg = f"{self.search_string} is not a valid regular expression"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def parse_abi(self, *args, **kwargs):
|
||||
|
||||
super().parse_abi(*args, **kwargs)
|
||||
|
||||
self.regex_group = {}
|
||||
|
||||
print("Converting ABI What fields into regexes...", file=sys.stderr)
|
||||
|
||||
for t in sorted(self.data.items(), key=lambda x: x[0]):
|
||||
v = t[1]
|
||||
if v.get("type") == "File":
|
||||
continue
|
||||
|
||||
v["regex"] = []
|
||||
|
||||
for what in v.get("what", []):
|
||||
if not what.startswith("/sys"):
|
||||
continue
|
||||
|
||||
new = what
|
||||
for r, s in self.re_whats:
|
||||
try:
|
||||
new = r.sub(s, new)
|
||||
except re.PatternError as e:
|
||||
# Help debugging troubles with new regexes
|
||||
raise re.PatternError(f"{e}\nwhile re.sub('{r.pattern}', {s}, str)") from e
|
||||
|
||||
v["regex"].append(new)
|
||||
|
||||
if self.debug & AbiDebug.REGEX:
|
||||
self.log.debug("%-90s <== %s", new, what)
|
||||
|
||||
# Store regex into a subgroup to speedup searches
|
||||
self.regex_append(what, new)
|
||||
|
||||
if self.debug & AbiDebug.SUBGROUP_DICT:
|
||||
self.log.debug("%s", pformat(self.regex_group))
|
||||
|
||||
if self.debug & AbiDebug.SUBGROUP_SIZE:
|
||||
biggestd_keys = sorted(self.regex_group.keys(),
|
||||
key= lambda k: len(self.regex_group[k]),
|
||||
reverse=True)
|
||||
|
||||
print("Top regex subgroups:", file=sys.stderr)
|
||||
for k in biggestd_keys[:10]:
|
||||
print(f"{k} has {len(self.regex_group[k])} elements", file=sys.stderr)
|
@ -16,13 +16,23 @@ class AbiDebug:
|
||||
WHAT_PARSING = 1
|
||||
WHAT_OPEN = 2
|
||||
DUMP_ABI_STRUCTS = 4
|
||||
UNDEFINED = 8
|
||||
REGEX = 16
|
||||
SUBGROUP_MAP = 32
|
||||
SUBGROUP_DICT = 64
|
||||
SUBGROUP_SIZE = 128
|
||||
GRAPH = 256
|
||||
|
||||
|
||||
DEBUG_HELP = """
|
||||
Print debug information according with the level(s),
|
||||
which is given by the following bitmask:
|
||||
|
||||
1 - enable debug parsing logic
|
||||
2 - enable debug messages on file open
|
||||
4 - enable debug for ABI parse data
|
||||
8 - enable extra debug information to identify troubles
|
||||
with ABI symbols found at the local machine that
|
||||
weren't found on ABI documentation (used only for
|
||||
undefined subcommand)
|
||||
16 - enable debug for what to regex conversion
|
||||
32 - enable debug for symbol regex subgroups
|
||||
64 - enable debug for sysfs graph tree variable
|
||||
"""
|
||||
|
378
scripts/lib/abi/system_symbols.py
Normal file
378
scripts/lib/abi/system_symbols.py
Normal file
@ -0,0 +1,378 @@
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=R0902,R0912,R0914,R0915,R1702
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
Parse ABI documentation and produce results from it.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from concurrent import futures
|
||||
from datetime import datetime
|
||||
from random import shuffle
|
||||
|
||||
from helpers import AbiDebug
|
||||
|
||||
class SystemSymbols:
|
||||
"""Stores arguments for the class and initialize class vars"""
|
||||
|
||||
def graph_add_file(self, path, link=None):
|
||||
"""
|
||||
add a file path to the sysfs graph stored at self.root
|
||||
"""
|
||||
|
||||
if path in self.files:
|
||||
return
|
||||
|
||||
name = ""
|
||||
ref = self.root
|
||||
for edge in path.split("/"):
|
||||
name += edge + "/"
|
||||
if edge not in ref:
|
||||
ref[edge] = {"__name": [name.rstrip("/")]}
|
||||
|
||||
ref = ref[edge]
|
||||
|
||||
if link and link not in ref["__name"]:
|
||||
ref["__name"].append(link.rstrip("/"))
|
||||
|
||||
self.files.add(path)
|
||||
|
||||
def print_graph(self, root_prefix="", root=None, level=0):
|
||||
"""Prints a reference tree graph using UTF-8 characters"""
|
||||
|
||||
if not root:
|
||||
root = self.root
|
||||
level = 0
|
||||
|
||||
# Prevent endless traverse
|
||||
if level > 5:
|
||||
return
|
||||
|
||||
if level > 0:
|
||||
prefix = "├──"
|
||||
last_prefix = "└──"
|
||||
else:
|
||||
prefix = ""
|
||||
last_prefix = ""
|
||||
|
||||
items = list(root.items())
|
||||
|
||||
names = root.get("__name", [])
|
||||
for k, edge in items:
|
||||
if k == "__name":
|
||||
continue
|
||||
|
||||
if not k:
|
||||
k = "/"
|
||||
|
||||
if len(names) > 1:
|
||||
k += " links: " + ",".join(names[1:])
|
||||
|
||||
if edge == items[-1][1]:
|
||||
print(root_prefix + last_prefix + k)
|
||||
p = root_prefix
|
||||
if level > 0:
|
||||
p += " "
|
||||
self.print_graph(p, edge, level + 1)
|
||||
else:
|
||||
print(root_prefix + prefix + k)
|
||||
p = root_prefix + "│ "
|
||||
self.print_graph(p, edge, level + 1)
|
||||
|
||||
def _walk(self, root):
|
||||
"""
|
||||
Walk through sysfs to get all devnodes that aren't ignored.
|
||||
|
||||
By default, uses /sys as sysfs mounting point. If another
|
||||
directory is used, it replaces them to /sys at the patches.
|
||||
"""
|
||||
|
||||
with os.scandir(root) as obj:
|
||||
for entry in obj:
|
||||
path = os.path.join(root, entry.name)
|
||||
if self.sysfs:
|
||||
p = path.replace(self.sysfs, "/sys", count=1)
|
||||
else:
|
||||
p = path
|
||||
|
||||
if self.re_ignore.search(p):
|
||||
return
|
||||
|
||||
# Handle link first to avoid directory recursion
|
||||
if entry.is_symlink():
|
||||
real = os.path.realpath(path)
|
||||
if not self.sysfs:
|
||||
self.aliases[path] = real
|
||||
else:
|
||||
real = real.replace(self.sysfs, "/sys", count=1)
|
||||
|
||||
# Add absfile location to graph if it doesn't exist
|
||||
if not self.re_ignore.search(real):
|
||||
# Add link to the graph
|
||||
self.graph_add_file(real, p)
|
||||
|
||||
elif entry.is_file():
|
||||
self.graph_add_file(p)
|
||||
|
||||
elif entry.is_dir():
|
||||
self._walk(path)
|
||||
|
||||
def __init__(self, abi, sysfs="/sys", hints=False):
|
||||
"""
|
||||
Initialize internal variables and get a list of all files inside
|
||||
sysfs that can currently be parsed.
|
||||
|
||||
Please notice that there are several entries on sysfs that aren't
|
||||
documented as ABI. Ignore those.
|
||||
|
||||
The real paths will be stored under self.files. Aliases will be
|
||||
stored in separate, as self.aliases.
|
||||
"""
|
||||
|
||||
self.abi = abi
|
||||
self.log = abi.log
|
||||
|
||||
if sysfs != "/sys":
|
||||
self.sysfs = sysfs.rstrip("/")
|
||||
else:
|
||||
self.sysfs = None
|
||||
|
||||
self.hints = hints
|
||||
|
||||
self.root = {}
|
||||
self.aliases = {}
|
||||
self.files = set()
|
||||
|
||||
dont_walk = [
|
||||
# Those require root access and aren't documented at ABI
|
||||
f"^{sysfs}/kernel/debug",
|
||||
f"^{sysfs}/kernel/tracing",
|
||||
f"^{sysfs}/fs/pstore",
|
||||
f"^{sysfs}/fs/bpf",
|
||||
f"^{sysfs}/fs/fuse",
|
||||
|
||||
# This is not documented at ABI
|
||||
f"^{sysfs}/module",
|
||||
|
||||
f"^{sysfs}/fs/cgroup", # this is big and has zero docs under ABI
|
||||
f"^{sysfs}/firmware", # documented elsewhere: ACPI, DT bindings
|
||||
"sections|notes", # aren't actually part of ABI
|
||||
|
||||
# kernel-parameters.txt - not easy to parse
|
||||
"parameters",
|
||||
]
|
||||
|
||||
self.re_ignore = re.compile("|".join(dont_walk))
|
||||
|
||||
print(f"Reading {sysfs} directory contents...", file=sys.stderr)
|
||||
self._walk(sysfs)
|
||||
|
||||
def check_file(self, refs, found):
|
||||
"""Check missing ABI symbols for a given sysfs file"""
|
||||
|
||||
res_list = []
|
||||
|
||||
try:
|
||||
for names in refs:
|
||||
fname = names[0]
|
||||
|
||||
res = {
|
||||
"found": False,
|
||||
"fname": fname,
|
||||
"msg": "",
|
||||
}
|
||||
res_list.append(res)
|
||||
|
||||
re_what = self.abi.get_regexes(fname)
|
||||
if not re_what:
|
||||
self.abi.log.warning(f"missing rules for {fname}")
|
||||
continue
|
||||
|
||||
for name in names:
|
||||
for r in re_what:
|
||||
if self.abi.debug & AbiDebug.UNDEFINED:
|
||||
self.log.debug("check if %s matches '%s'", name, r.pattern)
|
||||
if r.match(name):
|
||||
res["found"] = True
|
||||
if found:
|
||||
res["msg"] += f" {fname}: regex:\n\t"
|
||||
continue
|
||||
|
||||
if self.hints and not res["found"]:
|
||||
res["msg"] += f" {fname} not found. Tested regexes:\n"
|
||||
for r in re_what:
|
||||
res["msg"] += " " + r.pattern + "\n"
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
return res_list
|
||||
|
||||
def _ref_interactor(self, root):
|
||||
"""Recursive function to interact over the sysfs tree"""
|
||||
|
||||
for k, v in root.items():
|
||||
if isinstance(v, dict):
|
||||
yield from self._ref_interactor(v)
|
||||
|
||||
if root == self.root or k == "__name":
|
||||
continue
|
||||
|
||||
if self.abi.re_string:
|
||||
fname = v["__name"][0]
|
||||
if self.abi.re_string.search(fname):
|
||||
yield v
|
||||
else:
|
||||
yield v
|
||||
|
||||
|
||||
def get_fileref(self, all_refs, chunk_size):
|
||||
"""Interactor to group refs into chunks"""
|
||||
|
||||
n = 0
|
||||
refs = []
|
||||
|
||||
for ref in all_refs:
|
||||
refs.append(ref)
|
||||
|
||||
n += 1
|
||||
if n >= chunk_size:
|
||||
yield refs
|
||||
n = 0
|
||||
refs = []
|
||||
|
||||
yield refs
|
||||
|
||||
def check_undefined_symbols(self, max_workers=None, chunk_size=50,
|
||||
found=None, dry_run=None):
|
||||
"""Seach ABI for sysfs symbols missing documentation"""
|
||||
|
||||
self.abi.parse_abi()
|
||||
|
||||
if self.abi.debug & AbiDebug.GRAPH:
|
||||
self.print_graph()
|
||||
|
||||
all_refs = []
|
||||
for ref in self._ref_interactor(self.root):
|
||||
all_refs.append(ref["__name"])
|
||||
|
||||
if dry_run:
|
||||
print("Would check", file=sys.stderr)
|
||||
for ref in all_refs:
|
||||
print(", ".join(ref))
|
||||
|
||||
return
|
||||
|
||||
print("Starting to search symbols (it may take several minutes):",
|
||||
file=sys.stderr)
|
||||
start = datetime.now()
|
||||
old_elapsed = None
|
||||
|
||||
# Python doesn't support multithreading due to limitations on its
|
||||
# global lock (GIL). While Python 3.13 finally made GIL optional,
|
||||
# there are still issues related to it. Also, we want to have
|
||||
# backward compatibility with older versions of Python.
|
||||
#
|
||||
# So, use instead multiprocess. However, Python is very slow passing
|
||||
# data from/to multiple processes. Also, it may consume lots of memory
|
||||
# if the data to be shared is not small. So, we need to group workload
|
||||
# in chunks that are big enough to generate performance gains while
|
||||
# not being so big that would cause out-of-memory.
|
||||
|
||||
num_refs = len(all_refs)
|
||||
print(f"Number of references to parse: {num_refs}", file=sys.stderr)
|
||||
|
||||
if not max_workers:
|
||||
max_workers = os.cpu_count()
|
||||
elif max_workers > os.cpu_count():
|
||||
max_workers = os.cpu_count()
|
||||
|
||||
max_workers = max(max_workers, 1)
|
||||
|
||||
max_chunk_size = int((num_refs + max_workers - 1) / max_workers)
|
||||
chunk_size = min(chunk_size, max_chunk_size)
|
||||
chunk_size = max(1, chunk_size)
|
||||
|
||||
if max_workers > 1:
|
||||
executor = futures.ProcessPoolExecutor
|
||||
|
||||
# Place references in a random order. This may help improving
|
||||
# performance, by mixing complex/simple expressions when creating
|
||||
# chunks
|
||||
shuffle(all_refs)
|
||||
else:
|
||||
# Python has a high overhead with processes. When there's just
|
||||
# one worker, it is faster to not create a new process.
|
||||
# Yet, User still deserves to have a progress print. So, use
|
||||
# python's "thread", which is actually a single process, using
|
||||
# an internal schedule to switch between tasks. No performance
|
||||
# gains for non-IO tasks, but still it can be quickly interrupted
|
||||
# from time to time to display progress.
|
||||
executor = futures.ThreadPoolExecutor
|
||||
|
||||
not_found = []
|
||||
f_list = []
|
||||
with executor(max_workers=max_workers) as exe:
|
||||
for refs in self.get_fileref(all_refs, chunk_size):
|
||||
if refs:
|
||||
try:
|
||||
f_list.append(exe.submit(self.check_file, refs, found))
|
||||
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
total = len(f_list)
|
||||
|
||||
if not total:
|
||||
if self.abi.re_string:
|
||||
print(f"No ABI symbol matches {self.abi.search_string}")
|
||||
else:
|
||||
self.abi.log.warning("No ABI symbols found")
|
||||
return
|
||||
|
||||
print(f"{len(f_list):6d} jobs queued on {max_workers} workers",
|
||||
file=sys.stderr)
|
||||
|
||||
while f_list:
|
||||
try:
|
||||
t = futures.wait(f_list, timeout=1,
|
||||
return_when=futures.FIRST_COMPLETED)
|
||||
|
||||
done = t[0]
|
||||
|
||||
for fut in done:
|
||||
res_list = fut.result()
|
||||
|
||||
for res in res_list:
|
||||
if not res["found"]:
|
||||
not_found.append(res["fname"])
|
||||
if res["msg"]:
|
||||
print(res["msg"])
|
||||
|
||||
f_list.remove(fut)
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
except RuntimeError as e:
|
||||
self.abi.log.warning(f"Future: {e}")
|
||||
break
|
||||
|
||||
if sys.stderr.isatty():
|
||||
elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
|
||||
if len(f_list) < total:
|
||||
elapsed += f" ({total - len(f_list)}/{total} jobs completed). "
|
||||
if elapsed != old_elapsed:
|
||||
print(elapsed + "\r", end="", flush=True,
|
||||
file=sys.stderr)
|
||||
old_elapsed = elapsed
|
||||
|
||||
elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
|
||||
print(elapsed, file=sys.stderr)
|
||||
|
||||
for f in sorted(not_found):
|
||||
print(f"{f} not found.")
|
Loading…
x
Reference in New Issue
Block a user