Skip to content

Commit 31f8e59

Browse files
committed
perf(update): use single global executor for full cross-category parallelism
Replace per-category sequential ThreadPoolExecutor with a single global executor for all regular tools. Previously each category was processed sequentially with its own executor, creating an unnecessary bottleneck where slow tools in one category blocked all subsequent categories. Also simplify guide.sh tool sorting to plain alphabetical within groups, replacing the complex runtime-first/multi-version-descending sort. Changes: - audit.py: single ThreadPoolExecutor for all regular tools - audit.py: add category label [cat] to progress output - audit.py: add grouped summary with per-category status counts - audit.py: handle CONFLICT/UNKNOWN statuses in summary - audit.py: guard against empty regular_tools (max_workers=0) - audit.py: fix KeyboardInterrupt to cancel futures before with-block exit - audit.py: remove redundant ToolCatalog construction in result loop - guide.sh: replace sort_tools_runtime_first with sort_tools_by_name Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
1 parent 817d235 commit 31f8e59

2 files changed

Lines changed: 117 additions & 132 deletions

File tree

audit.py

Lines changed: 111 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -551,107 +551,128 @@ def cmd_update(args: argparse.Namespace) -> int:
551551
# Total includes both regular tools and multi-version entries
552552
total = len(regular_tools) + len(multi_version_tools)
553553

554-
# Parallel audit with progress tracking, grouped by category
554+
# Parallel audit with progress tracking
555+
# All tools submitted to a single executor for maximum parallelism
556+
# (no per-category sequential bottleneck)
555557
results = []
556558
completed = 0
557559

