package main import ( "bytes" "fmt" "html/template" "io" "log" "net" "net/http" "os" "sort" "sync" "time" "golang.org/x/sys/unix" "github.com/vishvananda/netlink" ) var ( startTime = time.Now() ) type route struct { Dst *net.IPNet Counter uint UnreachableDuration time.Duration UnreachableSince *time.Time } func (r route) DurationUntilRounded(t time.Time) time.Duration { return r.DurationUntil(t).Round(time.Second) } func (r route) DurationUntil(t time.Time) time.Duration { ret := r.UnreachableDuration if r.UnreachableSince != nil { ret += t.Sub(*r.UnreachableSince) } return ret } func (r route) Downtime(now time.Time) float64 { return float64(r.DurationUntil(now)) / float64(now.Sub(startTime)) * 100 } func (r route) Noise(now time.Time) bool { return r.Counter <= uint(now.Sub(startTime).Hours()) && r.Downtime(now) < 0.01 } func (r route) String() string { now := time.Now() return fmt.Sprintf("%44s %5d %2.3f%% %s", r.Dst, r.Counter, r.Downtime(now), r.DurationUntil(now)) } type routeStats struct { sync.Mutex stats map[string]*route } func newRouteStats() *routeStats { return &routeStats{ stats: make(map[string]*route), } } func (rs *routeStats) update(ru netlink.RouteUpdate) { rs.Lock() defer rs.Unlock() switch ru.Type { case unix.RTM_NEWROUTE: rs.add(ru) case unix.RTM_DELROUTE: rs.del(ru) default: fmt.Fprintf(os.Stderr, "Unknown route type %d\n", ru.Type) } } func (rs *routeStats) add(ru netlink.RouteUpdate) { key := ru.Route.Dst.String() r := rs.stats[key] if r == nil { r = &route{Dst: ru.Route.Dst} } r.Counter++ if r.UnreachableSince == nil { t := time.Now() r.UnreachableSince = &t } rs.stats[key] = r } func (rs *routeStats) del(ru netlink.RouteUpdate) { key := ru.Route.Dst.String() r := rs.stats[key] if r == nil { r = &route{Dst: ru.Route.Dst} return } r.UnreachableDuration += time.Since(*r.UnreachableSince) r.UnreachableSince = nil rs.stats[key] = r } func (rs *routeStats) getAll() []route { ret := make([]route, 0, len(rs.stats)) rs.Lock() defer rs.Unlock() for _, v := range rs.stats { ret = append(ret, *v) } return ret } func (rs *routeStats) getLongest() []route { ret := rs.getAll() t := time.Now() sort.Slice(ret, func(i, j int) bool { return ret[i].DurationUntil(t) > ret[j].DurationUntil(t) }) return ret } func monitor(done <-chan struct{}, rs *routeStats) { rups := make(chan netlink.RouteUpdate, 64) opts := netlink.RouteSubscribeOptions{ ErrorCallback: func(err error) { fmt.Fprintf(os.Stderr, "%s\n", err, err) }, } if err := netlink.RouteSubscribeWithOptions(rups, done, opts); err != nil { log.Fatal(err) } for ru := range rups { if ru.Route.Type != unix.RTN_UNREACHABLE { continue } // show unly babel routes if ru.Route.Protocol != 42 { continue } rs.update(ru) } } func render(w io.Writer, rs []route) error { const tmplHTML = `
Start: | {{ .Start.Format "2006-01-02 15:04:05" }} |
Update: | {{ .Now.Format "2006-01-02 15:04:05" }} |
Net | Downtime | ||
---|---|---|---|
Counts | rel | abs | |
{{.Dst}} | {{.Counter}} | {{.Downtime $now | printf "%.3f%%"}} | {{.DurationUntilRounded $now}} |