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