Skip to content

Commit c331ff9

Browse files
authored
Merge pull request #26 from netresearch/perf/global-parallelism-and-sort
perf(update): full cross-category parallelism for version detection
2 parents 817d235 + 31f8e59 commit c331ff9

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)