fix(scan): two-phase progress + hosts_processed; clarify status at 99% SNMP tail
Made-with: Cursor
This commit is contained in:
+57
-17
@@ -33,6 +33,24 @@ func NewRunner(store Store, cfg RunnerConfig) *Runner {
|
||||
return &Runner{store: store, cfg: cfg}
|
||||
}
|
||||
|
||||
// scanProgressBlend: часть прогресса за ping/порты (быстро), основная — за полное завершение хоста (SNMP и т.д.).
|
||||
// Пока не все хосты завершены полностью, процент не поднимается выше 99 — чтобы не казалось, что скан «завис на 100%».
|
||||
func scanProgressBlend(quickDone, fullDone, total int) int {
|
||||
if total <= 0 {
|
||||
return 100
|
||||
}
|
||||
if fullDone >= total {
|
||||
return 100
|
||||
}
|
||||
// Округление к ближайшему проценту, иначе 15*256+85*253 даёт 98 при ожидании «почти 99».
|
||||
num := 15*quickDone + 85*fullDone
|
||||
p := (num + total/2) / total
|
||||
if p >= 99 {
|
||||
return 99
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (r *Runner) Start(job ScanJob) {
|
||||
go r.run(job)
|
||||
}
|
||||
@@ -64,7 +82,7 @@ func (r *Runner) run(job ScanJob) {
|
||||
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
||||
Status: "done",
|
||||
Progress: 100,
|
||||
Stats: ScanStats{HostsTotal: 0, HostsUp: 0},
|
||||
Stats: ScanStats{},
|
||||
FinishedAt: &finished,
|
||||
})
|
||||
return
|
||||
@@ -76,11 +94,29 @@ func (r *Runner) run(job ScanJob) {
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
var done int
|
||||
var quickDone int // ping (+ TCP-порты)
|
||||
var fullDone int // полностью, включая SNMP
|
||||
var up int
|
||||
workCh := make(chan string)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
pushProgress := func() {
|
||||
mu.Lock()
|
||||
q, f, u := quickDone, fullDone, up
|
||||
mu.Unlock()
|
||||
prog := scanProgressBlend(q, f, total)
|
||||
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
||||
Status: "running",
|
||||
Progress: prog,
|
||||
Stats: ScanStats{
|
||||
HostsTotal: total,
|
||||
HostsUp: u,
|
||||
HostsPingPhaseDone: q,
|
||||
HostsProcessed: f,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
worker := func() {
|
||||
defer wg.Done()
|
||||
for ip := range workCh {
|
||||
@@ -101,6 +137,15 @@ func (r *Runner) run(job ScanJob) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if isUp {
|
||||
up++
|
||||
}
|
||||
quickDone++
|
||||
mu.Unlock()
|
||||
pushProgress()
|
||||
|
||||
if isUp && r.cfg.SNMPEnabled {
|
||||
snmpRes, lldpRes, ifRes, portDevRes := probeSNMPAndLLDP(ip, r.cfg.SNMPCommunity, checkedAt, job.Options.PingTimeoutMS)
|
||||
_ = r.store.SaveSNMPResult(job.ID, snmpRes)
|
||||
@@ -122,19 +167,9 @@ func (r *Runner) run(job ScanJob) {
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
done++
|
||||
if isUp {
|
||||
up++
|
||||
}
|
||||
progress := done * 100 / total
|
||||
stats := ScanStats{HostsTotal: total, HostsUp: up}
|
||||
fullDone++
|
||||
mu.Unlock()
|
||||
|
||||
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
||||
Status: "running",
|
||||
Progress: progress,
|
||||
Stats: stats,
|
||||
})
|
||||
pushProgress()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,9 +186,14 @@ func (r *Runner) run(job ScanJob) {
|
||||
|
||||
finished := time.Now().UTC()
|
||||
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
||||
Status: "done",
|
||||
Progress: 100,
|
||||
Stats: ScanStats{HostsTotal: total, HostsUp: up},
|
||||
Status: "done",
|
||||
Progress: 100,
|
||||
Stats: ScanStats{
|
||||
HostsTotal: total,
|
||||
HostsUp: up,
|
||||
HostsPingPhaseDone: total,
|
||||
HostsProcessed: total,
|
||||
},
|
||||
FinishedAt: &finished,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package scans
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestScanProgressBlend(t *testing.T) {
|
||||
const total = 256
|
||||
if g := scanProgressBlend(0, 0, total); g != 0 {
|
||||
t.Fatalf("0,0 -> 0 got %d", g)
|
||||
}
|
||||
if g := scanProgressBlend(total, total-1, total); g != 99 {
|
||||
t.Fatalf("quick=total full=total-1 -> 99 got %d", g)
|
||||
}
|
||||
if g := scanProgressBlend(total, total, total); g != 100 {
|
||||
t.Fatalf("complete -> 100 got %d", g)
|
||||
}
|
||||
// типичный «застряли на последних SNMP»: ping по всем, SNMP не догнан
|
||||
if g := scanProgressBlend(256, 253, total); g != 99 {
|
||||
t.Fatalf("256,253 -> 99 got %d", g)
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ type ScanJob struct {
|
||||
type ScanStats struct {
|
||||
HostsTotal int `json:"hosts_total"`
|
||||
HostsUp int `json:"hosts_up"`
|
||||
// HostsPingPhaseDone — завершены ping и (если включено) проверка TCP-портов; SNMP ещё может идти.
|
||||
HostsPingPhaseDone int `json:"hosts_ping_phase_done"`
|
||||
// HostsProcessed — хост полностью обработан (включая SNMP/LLDP/FDB при необходимости).
|
||||
HostsProcessed int `json:"hosts_processed"`
|
||||
}
|
||||
|
||||
type ScanUpdate struct {
|
||||
|
||||
@@ -273,7 +273,18 @@
|
||||
return `Задача ${id}: в очереди, подготовка списка адресов…`;
|
||||
}
|
||||
if (st === "running") {
|
||||
return `Скан ${id}: выполняется обход сети — обработано ~${p}% адресов из ${t}; отвечают на ping (уже проверены): ${up}. Для каждого отвечающего: порты → SNMP → LLDP.`;
|
||||
const pingDone = job.stats?.hosts_ping_phase_done;
|
||||
const proc = job.stats?.hosts_processed;
|
||||
let phase = "";
|
||||
if (t > 0 && (pingDone != null || proc != null)) {
|
||||
const pd = pingDone != null ? pingDone : "?";
|
||||
const pr = proc != null ? proc : "?";
|
||||
phase = ` Этапы: ping+порты ${pd}/${t} адр.; полностью (SNMP/LLDP/FDB) ${pr}/${t}.`;
|
||||
if (proc != null && proc < t) {
|
||||
phase += ` Пока ${pr} < ${t} — идёт тяжёлый SNMP на оставшихся (часто коммутаторы с большим FDB), это нормально и может занять минуты.`;
|
||||
}
|
||||
}
|
||||
return `Скан ${id}: обход сети — прогресс ~${p}% (из ${t} адресов); отвечают на ping: ${up}.${phase} Для отвечающих: порты → SNMP → LLDP.`;
|
||||
}
|
||||
if (st === "done") {
|
||||
return `Скан ${id}: завершён. Всего адресов в задании: ${t}, ответили на ping: ${up}.`;
|
||||
|
||||
Reference in New Issue
Block a user