560+
# ANSI colors for all platforms
561+
GREEN = "\033[32m"
562+
BOLD_GREEN = "\033[1;32m"
563+
YELLOW = "\033[33m"
564+
BLUE = "\033[34m"
565+
RESET = "\033[0m"
566+
558567
try:
568+
if regular_tools:
569+
with ThreadPoolExecutor(max_workers=min(MAX_WORKERS, len(regular_tools))) as executor:
570+
future_to_tool = {executor.submit(audit_tool, tool, None): tool for tool in regular_tools}
571+
572+
try:
573+
for future in as_completed(future_to_tool):
574+
tool = future_to_tool[future]
575+
try:
576+
result = future.result()
577+
results.append(result)
578+
completed += 1
579+
580+
# Progress
581+
inst = result.get("installed", "")
582+
latest = result.get("latest_upstream", "")
583+
status = result.get("status", "")
584+
cat = tool.category or "general"
585+
586+
# Color the installed version based on status
587+
if status == "UP-TO-DATE":
588+
inst_color = GREEN
589+
latest_color = GREEN
590+
op = "==="
591+
elif status == "OUTDATED":
592+
inst_color = YELLOW
593+
latest_color = BOLD_GREEN
594+
op = "!=="
595+
elif status == "CONFLICT":
596+
inst_color = YELLOW
597+
latest_color = BOLD_GREEN
598+
op = "⚠️"
599+
else: # NOT INSTALLED, UNKNOWN
600+
inst_color = BLUE
601+
latest_color = BLUE
602+
op = "?"
603+
604+
inst_display = inst if inst else "n/a"
605+
latest_display = latest if latest else "n/a"
606+
607+
# Add pinned/skip markers (reuse catalog from outer scope)
608+
markers = []
609+
if catalog.is_pinned(tool.name):
610+
markers.append("PINNED")
611+
if catalog.should_skip(tool.name, latest):
612+
markers.append("SKIP")
613+
614+
marker_str = f" [{' '.join(markers)}]" if markers else ""
615+
inst_fmt = f"{inst_color}{inst_display}{RESET}"
616+
latest_fmt = f"{latest_color}{latest_display}{RESET}"
617+
msg = f"# [{completed}/{total}] [{cat}] {tool.name} (installed: {inst_fmt} {op} latest: {latest_fmt}){marker_str}"
618+
619+
print(msg, file=sys.stderr, flush=True)
620+
621+
except Exception as e:
622+
completed += 1
623+
print(f"# [{completed}/{total}] {tool.name} (failed: {e})", file=sys.stderr, flush=True)
624+
625+
# Add failure entry
626+
results.append({
627+
"tool": tool.name,
628+
"category": tool.category,
629+
"installed": "",
630+
"installed_method": "",
631+
"installed_version": "",
632+
"installed_path_selected": "",
633+
"classification_reason_selected": "Detection failed",
634+
"latest_upstream": "",
635+
"latest_version": "",
636+
"upstream_method": tool.source_kind,
637+
"status": "UNKNOWN",
638+
"tool_url": tool_homepage_url(tool),
639+
"latest_url": "",
640+
"hint": "",
641+
})
642+
except KeyboardInterrupt:
643+
executor.shutdown(wait=False, cancel_futures=True)
644+
raise
645+
646+
# Print grouped summary
647+
print(f"\n# Summary by category:", file=sys.stderr)
559648
for category in sorted_cats:
560649
cat_tools = categorized[category]
561650
icon = CATEGORY_ICON.get(category, "📦")
562651
desc = CATEGORY_DESC.get(category, category)
563-
print(f"\n# {icon} {desc} ({len(cat_tools)} tools)", file=sys.stderr)
564-
565-
with ThreadPoolExecutor(max_workers=min(MAX_WORKERS, len(cat_tools))) as executor:
566-
future_to_tool = {executor.submit(audit_tool, tool, None): tool for tool in cat_tools}
567-
568-
for future in as_completed(future_to_tool):
569-
tool = future_to_tool[future]
570-
try:
571-
result = future.result()
572-
results.append(result)
573-
completed += 1
574-
575-
# Progress
576-
inst = result.get("installed", "")
577-
latest = result.get("latest_upstream", "")
578-
status = result.get("status", "")
579-
580-
# ANSI colors for all platforms
581-
GREEN = "\033[32m"
582-
BOLD_GREEN = "\033[1;32m"
583-
YELLOW = "\033[33m"
584-
BLUE = "\033[34m"
585-
RESET = "\033[0m"
586-
587-
# Color the installed version based on status
588-
if status == "UP-TO-DATE":
589-
inst_color = GREEN
590-
latest_color = GREEN
591-
op = "==="
592-
elif status == "OUTDATED":
593-
inst_color = YELLOW
594-
latest_color = BOLD_GREEN # Latest is newer, make it bold green
595-
op = "!=="
596-
elif status == "CONFLICT":
597-
inst_color = YELLOW
598-
latest_color = BOLD_GREEN
599-
op = "⚠️"
600-
else: # NOT INSTALLED, UNKNOWN
601-
inst_color = BLUE # Blue for not-installed
602-
latest_color = BLUE
603-
op = "?"
604-
605-
inst_display = inst if inst else "n/a"
606-
latest_display = latest if latest else "n/a"
607-
608-
# Add pinned/skip markers
609-
from cli_audit.catalog import ToolCatalog
610-
catalog = ToolCatalog()
611-
markers = []
612-
if catalog.is_pinned(tool.name):
613-
markers.append("PINNED")
614-
if catalog.should_skip(tool.name, latest):
615-
markers.append("SKIP")
616-
617-
marker_str = f" [{' '.join(markers)}]" if markers else ""
618-
inst_fmt = f"{inst_color}{inst_display}{RESET}"
619-
latest_fmt = f"{latest_color}{latest_display}{RESET}"
620-
msg = f"# [{completed}/{total}] {tool.name} (installed: {inst_fmt} {op} latest: {latest_fmt}){marker_str}"
621-
622-
print(msg, file=sys.stderr, flush=True)
623-
624-
except Exception as e:
625-
completed += 1
626-
print(f"# [{completed}/{total}] {tool.name} (failed: {e})", file=sys.stderr, flush=True)
627-
628-
# Add failure entry
629-
results.append({
630-
"tool": tool.name,
631-
"category": tool.category,
632-
"installed": "",
633-
"installed_method": "",
634-
"installed_version": "",
635-
"installed_path_selected": "",
636-
"classification_reason_selected": "Detection failed",
637-
"latest_upstream": "",
638-
"latest_version": "",
639-
"upstream_method": tool.source_kind,
640-
"status": "UNKNOWN",
641-
"tool_url": tool_homepage_url(tool),
642-
"latest_url": "",
643-
"hint": "",
644-
})
652+
# Count statuses for this category
653+
cat_names = {t.name for t in cat_tools}
654+
cat_results = [r for r in results if r.get("tool") in cat_names]
655+
up_to_date = sum(1 for r in cat_results if r.get("status") == "UP-TO-DATE")
656+
outdated = sum(1 for r in cat_results if r.get("status") == "OUTDATED")
657+
not_installed = sum(1 for r in cat_results if r.get("status") == "NOT INSTALLED")
658+
conflict = sum(1 for r in cat_results if r.get("status") == "CONFLICT")
659+
unknown = sum(1 for r in cat_results if r.get("status") == "UNKNOWN")
660+
parts = []
661+
if up_to_date:
662+
parts.append(f"{GREEN}{up_to_date} current{RESET}")
663+
if outdated:
664+
parts.append(f"{YELLOW}{outdated} outdated{RESET}")
665+
if not_installed:
666+
parts.append(f"{BLUE}{not_installed} missing{RESET}")
667+
if conflict:
668+
parts.append(f"{YELLOW}{conflict} conflict{RESET}")
669+
if unknown:
670+
parts.append(f"{BLUE}{unknown} unknown{RESET}")
671+
summary = ", ".join(parts) if parts else "–"
672+
print(f"# {icon} {desc}: {summary}", file=sys.stderr)
645673

