xref: /linux/tools/lib/python/abi/system_symbols.py (revision 72c395024dac5e215136cbff793455f065603b06)
10d5fd968SMauro Carvalho Chehab#!/usr/bin/env python3
20d5fd968SMauro Carvalho Chehab# pylint: disable=R0902,R0912,R0914,R0915,R1702
30d5fd968SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
40d5fd968SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
50d5fd968SMauro Carvalho Chehab
60d5fd968SMauro Carvalho Chehab"""
70d5fd968SMauro Carvalho ChehabParse ABI documentation and produce results from it.
80d5fd968SMauro Carvalho Chehab"""
90d5fd968SMauro Carvalho Chehab
100d5fd968SMauro Carvalho Chehabimport os
110d5fd968SMauro Carvalho Chehabimport re
120d5fd968SMauro Carvalho Chehabimport sys
130d5fd968SMauro Carvalho Chehab
140d5fd968SMauro Carvalho Chehabfrom concurrent import futures
150d5fd968SMauro Carvalho Chehabfrom datetime import datetime
160d5fd968SMauro Carvalho Chehabfrom random import shuffle
170d5fd968SMauro Carvalho Chehab
18992a9df4SJonathan Corbetfrom abi.helpers import AbiDebug
190d5fd968SMauro Carvalho Chehab
200d5fd968SMauro Carvalho Chehabclass SystemSymbols:
21*a50c62d3SMauro Carvalho Chehab    """Stores arguments for the class and initialize class vars."""
220d5fd968SMauro Carvalho Chehab
230d5fd968SMauro Carvalho Chehab    def graph_add_file(self, path, link=None):
240d5fd968SMauro Carvalho Chehab        """
25*a50c62d3SMauro Carvalho Chehab        add a file path to the sysfs graph stored at self.root.
260d5fd968SMauro Carvalho Chehab        """
270d5fd968SMauro Carvalho Chehab
280d5fd968SMauro Carvalho Chehab        if path in self.files:
290d5fd968SMauro Carvalho Chehab            return
300d5fd968SMauro Carvalho Chehab
310d5fd968SMauro Carvalho Chehab        name = ""
320d5fd968SMauro Carvalho Chehab        ref = self.root
330d5fd968SMauro Carvalho Chehab        for edge in path.split("/"):
340d5fd968SMauro Carvalho Chehab            name += edge + "/"
350d5fd968SMauro Carvalho Chehab            if edge not in ref:
360d5fd968SMauro Carvalho Chehab                ref[edge] = {"__name": [name.rstrip("/")]}
370d5fd968SMauro Carvalho Chehab
380d5fd968SMauro Carvalho Chehab            ref = ref[edge]
390d5fd968SMauro Carvalho Chehab
400d5fd968SMauro Carvalho Chehab        if link and link not in ref["__name"]:
410d5fd968SMauro Carvalho Chehab            ref["__name"].append(link.rstrip("/"))
420d5fd968SMauro Carvalho Chehab
430d5fd968SMauro Carvalho Chehab        self.files.add(path)
440d5fd968SMauro Carvalho Chehab
450d5fd968SMauro Carvalho Chehab    def print_graph(self, root_prefix="", root=None, level=0):
46*a50c62d3SMauro Carvalho Chehab        """Prints a reference tree graph using UTF-8 characters."""
470d5fd968SMauro Carvalho Chehab
480d5fd968SMauro Carvalho Chehab        if not root:
490d5fd968SMauro Carvalho Chehab            root = self.root
500d5fd968SMauro Carvalho Chehab            level = 0
510d5fd968SMauro Carvalho Chehab
520d5fd968SMauro Carvalho Chehab        # Prevent endless traverse
530d5fd968SMauro Carvalho Chehab        if level > 5:
540d5fd968SMauro Carvalho Chehab            return
550d5fd968SMauro Carvalho Chehab
560d5fd968SMauro Carvalho Chehab        if level > 0:
570d5fd968SMauro Carvalho Chehab            prefix = "├──"
580d5fd968SMauro Carvalho Chehab            last_prefix = "└──"
590d5fd968SMauro Carvalho Chehab        else:
600d5fd968SMauro Carvalho Chehab            prefix = ""
610d5fd968SMauro Carvalho Chehab            last_prefix = ""
620d5fd968SMauro Carvalho Chehab
630d5fd968SMauro Carvalho Chehab        items = list(root.items())
640d5fd968SMauro Carvalho Chehab
650d5fd968SMauro Carvalho Chehab        names = root.get("__name", [])
660d5fd968SMauro Carvalho Chehab        for k, edge in items:
670d5fd968SMauro Carvalho Chehab            if k == "__name":
680d5fd968SMauro Carvalho Chehab                continue
690d5fd968SMauro Carvalho Chehab
700d5fd968SMauro Carvalho Chehab            if not k:
710d5fd968SMauro Carvalho Chehab                k = "/"
720d5fd968SMauro Carvalho Chehab
730d5fd968SMauro Carvalho Chehab            if len(names) > 1:
740d5fd968SMauro Carvalho Chehab                k += " links: " + ",".join(names[1:])
750d5fd968SMauro Carvalho Chehab
760d5fd968SMauro Carvalho Chehab            if edge == items[-1][1]:
770d5fd968SMauro Carvalho Chehab                print(root_prefix + last_prefix + k)
780d5fd968SMauro Carvalho Chehab                p = root_prefix
790d5fd968SMauro Carvalho Chehab                if level > 0:
800d5fd968SMauro Carvalho Chehab                    p += "   "
810d5fd968SMauro Carvalho Chehab                self.print_graph(p, edge, level + 1)
820d5fd968SMauro Carvalho Chehab            else:
830d5fd968SMauro Carvalho Chehab                print(root_prefix + prefix + k)
840d5fd968SMauro Carvalho Chehab                p = root_prefix + "│   "
850d5fd968SMauro Carvalho Chehab                self.print_graph(p, edge, level + 1)
860d5fd968SMauro Carvalho Chehab
870d5fd968SMauro Carvalho Chehab    def _walk(self, root):
880d5fd968SMauro Carvalho Chehab        """
890d5fd968SMauro Carvalho Chehab        Walk through sysfs to get all devnodes that aren't ignored.
900d5fd968SMauro Carvalho Chehab
910d5fd968SMauro Carvalho Chehab        By default, uses /sys as sysfs mounting point. If another
920d5fd968SMauro Carvalho Chehab        directory is used, it replaces them to /sys at the patches.
930d5fd968SMauro Carvalho Chehab        """
940d5fd968SMauro Carvalho Chehab
950d5fd968SMauro Carvalho Chehab        with os.scandir(root) as obj:
960d5fd968SMauro Carvalho Chehab            for entry in obj:
970d5fd968SMauro Carvalho Chehab                path = os.path.join(root, entry.name)
980d5fd968SMauro Carvalho Chehab                if self.sysfs:
990d5fd968SMauro Carvalho Chehab                    p = path.replace(self.sysfs, "/sys", count=1)
1000d5fd968SMauro Carvalho Chehab                else:
1010d5fd968SMauro Carvalho Chehab                    p = path
1020d5fd968SMauro Carvalho Chehab
1030d5fd968SMauro Carvalho Chehab                if self.re_ignore.search(p):
1040d5fd968SMauro Carvalho Chehab                    return
1050d5fd968SMauro Carvalho Chehab
1060d5fd968SMauro Carvalho Chehab                # Handle link first to avoid directory recursion
1070d5fd968SMauro Carvalho Chehab                if entry.is_symlink():
1080d5fd968SMauro Carvalho Chehab                    real = os.path.realpath(path)
1090d5fd968SMauro Carvalho Chehab                    if not self.sysfs:
1100d5fd968SMauro Carvalho Chehab                        self.aliases[path] = real
1110d5fd968SMauro Carvalho Chehab                    else:
1120d5fd968SMauro Carvalho Chehab                        real = real.replace(self.sysfs, "/sys", count=1)
1130d5fd968SMauro Carvalho Chehab
1140d5fd968SMauro Carvalho Chehab                    # Add absfile location to graph if it doesn't exist
1150d5fd968SMauro Carvalho Chehab                    if not self.re_ignore.search(real):
1160d5fd968SMauro Carvalho Chehab                        # Add link to the graph
1170d5fd968SMauro Carvalho Chehab                        self.graph_add_file(real, p)
1180d5fd968SMauro Carvalho Chehab
1190d5fd968SMauro Carvalho Chehab                elif entry.is_file():
1200d5fd968SMauro Carvalho Chehab                    self.graph_add_file(p)
1210d5fd968SMauro Carvalho Chehab
1220d5fd968SMauro Carvalho Chehab                elif entry.is_dir():
1230d5fd968SMauro Carvalho Chehab                    self._walk(path)
1240d5fd968SMauro Carvalho Chehab
1250d5fd968SMauro Carvalho Chehab    def __init__(self, abi, sysfs="/sys", hints=False):
1260d5fd968SMauro Carvalho Chehab        """
1270d5fd968SMauro Carvalho Chehab        Initialize internal variables and get a list of all files inside
1280d5fd968SMauro Carvalho Chehab        sysfs that can currently be parsed.
1290d5fd968SMauro Carvalho Chehab
1300d5fd968SMauro Carvalho Chehab        Please notice that there are several entries on sysfs that aren't
1310d5fd968SMauro Carvalho Chehab        documented as ABI. Ignore those.
1320d5fd968SMauro Carvalho Chehab
1330d5fd968SMauro Carvalho Chehab        The real paths will be stored under self.files. Aliases will be
1340d5fd968SMauro Carvalho Chehab        stored in separate, as self.aliases.
1350d5fd968SMauro Carvalho Chehab        """
1360d5fd968SMauro Carvalho Chehab
1370d5fd968SMauro Carvalho Chehab        self.abi = abi
1380d5fd968SMauro Carvalho Chehab        self.log = abi.log
1390d5fd968SMauro Carvalho Chehab
1400d5fd968SMauro Carvalho Chehab        if sysfs != "/sys":
1410d5fd968SMauro Carvalho Chehab            self.sysfs = sysfs.rstrip("/")
1420d5fd968SMauro Carvalho Chehab        else:
1430d5fd968SMauro Carvalho Chehab            self.sysfs = None
1440d5fd968SMauro Carvalho Chehab
1450d5fd968SMauro Carvalho Chehab        self.hints = hints
1460d5fd968SMauro Carvalho Chehab
1470d5fd968SMauro Carvalho Chehab        self.root = {}
1480d5fd968SMauro Carvalho Chehab        self.aliases = {}
1490d5fd968SMauro Carvalho Chehab        self.files = set()
1500d5fd968SMauro Carvalho Chehab
1510d5fd968SMauro Carvalho Chehab        dont_walk = [
1520d5fd968SMauro Carvalho Chehab            # Those require root access and aren't documented at ABI
1530d5fd968SMauro Carvalho Chehab            f"^{sysfs}/kernel/debug",
1540d5fd968SMauro Carvalho Chehab            f"^{sysfs}/kernel/tracing",
1550d5fd968SMauro Carvalho Chehab            f"^{sysfs}/fs/pstore",
1560d5fd968SMauro Carvalho Chehab            f"^{sysfs}/fs/bpf",
1570d5fd968SMauro Carvalho Chehab            f"^{sysfs}/fs/fuse",
1580d5fd968SMauro Carvalho Chehab
1590d5fd968SMauro Carvalho Chehab            # This is not documented at ABI
1600d5fd968SMauro Carvalho Chehab            f"^{sysfs}/module",
1610d5fd968SMauro Carvalho Chehab
1620d5fd968SMauro Carvalho Chehab            f"^{sysfs}/fs/cgroup",  # this is big and has zero docs under ABI
1630d5fd968SMauro Carvalho Chehab            f"^{sysfs}/firmware",   # documented elsewhere: ACPI, DT bindings
1640d5fd968SMauro Carvalho Chehab            "sections|notes",       # aren't actually part of ABI
1650d5fd968SMauro Carvalho Chehab
1660d5fd968SMauro Carvalho Chehab            # kernel-parameters.txt - not easy to parse
1670d5fd968SMauro Carvalho Chehab            "parameters",
1680d5fd968SMauro Carvalho Chehab        ]
1690d5fd968SMauro Carvalho Chehab
1700d5fd968SMauro Carvalho Chehab        self.re_ignore = re.compile("|".join(dont_walk))
1710d5fd968SMauro Carvalho Chehab
1720d5fd968SMauro Carvalho Chehab        print(f"Reading {sysfs} directory contents...", file=sys.stderr)
1730d5fd968SMauro Carvalho Chehab        self._walk(sysfs)
1740d5fd968SMauro Carvalho Chehab
1750d5fd968SMauro Carvalho Chehab    def check_file(self, refs, found):
176*a50c62d3SMauro Carvalho Chehab        """Check missing ABI symbols for a given sysfs file."""
1770d5fd968SMauro Carvalho Chehab
1780d5fd968SMauro Carvalho Chehab        res_list = []
1790d5fd968SMauro Carvalho Chehab
1800d5fd968SMauro Carvalho Chehab        try:
1810d5fd968SMauro Carvalho Chehab            for names in refs:
1820d5fd968SMauro Carvalho Chehab                fname = names[0]
1830d5fd968SMauro Carvalho Chehab
1840d5fd968SMauro Carvalho Chehab                res = {
1850d5fd968SMauro Carvalho Chehab                    "found": False,
1860d5fd968SMauro Carvalho Chehab                    "fname": fname,
1870d5fd968SMauro Carvalho Chehab                    "msg": "",
1880d5fd968SMauro Carvalho Chehab                }
1890d5fd968SMauro Carvalho Chehab                res_list.append(res)
1900d5fd968SMauro Carvalho Chehab
1910d5fd968SMauro Carvalho Chehab                re_what = self.abi.get_regexes(fname)
1920d5fd968SMauro Carvalho Chehab                if not re_what:
1930d5fd968SMauro Carvalho Chehab                    self.abi.log.warning(f"missing rules for {fname}")
1940d5fd968SMauro Carvalho Chehab                    continue
1950d5fd968SMauro Carvalho Chehab
1960d5fd968SMauro Carvalho Chehab                for name in names:
1970d5fd968SMauro Carvalho Chehab                    for r in re_what:
1980d5fd968SMauro Carvalho Chehab                        if self.abi.debug & AbiDebug.UNDEFINED:
1990d5fd968SMauro Carvalho Chehab                            self.log.debug("check if %s matches '%s'", name, r.pattern)
2000d5fd968SMauro Carvalho Chehab                        if r.match(name):
2010d5fd968SMauro Carvalho Chehab                            res["found"] = True
2020d5fd968SMauro Carvalho Chehab                            if found:
2030d5fd968SMauro Carvalho Chehab                                res["msg"] += f"  {fname}: regex:\n\t"
2040d5fd968SMauro Carvalho Chehab                            continue
2050d5fd968SMauro Carvalho Chehab
2060d5fd968SMauro Carvalho Chehab                if self.hints and not res["found"]:
2070d5fd968SMauro Carvalho Chehab                    res["msg"] += f"  {fname} not found. Tested regexes:\n"
2080d5fd968SMauro Carvalho Chehab                    for r in re_what:
2090d5fd968SMauro Carvalho Chehab                        res["msg"] += "    " + r.pattern + "\n"
2100d5fd968SMauro Carvalho Chehab
2110d5fd968SMauro Carvalho Chehab        except KeyboardInterrupt:
2120d5fd968SMauro Carvalho Chehab            pass
2130d5fd968SMauro Carvalho Chehab
2140d5fd968SMauro Carvalho Chehab        return res_list
2150d5fd968SMauro Carvalho Chehab
2160d5fd968SMauro Carvalho Chehab    def _ref_interactor(self, root):
217*a50c62d3SMauro Carvalho Chehab        """Recursive function to interact over the sysfs tree."""
2180d5fd968SMauro Carvalho Chehab
2190d5fd968SMauro Carvalho Chehab        for k, v in root.items():
2200d5fd968SMauro Carvalho Chehab            if isinstance(v, dict):
2210d5fd968SMauro Carvalho Chehab                yield from self._ref_interactor(v)
2220d5fd968SMauro Carvalho Chehab
2230d5fd968SMauro Carvalho Chehab            if root == self.root or k == "__name":
2240d5fd968SMauro Carvalho Chehab                continue
2250d5fd968SMauro Carvalho Chehab
2260d5fd968SMauro Carvalho Chehab            if self.abi.re_string:
2270d5fd968SMauro Carvalho Chehab                fname = v["__name"][0]
2280d5fd968SMauro Carvalho Chehab                if self.abi.re_string.search(fname):
2290d5fd968SMauro Carvalho Chehab                    yield v
2300d5fd968SMauro Carvalho Chehab            else:
2310d5fd968SMauro Carvalho Chehab                yield v
2320d5fd968SMauro Carvalho Chehab
2330d5fd968SMauro Carvalho Chehab
2340d5fd968SMauro Carvalho Chehab    def get_fileref(self, all_refs, chunk_size):
235*a50c62d3SMauro Carvalho Chehab        """Interactor to group refs into chunks."""
2360d5fd968SMauro Carvalho Chehab
2370d5fd968SMauro Carvalho Chehab        n = 0
2380d5fd968SMauro Carvalho Chehab        refs = []
2390d5fd968SMauro Carvalho Chehab
2400d5fd968SMauro Carvalho Chehab        for ref in all_refs:
2410d5fd968SMauro Carvalho Chehab            refs.append(ref)
2420d5fd968SMauro Carvalho Chehab
2430d5fd968SMauro Carvalho Chehab            n += 1
2440d5fd968SMauro Carvalho Chehab            if n >= chunk_size:
2450d5fd968SMauro Carvalho Chehab                yield refs
2460d5fd968SMauro Carvalho Chehab                n = 0
2470d5fd968SMauro Carvalho Chehab                refs = []
2480d5fd968SMauro Carvalho Chehab
2490d5fd968SMauro Carvalho Chehab        yield refs
2500d5fd968SMauro Carvalho Chehab
2510d5fd968SMauro Carvalho Chehab    def check_undefined_symbols(self, max_workers=None, chunk_size=50,
2520d5fd968SMauro Carvalho Chehab                                found=None, dry_run=None):
253*a50c62d3SMauro Carvalho Chehab        """Seach ABI for sysfs symbols missing documentation."""
2540d5fd968SMauro Carvalho Chehab
2550d5fd968SMauro Carvalho Chehab        self.abi.parse_abi()
2560d5fd968SMauro Carvalho Chehab
2570d5fd968SMauro Carvalho Chehab        if self.abi.debug & AbiDebug.GRAPH:
2580d5fd968SMauro Carvalho Chehab            self.print_graph()
2590d5fd968SMauro Carvalho Chehab
2600d5fd968SMauro Carvalho Chehab        all_refs = []
2610d5fd968SMauro Carvalho Chehab        for ref in self._ref_interactor(self.root):
2620d5fd968SMauro Carvalho Chehab            all_refs.append(ref["__name"])
2630d5fd968SMauro Carvalho Chehab
2640d5fd968SMauro Carvalho Chehab        if dry_run:
2650d5fd968SMauro Carvalho Chehab            print("Would check", file=sys.stderr)
2660d5fd968SMauro Carvalho Chehab            for ref in all_refs:
2670d5fd968SMauro Carvalho Chehab                print(", ".join(ref))
2680d5fd968SMauro Carvalho Chehab
2690d5fd968SMauro Carvalho Chehab            return
2700d5fd968SMauro Carvalho Chehab
2710d5fd968SMauro Carvalho Chehab        print("Starting to search symbols (it may take several minutes):",
2720d5fd968SMauro Carvalho Chehab              file=sys.stderr)
2730d5fd968SMauro Carvalho Chehab        start = datetime.now()
2740d5fd968SMauro Carvalho Chehab        old_elapsed = None
2750d5fd968SMauro Carvalho Chehab
2760d5fd968SMauro Carvalho Chehab        # Python doesn't support multithreading due to limitations on its
2770d5fd968SMauro Carvalho Chehab        # global lock (GIL). While Python 3.13 finally made GIL optional,
2780d5fd968SMauro Carvalho Chehab        # there are still issues related to it. Also, we want to have
2790d5fd968SMauro Carvalho Chehab        # backward compatibility with older versions of Python.
2800d5fd968SMauro Carvalho Chehab        #
2810d5fd968SMauro Carvalho Chehab        # So, use instead multiprocess. However, Python is very slow passing
2820d5fd968SMauro Carvalho Chehab        # data from/to multiple processes. Also, it may consume lots of memory
2830d5fd968SMauro Carvalho Chehab        # if the data to be shared is not small.  So, we need to group workload
2840d5fd968SMauro Carvalho Chehab        # in chunks that are big enough to generate performance gains while
2850d5fd968SMauro Carvalho Chehab        # not being so big that would cause out-of-memory.
2860d5fd968SMauro Carvalho Chehab
2870d5fd968SMauro Carvalho Chehab        num_refs = len(all_refs)
2880d5fd968SMauro Carvalho Chehab        print(f"Number of references to parse: {num_refs}", file=sys.stderr)
2890d5fd968SMauro Carvalho Chehab
2900d5fd968SMauro Carvalho Chehab        if not max_workers:
2910d5fd968SMauro Carvalho Chehab            max_workers = os.cpu_count()
2920d5fd968SMauro Carvalho Chehab        elif max_workers > os.cpu_count():
2930d5fd968SMauro Carvalho Chehab            max_workers = os.cpu_count()
2940d5fd968SMauro Carvalho Chehab
2950d5fd968SMauro Carvalho Chehab        max_workers = max(max_workers, 1)
2960d5fd968SMauro Carvalho Chehab
2970d5fd968SMauro Carvalho Chehab        max_chunk_size = int((num_refs + max_workers - 1) / max_workers)
2980d5fd968SMauro Carvalho Chehab        chunk_size = min(chunk_size, max_chunk_size)
2990d5fd968SMauro Carvalho Chehab        chunk_size = max(1, chunk_size)
3000d5fd968SMauro Carvalho Chehab
3010d5fd968SMauro Carvalho Chehab        if max_workers > 1:
3020d5fd968SMauro Carvalho Chehab            executor = futures.ProcessPoolExecutor
3030d5fd968SMauro Carvalho Chehab
3040d5fd968SMauro Carvalho Chehab            # Place references in a random order. This may help improving
3050d5fd968SMauro Carvalho Chehab            # performance, by mixing complex/simple expressions when creating
3060d5fd968SMauro Carvalho Chehab            # chunks
3070d5fd968SMauro Carvalho Chehab            shuffle(all_refs)
3080d5fd968SMauro Carvalho Chehab        else:
3090d5fd968SMauro Carvalho Chehab            # Python has a high overhead with processes. When there's just
3100d5fd968SMauro Carvalho Chehab            # one worker, it is faster to not create a new process.
3110d5fd968SMauro Carvalho Chehab            # Yet, User still deserves to have a progress print. So, use
3120d5fd968SMauro Carvalho Chehab            # python's "thread", which is actually a single process, using
3130d5fd968SMauro Carvalho Chehab            # an internal schedule to switch between tasks. No performance
3140d5fd968SMauro Carvalho Chehab            # gains for non-IO tasks, but still it can be quickly interrupted
3150d5fd968SMauro Carvalho Chehab            # from time to time to display progress.
3160d5fd968SMauro Carvalho Chehab            executor = futures.ThreadPoolExecutor
3170d5fd968SMauro Carvalho Chehab
3180d5fd968SMauro Carvalho Chehab        not_found = []
3190d5fd968SMauro Carvalho Chehab        f_list = []
3200d5fd968SMauro Carvalho Chehab        with executor(max_workers=max_workers) as exe:
3210d5fd968SMauro Carvalho Chehab            for refs in self.get_fileref(all_refs, chunk_size):
3220d5fd968SMauro Carvalho Chehab                if refs:
3230d5fd968SMauro Carvalho Chehab                    try:
3240d5fd968SMauro Carvalho Chehab                        f_list.append(exe.submit(self.check_file, refs, found))
3250d5fd968SMauro Carvalho Chehab
3260d5fd968SMauro Carvalho Chehab                    except KeyboardInterrupt:
3270d5fd968SMauro Carvalho Chehab                        return
3280d5fd968SMauro Carvalho Chehab
3290d5fd968SMauro Carvalho Chehab            total = len(f_list)
3300d5fd968SMauro Carvalho Chehab
3310d5fd968SMauro Carvalho Chehab            if not total:
3320d5fd968SMauro Carvalho Chehab                if self.abi.re_string:
3330d5fd968SMauro Carvalho Chehab                    print(f"No ABI symbol matches {self.abi.search_string}")
3340d5fd968SMauro Carvalho Chehab                else:
3350d5fd968SMauro Carvalho Chehab                    self.abi.log.warning("No ABI symbols found")
3360d5fd968SMauro Carvalho Chehab                return
3370d5fd968SMauro Carvalho Chehab
3380d5fd968SMauro Carvalho Chehab            print(f"{len(f_list):6d} jobs queued on {max_workers} workers",
3390d5fd968SMauro Carvalho Chehab                  file=sys.stderr)
3400d5fd968SMauro Carvalho Chehab
3410d5fd968SMauro Carvalho Chehab            while f_list:
3420d5fd968SMauro Carvalho Chehab                try:
3430d5fd968SMauro Carvalho Chehab                    t = futures.wait(f_list, timeout=1,
3440d5fd968SMauro Carvalho Chehab                                     return_when=futures.FIRST_COMPLETED)
3450d5fd968SMauro Carvalho Chehab
3460d5fd968SMauro Carvalho Chehab                    done = t[0]
3470d5fd968SMauro Carvalho Chehab
3480d5fd968SMauro Carvalho Chehab                    for fut in done:
3490d5fd968SMauro Carvalho Chehab                        res_list = fut.result()
3500d5fd968SMauro Carvalho Chehab
3510d5fd968SMauro Carvalho Chehab                        for res in res_list:
3520d5fd968SMauro Carvalho Chehab                            if not res["found"]:
3530d5fd968SMauro Carvalho Chehab                                not_found.append(res["fname"])
3540d5fd968SMauro Carvalho Chehab                            if res["msg"]:
3550d5fd968SMauro Carvalho Chehab                                print(res["msg"])
3560d5fd968SMauro Carvalho Chehab
3570d5fd968SMauro Carvalho Chehab                        f_list.remove(fut)
3580d5fd968SMauro Carvalho Chehab                except KeyboardInterrupt:
3590d5fd968SMauro Carvalho Chehab                    return
3600d5fd968SMauro Carvalho Chehab
3610d5fd968SMauro Carvalho Chehab                except RuntimeError as e:
3620d5fd968SMauro Carvalho Chehab                    self.abi.log.warning(f"Future: {e}")
3630d5fd968SMauro Carvalho Chehab                    break
3640d5fd968SMauro Carvalho Chehab
3650d5fd968SMauro Carvalho Chehab                if sys.stderr.isatty():
3660d5fd968SMauro Carvalho Chehab                    elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
3670d5fd968SMauro Carvalho Chehab                    if len(f_list) < total:
3680d5fd968SMauro Carvalho Chehab                        elapsed += f" ({total - len(f_list)}/{total} jobs completed).  "
3690d5fd968SMauro Carvalho Chehab                    if elapsed != old_elapsed:
3700d5fd968SMauro Carvalho Chehab                        print(elapsed + "\r", end="", flush=True,
3710d5fd968SMauro Carvalho Chehab                              file=sys.stderr)
3720d5fd968SMauro Carvalho Chehab                        old_elapsed = elapsed
3730d5fd968SMauro Carvalho Chehab
3740d5fd968SMauro Carvalho Chehab        elapsed = str(datetime.now() - start).split(".", maxsplit=1)[0]
3750d5fd968SMauro Carvalho Chehab        print(elapsed, file=sys.stderr)
3760d5fd968SMauro Carvalho Chehab
3770d5fd968SMauro Carvalho Chehab        for f in sorted(not_found):
3780d5fd968SMauro Carvalho Chehab            print(f"{f} not found.")
379