From e7c3cf0e69d180d3c268c92a8dbb8ad981a78e66 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:14:40 +0200 Subject: [PATCH 1/2] watch: ignore chmod-only + directory-create events; add repeated-work stats --- internal/watcher/watcher.go | 82 +++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 2ec4f37..564f8a3 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -20,23 +20,47 @@ type Watcher struct { store *store.Store indexer *indexer.Indexer + eventsSeen atomic.Int64 + eventsIgnored atomic.Int64 + eventsIgnoredDir atomic.Int64 + eventsIgnoredChmod atomic.Int64 + flushSignals atomic.Int64 coalescedSignals atomic.Int64 flushRuns atomic.Int64 flushErrors atomic.Int64 - queueExecs atomic.Int64 - queueSkipped atomic.Int64 - queueErrors atomic.Int64 + flushNoop atomic.Int64 + + queueExecs atomic.Int64 + queueSkipped atomic.Int64 + queueErrors atomic.Int64 + + drainRuns atomic.Int64 + drainPaths atomic.Int64 + updateRuns atomic.Int64 + updatePaths atomic.Int64 } type WatchStats struct { + EventsSeen int64 `json:"events_seen"` + EventsIgnored int64 `json:"events_ignored"` + EventsIgnoredDir int64 `json:"events_ignored_dir"` + EventsIgnoredChmod int64 `json:"events_ignored_chmod"` + FlushSignals int64 `json:"flush_signals"` CoalescedSignals int64 `json:"coalesced_signals"` FlushRuns int64 `json:"flush_runs"` FlushErrors int64 `json:"flush_errors"` - QueueExecs int64 `json:"queue_execs"` - QueueSkipped int64 `json:"queue_skipped"` - QueueErrors int64 `json:"queue_errors"` + FlushNoop int64 `json:"flush_noop"` + + DrainRuns int64 `json:"drain_runs"` + DrainPaths int64 `json:"drain_paths"` + UpdateRuns int64 `json:"update_runs"` + UpdatePaths int64 `json:"update_paths"` + + QueueExecs int64 `json:"queue_execs"` + QueueSkipped int64 `json:"queue_skipped"` + QueueErrors int64 `json:"queue_errors"` } func New(s *store.Store, idx *indexer.Indexer) *Watcher { @@ -45,24 +69,45 @@ func New(s *store.Store, idx *indexer.Indexer) *Watcher { func (w *Watcher) Stats() WatchStats { return WatchStats{ + EventsSeen: w.eventsSeen.Load(), + EventsIgnored: w.eventsIgnored.Load(), + EventsIgnoredDir: w.eventsIgnoredDir.Load(), + EventsIgnoredChmod: w.eventsIgnoredChmod.Load(), + FlushSignals: w.flushSignals.Load(), CoalescedSignals: w.coalescedSignals.Load(), FlushRuns: w.flushRuns.Load(), FlushErrors: w.flushErrors.Load(), - QueueExecs: w.queueExecs.Load(), - QueueSkipped: w.queueSkipped.Load(), - QueueErrors: w.queueErrors.Load(), + FlushNoop: w.flushNoop.Load(), + + DrainRuns: w.drainRuns.Load(), + DrainPaths: w.drainPaths.Load(), + UpdateRuns: w.updateRuns.Load(), + UpdatePaths: w.updatePaths.Load(), + + QueueExecs: w.queueExecs.Load(), + QueueSkipped: w.queueSkipped.Load(), + QueueErrors: w.queueErrors.Load(), } } func (w *Watcher) Run(ctx context.Context, repoRoot string, repoID int64, debounce time.Duration) error { + w.eventsSeen.Store(0) + w.eventsIgnored.Store(0) + w.eventsIgnoredDir.Store(0) + w.eventsIgnoredChmod.Store(0) w.flushSignals.Store(0) w.coalescedSignals.Store(0) w.flushRuns.Store(0) w.flushErrors.Store(0) + w.flushNoop.Store(0) w.queueExecs.Store(0) w.queueSkipped.Store(0) w.queueErrors.Store(0) + w.drainRuns.Store(0) + w.drainPaths.Store(0) + w.updateRuns.Store(0) + w.updatePaths.Store(0) if debounce <= 0 { debounce = 750 * time.Millisecond } @@ -100,13 +145,18 @@ func (w *Watcher) Run(ctx context.Context, repoRoot string, repoID int64, deboun } flush := func() error { + w.drainRuns.Add(1) paths, err := w.store.DrainDirtyFiles(ctx, repoID) if err != nil { return err } if len(paths) == 0 { + w.flushNoop.Add(1) return nil } + w.drainPaths.Add(int64(len(paths))) + w.updateRuns.Add(1) + w.updatePaths.Add(int64(len(paths))) _, err = w.indexer.Update(ctx, indexer.Options{ RepoRoot: repoRoot, Paths: paths, @@ -176,12 +226,22 @@ func (w *Watcher) Run(ctx context.Context, repoRoot string, repoID int64, deboun if !ok { return nil } + w.eventsSeen.Add(1) + + // Avoid churn from chmod-only events (common on some editors). + if event.Op == fsnotify.Chmod { + w.eventsIgnored.Add(1) + w.eventsIgnoredChmod.Add(1) + continue + } rel, err := filepath.Rel(repoRoot, event.Name) if err != nil { + w.eventsIgnored.Add(1) continue } rel = filepath.Clean(rel) if shouldIgnorePath(rel) { + w.eventsIgnored.Add(1) continue } if event.Op&fsnotify.Create == fsnotify.Create { @@ -189,6 +249,10 @@ func (w *Watcher) Run(ctx context.Context, repoRoot string, repoID int64, deboun if err := addWatchTree(event.Name); err != nil { return err } + // Directory creates are not indexable file updates. + w.eventsIgnored.Add(1) + w.eventsIgnoredDir.Add(1) + continue } } From 475fd982e6c19eaa015b8aed1f5ddec0199c8782 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:38:41 +0200 Subject: [PATCH 2/2] fix(watcher): count only successful update runs in watcher stats --- internal/watcher/watcher.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 564f8a3..1cbf7de 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -155,13 +155,15 @@ func (w *Watcher) Run(ctx context.Context, repoRoot string, repoID int64, deboun return nil } w.drainPaths.Add(int64(len(paths))) - w.updateRuns.Add(1) - w.updatePaths.Add(int64(len(paths))) _, err = w.indexer.Update(ctx, indexer.Options{ RepoRoot: repoRoot, Paths: paths, ScanKind: "watch", }) + if err == nil { + w.updateRuns.Add(1) + w.updatePaths.Add(int64(len(paths))) + } return err }