add garbage collection for stale routes
This commit is contained in:
parent
193dc695d1
commit
3d1a843b64
139
main.go
139
main.go
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"container/heap"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -30,11 +31,14 @@ var (
|
||||||
|
|
||||||
type route struct {
|
type route struct {
|
||||||
Dst netaddr.IPPrefix
|
Dst netaddr.IPPrefix
|
||||||
Counter uint
|
|
||||||
UnreachableDuration time.Duration
|
UnreachableDuration time.Duration
|
||||||
UnreachableSince time.Time
|
UnreachableSince time.Time
|
||||||
ReachableSince time.Time
|
ReachableSince time.Time
|
||||||
|
Counter uint
|
||||||
Hostname string
|
Hostname string
|
||||||
|
|
||||||
|
LastUpdate time.Time
|
||||||
|
gqIdx int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r route) DurationUntilRounded(t time.Time) time.Duration {
|
func (r route) DurationUntilRounded(t time.Time) time.Duration {
|
||||||
|
@ -59,20 +63,59 @@ func (r route) Noise(now time.Time) bool {
|
||||||
r.Downtime(now) < 0.5
|
r.Downtime(now) < 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r route) LastUpdateUntil(t time.Time) time.Duration {
|
||||||
|
return t.Sub(r.LastUpdate).Round(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
func (r route) String() string {
|
func (r route) String() string {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return fmt.Sprintf("%44s %5d %2.3f%% %s", r.Dst, r.Counter, r.Downtime(now), r.DurationUntil(now))
|
return fmt.Sprintf("%44s %5d %2.3f%% %s", r.Dst, r.Counter, r.Downtime(now), r.DurationUntil(now))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gcQueue []*route
|
||||||
|
|
||||||
|
var _ heap.Interface = &gcQueue{}
|
||||||
|
|
||||||
|
func (gcq gcQueue) Len() int {
|
||||||
|
return len(gcq)
|
||||||
|
}
|
||||||
|
func (gcq gcQueue) Less(i, j int) bool {
|
||||||
|
return gcq[i].LastUpdate.Before(gcq[j].LastUpdate)
|
||||||
|
}
|
||||||
|
func (gcq gcQueue) Swap(i, j int) {
|
||||||
|
gcq[i], gcq[j] = gcq[j], gcq[i]
|
||||||
|
gcq[i].gqIdx = i
|
||||||
|
gcq[j].gqIdx = j
|
||||||
|
}
|
||||||
|
func (gcq *gcQueue) Push(x interface{}) {
|
||||||
|
r := x.(*route)
|
||||||
|
r.gqIdx = len(*gcq)
|
||||||
|
*gcq = append(*gcq, r)
|
||||||
|
}
|
||||||
|
func (gcq *gcQueue) Pop() interface{} {
|
||||||
|
last := len(*gcq) - 1
|
||||||
|
ret := (*gcq)[last]
|
||||||
|
ret.gqIdx = -1
|
||||||
|
(*gcq)[last] = nil
|
||||||
|
*gcq = (*gcq)[:last]
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
type routeStats struct {
|
type routeStats struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
stats map[netaddr.IPPrefix]*route
|
stats map[netaddr.IPPrefix]*route
|
||||||
|
gcstats map[netaddr.IPPrefix]*route
|
||||||
|
gq gcQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRouteStats() *routeStats {
|
func newRouteStats() *routeStats {
|
||||||
return &routeStats{
|
rs := &routeStats{
|
||||||
stats: make(map[netaddr.IPPrefix]*route),
|
stats: make(map[netaddr.IPPrefix]*route),
|
||||||
|
gcstats: make(map[netaddr.IPPrefix]*route),
|
||||||
|
gq: gcQueue{},
|
||||||
}
|
}
|
||||||
|
heap.Init(&rs.gq)
|
||||||
|
return rs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *routeStats) add(prefix netaddr.IPPrefix) {
|
func (rs *routeStats) add(prefix netaddr.IPPrefix) {
|
||||||
|
@ -80,6 +123,8 @@ func (rs *routeStats) add(prefix netaddr.IPPrefix) {
|
||||||
defer rs.Unlock()
|
defer rs.Unlock()
|
||||||
|
|
||||||
r := rs.stats[prefix]
|
r := rs.stats[prefix]
|
||||||
|
if r == nil {
|
||||||
|
r = rs.gcstats[prefix]
|
||||||
if r == nil {
|
if r == nil {
|
||||||
r = &route{Dst: prefix}
|
r = &route{Dst: prefix}
|
||||||
go func(r *route, rs *routeStats) {
|
go func(r *route, rs *routeStats) {
|
||||||
|
@ -94,14 +139,28 @@ func (rs *routeStats) add(prefix netaddr.IPPrefix) {
|
||||||
r.Hostname = strings.TrimRight(names[0], ".")
|
r.Hostname = strings.TrimRight(names[0], ".")
|
||||||
rs.Unlock()
|
rs.Unlock()
|
||||||
}(r, rs)
|
}(r, rs)
|
||||||
|
} else {
|
||||||
|
delete(rs.gcstats, r.Dst)
|
||||||
|
r.LastUpdate = time.Time{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
if r.UnreachableSince.IsZero() {
|
if r.UnreachableSince.IsZero() {
|
||||||
r.UnreachableSince = time.Now()
|
r.UnreachableSince = now
|
||||||
r.ReachableSince = time.Time{}
|
r.ReachableSince = time.Time{}
|
||||||
r.Counter++
|
r.Counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldLastUpdate := r.LastUpdate
|
||||||
|
r.LastUpdate = now
|
||||||
|
|
||||||
|
if oldLastUpdate.IsZero() {
|
||||||
|
heap.Push(&rs.gq, r)
|
||||||
|
} else {
|
||||||
|
heap.Fix(&rs.gq, r.gqIdx)
|
||||||
|
}
|
||||||
|
|
||||||
rs.stats[prefix] = r
|
rs.stats[prefix] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,15 +173,53 @@ func (rs *routeStats) del(prefix netaddr.IPPrefix) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
if !r.UnreachableSince.IsZero() {
|
if !r.UnreachableSince.IsZero() {
|
||||||
r.UnreachableDuration += time.Since(r.UnreachableSince)
|
r.UnreachableDuration += time.Since(r.UnreachableSince)
|
||||||
r.UnreachableSince = time.Time{}
|
r.UnreachableSince = time.Time{}
|
||||||
r.ReachableSince = time.Now()
|
r.ReachableSince = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.LastUpdate = now
|
||||||
|
heap.Fix(&rs.gq, r.gqIdx)
|
||||||
|
|
||||||
rs.stats[prefix] = r
|
rs.stats[prefix] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs *routeStats) runGC() {
|
||||||
|
rs.Lock()
|
||||||
|
defer rs.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for len(rs.gq) > 0 {
|
||||||
|
first := rs.gq[0]
|
||||||
|
if first.LastUpdate.Add(3 * time.Minute).Before(now) {
|
||||||
|
heap.Pop(&rs.gq)
|
||||||
|
delete(rs.stats, first.Dst)
|
||||||
|
rs.gcstats[first.Dst] = first
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *routeStats) getCollected() []route {
|
||||||
|
ret := make([]route, 0, len(rs.gcstats))
|
||||||
|
|
||||||
|
rs.Lock()
|
||||||
|
defer rs.Unlock()
|
||||||
|
|
||||||
|
for _, v := range rs.gcstats {
|
||||||
|
ret = append(ret, *v)
|
||||||
|
}
|
||||||
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
return ret[i].LastUpdate.Before(ret[j].LastUpdate)
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func (rs *routeStats) getAll() []route {
|
func (rs *routeStats) getAll() []route {
|
||||||
ret := make([]route, 0, len(rs.stats))
|
ret := make([]route, 0, len(rs.stats))
|
||||||
|
|
||||||
|
@ -182,13 +279,14 @@ func monitor(done <-chan struct{}, rs *routeStats) error {
|
||||||
} else {
|
} else {
|
||||||
rs.del(t.Prefix)
|
rs.del(t.Prefix)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
rs.runGC()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(w io.Writer, rs []route) error {
|
func render(w io.Writer, rs []route, gc []route) error {
|
||||||
const tmplHTML = `<!doctype html>
|
const tmplHTML = `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -251,6 +349,7 @@ func render(w io.Writer, rs []route) error {
|
||||||
<th>Net</th>
|
<th>Net</th>
|
||||||
<th>Hostname</th>
|
<th>Hostname</th>
|
||||||
<th colspan="3">Downtime</th>
|
<th colspan="3">Downtime</th>
|
||||||
|
<th>Last</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
@ -258,10 +357,11 @@ func render(w io.Writer, rs []route) error {
|
||||||
<th>Counts</th>
|
<th>Counts</th>
|
||||||
<th>rel</th>
|
<th>rel</th>
|
||||||
<th>abs</th>
|
<th>abs</th>
|
||||||
|
<th>Update</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td colspan="5"><hr/></td></tr>
|
<tr><td colspan="6"><hr/></td></tr>
|
||||||
{{- $now := .Now -}}
|
{{- $now := .Now -}}
|
||||||
{{- range .R }}
|
{{- range .R }}
|
||||||
<tr class="
|
<tr class="
|
||||||
|
@ -273,9 +373,28 @@ func render(w io.Writer, rs []route) error {
|
||||||
<td class="counter">{{.Counter}}</td>
|
<td class="counter">{{.Counter}}</td>
|
||||||
<td class="downtime">{{.Downtime $now | printf "%.3f%%"}}</td>
|
<td class="downtime">{{.Downtime $now | printf "%.3f%%"}}</td>
|
||||||
<td class="duration">{{.DurationUntilRounded $now}}</td>
|
<td class="duration">{{.DurationUntilRounded $now}}</td>
|
||||||
|
<td class="duration">{{.LastUpdateUntil $now}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{{with .GC}}
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="6"> </td></tr>
|
||||||
|
</tbody>
|
||||||
|
<thead>
|
||||||
|
<tr><th colspan="6">Garbage Collected</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="6"><hr/></td></tr>
|
||||||
|
{{- range . }}
|
||||||
|
<tr>
|
||||||
|
<td class="dst">{{.Dst.String}}</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td colspan="2">{{.LastUpdate.Format "2006-01-02 15:04:05"}}</td>
|
||||||
|
</tr>
|
||||||
|
{{- end}}
|
||||||
|
</tbody>
|
||||||
|
{{- end}}
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -285,10 +404,12 @@ func render(w io.Writer, rs []route) error {
|
||||||
Now time.Time
|
Now time.Time
|
||||||
Start time.Time
|
Start time.Time
|
||||||
R []route
|
R []route
|
||||||
|
GC []route
|
||||||
}{
|
}{
|
||||||
time.Now(),
|
time.Now(),
|
||||||
startTime,
|
startTime,
|
||||||
rs,
|
rs,
|
||||||
|
gc,
|
||||||
}
|
}
|
||||||
t := template.Must(template.New("main").Parse(tmplHTML))
|
t := template.Must(template.New("main").Parse(tmplHTML))
|
||||||
return t.Execute(w, data)
|
return t.Execute(w, data)
|
||||||
|
@ -347,7 +468,7 @@ func cachedRender(rs *routeStats) http.HandlerFunc {
|
||||||
content.Reset()
|
content.Reset()
|
||||||
compressed.Reset()
|
compressed.Reset()
|
||||||
|
|
||||||
err = render(&content, rs.getLongest())
|
err = render(&content, rs.getLongest(), rs.getCollected())
|
||||||
err = compress(&compressed, bytes.NewReader(content.Bytes()), err)
|
err = compress(&compressed, bytes.NewReader(content.Bytes()), err)
|
||||||
|
|
||||||
timestamp = time.Now()
|
timestamp = time.Now()
|
||||||
|
|
Loading…
Reference in New Issue