646674
# Audit multi-version runtimes
647675
if multi_version_tools:
648-
# ANSI colors
649-
GREEN = "\033[32m"
650-
BOLD_GREEN = "\033[1;32m"
651-
YELLOW = "\033[33m"
652-
BLUE = "\033[34m"
653-
RESET = "\033[0m"
654-
655676
print(f"\n# 🔄 Multi-version runtimes ({len(multi_version_tools)} runtimes)", file=sys.stderr)
656677

657678
for tool_name, (catalog_data, mv_config) in multi_version_tools.items():
@@ -686,8 +707,6 @@ def cmd_update(args: argparse.Namespace) -> int:
686707
print(f"# → {versioned_name}: {inst_fmt} {op} {latest_fmt}", file=sys.stderr, flush=True)
687708

688709
except KeyboardInterrupt:
689-
# Shutdown executor immediately without waiting for threads
690-
executor.shutdown(wait=False, cancel_futures=True)
691710
print("\n\n✗ Interrupted", file=sys.stderr)
692711
# Reset terminal state before exiting
693712
print("\033[0m", end="", file=sys.stderr, flush=True)

scripts/guide.sh

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -799,55 +799,21 @@ category_matches_filter() {
799799
return 1
800800
}
801801

802-
# Helper: sort tools with runtime first, then multi-version, then others
803-
# Priority: base runtime (0) < multi-version (1-99 by version desc) < other tools (100)
804-
sort_tools_runtime_first() {
802+
# Helper: sort tools alphabetically by name within a category
803+
sort_tools_by_name() {
805804
local tools="$1"
806-
local category="$2"
807-
808-
# Runtimes that should appear first in their category
809-
local runtimes="python node php go ruby rust"
810-
811805
for tool in $tools; do
812-
local sort_key="999" # Default: other tools sort last
813-
814-
# Check if tool is a base runtime
815-
for rt in $runtimes; do
816-
if [ "$tool" = "$rt" ]; then
817-
sort_key="000"
818-
break
819-
fi
820-
done
821-
822-
# Check if tool is a multi-version runtime (e.g., python@3.14)
823-
if [ "$sort_key" = "999" ] && [[ "$tool" == *"@"* ]]; then
824-
local base="${tool%%@*}"
825-
local version="${tool##*@}"
826-
for rt in $runtimes; do
827-
if [ "$base" = "$rt" ]; then
828-
# Sort by version descending (higher versions first)
829-
# Convert version to sortable number (3.14 -> 986, 3.13 -> 987)
830-
local major="${version%%.*}"
831-
local minor="${version#*.}"
832-
minor="${minor%%.*}"
833-
# Invert so higher versions sort first
834-
sort_key=$(printf "%03d" $((999 - major * 10 - minor)))
835-
break
836-
fi
837-
done
838-
fi
839-
840-
echo "$sort_key $tool"
841-
done | sort | awk '{print $2}'
806+
echo "$tool"
807+
done | sort
842808
}
843809

844810
# Process tools grouped by category (in category order)
845811
for category in $(printf '%s\n' "${!CATEGORY_TOOLS[@]}" | while read c; do echo "${CATEGORY_ORDER[$c]:-99} $c"; done | sort -n | awk '{print $2}'); do
846812
tools="${CATEGORY_TOOLS[$category]}"
847813
[ -z "$tools" ] && continue
848814

849-
# Sort tools: runtime first, then multi-version (desc), then others
850-
tools="$(sort_tools_runtime_first "$tools" "$category")"
815+
# Sort tools alphabetically by name
816+
tools="$(sort_tools_by_name "$tools")"
851817

852818
# Skip if category doesn't match filter
853819
if ! category_matches_filter "$category"; then

0 commit comments

Comments
 (0)