diff options
| author | 2026-07-06 13:02:54 +0200 | |
|---|---|---|
| committer | 2026-07-09 09:59:28 +0200 | |
| commit | 874866185ed05ae837a8852e44ac6ac8f253c3bc (patch) | |
| tree | 0c57276eb5a45a22b55f4581ba522600a5f0491a /src/main/java/com/it_jaros/jscanner/ProgressBar.java | |
| parent | d2ab45e7ecdfee71806eb0388527cd50ba96975d (diff) | |
Completetly reworked progress output. Now instead of individual hosts
give a statusline that prints current stats to err
Diffstat (limited to 'src/main/java/com/it_jaros/jscanner/ProgressBar.java')
| -rw-r--r-- | src/main/java/com/it_jaros/jscanner/ProgressBar.java | 175 |
1 files changed, 104 insertions, 71 deletions
diff --git a/src/main/java/com/it_jaros/jscanner/ProgressBar.java b/src/main/java/com/it_jaros/jscanner/ProgressBar.java index 12a359c..d1679c4 100644 --- a/src/main/java/com/it_jaros/jscanner/ProgressBar.java +++ b/src/main/java/com/it_jaros/jscanner/ProgressBar.java @@ -2,29 +2,75 @@ package com.it_jaros.jscanner; import java.io.BufferedReader; import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; +import java.util.BitSet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class ProgressBar { - private final List<Progress> hosts = new ArrayList<>(); - private final ReentrantLock lock = new ReentrantLock(); private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor(); + private final Scan scan; private final boolean quiet; private final int columnWidth = ProgressBar.getColumnWidth(); + private final Object outputLock = new Object(); - public ProgressBar(boolean quiet) { + public ProgressBar(Scan scan, boolean quiet) { + this.scan = scan; this.quiet = quiet; } + private void clearStatusLine() { + System.err.print("\r\033[2K"); + System.err.flush(); + } + + public void printResult(ScanResult result, boolean showFilteredPorts) { + synchronized (outputLock) { + if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) { + return; + } + + clearStatusLine(); + printPortsResults(result, showFilteredPorts); + } + } + + private void printPortsResults(ScanResult result, boolean showFilteredPorts) { + if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) { + return; + } + + System.out.println(result.host()); + if (!result.openPorts().isEmpty()) { + System.out.printf("\t (%d) open:\t%s%n", result.openPorts().cardinality(), map(result.openPorts(), result.bannerRecognition())); + } + if (showFilteredPorts && !result.filteredPorts().isEmpty()) { + System.out.printf("\t (%d) filtered:\t%s%n", result.filteredPorts().cardinality(), map(result.filteredPorts())); + } + System.out.println(); + System.out.flush(); + } + + private static String map(BitSet ports) { + return ports.stream().mapToObj(String::valueOf).collect(Collectors.joining(",")); + } + + private static String map(BitSet ports, java.util.HashMap<Integer, ServiceType> bannerRecognition) { + return ports.stream().mapToObj(port -> { + ServiceType serviceType = bannerRecognition.getOrDefault(port, ServiceType.UNKNOWN); + + if (serviceType == ServiceType.UNKNOWN) { + return String.valueOf(port); + } + + return port + "/" + serviceType.name().toLowerCase(); + }).collect(Collectors.joining(",")); + } + private static int getColumnWidth() { try { Process process = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").start(); @@ -47,86 +93,73 @@ public class ProgressBar { } public void start() { - if (!quiet) { - System.out.print("Scanning targets...\n\n"); + if (quiet) { + return; } // although we don't print anything, we still need to // gc all the done hosts + System.err.print("Scanning targets...\n\n"); ui.scheduleAtFixedRate(() -> { - lock.lock(); - drawProgress(false); - lock.unlock(); - }, 1, 500, TimeUnit.MILLISECONDS); - } - - private void drawProgress(boolean last) { - // sort -> the more progressed the host the more up it is - hosts.sort(Comparator.comparingInt((Progress p) -> p.done().get()).reversed()); - - // find and print finished hosts one last time - for (Iterator<Progress> it = hosts.iterator(); it.hasNext(); ) { - Progress p = it.next(); - if (p.done().get() == p.total()) { - it.remove(); - drawBar(p); - } else { - break; + synchronized (outputLock) { + printStatusLine(scan); } - } - - for (Progress progress : hosts) { - drawBar(progress); - } + }, 1, 1000, TimeUnit.MILLISECONDS); + } - if (!last) { - // move cursor up again - if (!quiet) { - System.out.print("\u001b[" + hosts.size() + "A"); - } + public void stop() { + ui.shutdownNow(); + try { + ui.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); } + printStats(); } - public void add(Progress progress) { - lock.lock(); - hosts.add(progress); - lock.unlock(); + private void printStats() { + System.out.println("\n--------------------"); + System.out.println("Stats:"); + System.out.println("Total hosts scanned: " + scan.getHostTotalCounter().max()); + System.out.println("Peak concurrent connects: " + scan.getSocketCounter().max()); + System.out.println("Peak concurrent threads: " + scan.getThreadCounter().max()); + System.out.println("--------------------\n"); } - private void drawBar(Progress progress) { - if (quiet) return; - System.out.print("\u001b[2K\r"); // clear whole line - System.out.println(createBar( - progress.host(), - progress.open().get(), - progress.filtered().get(), - progress.done().get(), - progress.total())); + private void printStatusLine(final Scan scan) { + this.printStatusLine(scan.getHostCounter(), scan.getHostTotalCounter(), scan.getSocketCounter(), scan.getThreadCounter()); } - public String createBar(String label, int open, int filtered, int done, int total) { - int terminalWidth = columnWidth; - int bracketWidth = terminalWidth / 3; - double pct = total == 0 ? 1.0 : (done / (double) total); - int percent = (int) (pct * 100); + private void printStatusLine( + Counter hostCounter, Counter hostTotalCounter, Counter + socketCounter, Counter + threadCounter) { + int doneHosts = hostTotalCounter.current(); + int runningHosts = hostCounter.current(); + int total = doneHosts + runningHosts; + long percent = total == 0 ? 100 : doneHosts * 100L / total; - String statsLeft = String.format("open:%d filtered:%d (%d/%d)", open, filtered, done, total); - String statsRight = String.format("%3d%%", percent); - int filled = (int) (pct * bracketWidth); - String brackets = "[" + "#".repeat(filled) + "-".repeat(bracketWidth - filled) + "]"; + String line = String.format( + "hosts: %d running, %d done | sockets: %d active, %d peak | workers: %d active, %d peak", + runningHosts, + doneHosts, + socketCounter.current(), + socketCounter.max(), + threadCounter.current(), + threadCounter.max() + ); - int spacesBetween = terminalWidth - bracketWidth - (label + statsLeft + statsRight).length() - 5; - return String.format("%s%s%s %s %s", label, " ".repeat(spacesBetween), statsLeft, brackets, statsRight); + String barFormat = " | [%s] %3d%%"; + int barWidth = Math.max(5, this.columnWidth - line.length() - barFormat.length()); + String progressBar = progressBar(percent, barWidth); + String status = line + String.format(barFormat, progressBar, percent); + System.err.print("\r" + status); + System.err.flush(); } - public void stop() { - ui.shutdown(); - try { - ui.awaitTermination(1, TimeUnit.SECONDS); - // we need to draw one last time for the 100% to appear - drawProgress(true); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } + private static String progressBar(long percent, int width) { + int filled = (int) (percent * width / 100); + + return "#".repeat(filled) + "-".repeat(width - filled); } } |
