watchd Monitoring Daemon
A compact version of the capstone: it polls a directory on a ticker and counts changes, serves live stats over a Unix domain socket, reloads on SIGHUP, and shuts down cleanly on SIGTERM. The demo creates a file, queries the socket, reloads, and stops itself.
package main
import (
"bufio"
"context"
"fmt"
"net"
"os"
"os/signal"
"path/filepath"
"sync/atomic"
"syscall"
"time"
)
func main() {
dir, _ := os.MkdirTemp("", "watchd")
defer os.RemoveAll(dir)
sock := filepath.Join(dir, "watchd.sock")
var changes atomic.Int64
start := time.Now()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
// Watcher: poll the directory, count added or resized files against a snapshot.
go func() {
seen := map[string]int64{}
t := time.NewTicker(100 * time.Millisecond)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
now := map[string]int64{}
entries, _ := os.ReadDir(dir)
for _, e := range entries {
info, err := e.Info()
if err != nil {
continue
}
now[e.Name()] = info.Size()
if seen[e.Name()] != info.Size() {
changes.Add(1)
}
}
seen = now
}
}
}()
// Stats server: each connection gets one line of live stats.
ln, err := net.Listen("unix", sock)
if err != nil {
fmt.Println("listen:", err)
return
}
defer ln.Close()
go func() {
<-ctx.Done()
ln.Close() // unblock Accept on shutdown
}()
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
fmt.Fprintf(conn, "uptime=%s changes=%d\n",
time.Since(start).Round(time.Millisecond), changes.Load())
conn.Close()
}
}()
// SIGHUP: reload config without restarting.
hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP)
go func() {
for range hup {
fmt.Println("SIGHUP: config reloaded")
}
}()
fmt.Println("watchd running on", sock)
// Demo driver: create a file, read the stats, reload, then shut down.
go func() {
time.Sleep(250 * time.Millisecond)
os.WriteFile(filepath.Join(dir, "new.txt"), []byte("hi"), 0644)
time.Sleep(250 * time.Millisecond)
if c, err := net.Dial("unix", sock); err == nil {
line, _ := bufio.NewReader(c).ReadString('\n')
fmt.Print("stats: ", line) // stats: uptime=... changes=1
c.Close()
}
syscall.Kill(os.Getpid(), syscall.SIGHUP)
time.Sleep(100 * time.Millisecond)
syscall.Kill(os.Getpid(), syscall.SIGTERM)
}()
<-ctx.Done()
fmt.Println("watchd shutting down")
}Save the code as main.go and run it:
go run main.goExpected output:
watchd running on /tmp/watchd2959957259/watchd.sock
stats: uptime=503ms changes=1
SIGHUP: config reloaded
watchd shutting downThe socket path and uptime vary, and the whole demo finishes in under a second. To run it as a real daemon, delete the demo-driver goroutine; then drop files into the printed directory, query it with nc -U <socket-path>, reload with kill -HUP <pid>, and stop with kill -TERM <pid>.