diff --git a/cmd/discosrv/main.go b/cmd/discosrv/main.go index c0131247..013a602a 100644 --- a/cmd/discosrv/main.go +++ b/cmd/discosrv/main.go @@ -21,6 +21,7 @@ var ( limitBurst = 20 dbConn = getEnvDefault("DISCOSRV_DB", "postgres://user:password@localhost/discosrv") globalStats stats + statsFile string ) func main() { @@ -38,6 +39,7 @@ func main() { flag.IntVar(&lruSize, "limit-cache", lruSize, "Limiter cache entries") flag.IntVar(&limitAvg, "limit-avg", limitAvg, "Allowed average package rate, per 10 s") flag.IntVar(&limitBurst, "limit-burst", limitBurst, "Allowed burst size, packets") + flag.StringVar(&statsFile, "stats-file", statsFile, "File to write periodic operation stats to") flag.Parse() addr, _ := net.ResolveUDPAddr("udp", listen) @@ -74,6 +76,8 @@ func main() { main.Add(&statssrv{ intv: statsIntv, + file: statsFile, + db: db, }) globalStats.Reset() diff --git a/cmd/discosrv/stats.go b/cmd/discosrv/stats.go index c0e1029b..1a014091 100644 --- a/cmd/discosrv/stats.go +++ b/cmd/discosrv/stats.go @@ -3,7 +3,12 @@ package main import ( + "bytes" + "database/sql" + "fmt" + "io/ioutil" "log" + "os" "sync" "time" ) @@ -52,6 +57,8 @@ func (s *stats) Reset() stats { type statssrv struct { intv time.Duration + file string + db *sql.DB } func (s *statssrv) Serve() { @@ -59,12 +66,71 @@ func (s *statssrv) Serve() { time.Sleep(next(s.intv)) stats := globalStats.Reset() - s := time.Since(stats.reset).Seconds() + d := time.Since(stats.reset).Seconds() log.Printf("Stats: %.02f announces/s, %.02f queries/s, %.02f answers/s, %.02f errors/s", - float64(stats.announces)/s, float64(stats.queries)/s, float64(stats.answers)/s, float64(stats.errors)/s) + float64(stats.announces)/d, float64(stats.queries)/d, float64(stats.answers)/d, float64(stats.errors)/d) + + if s.file != "" { + s.writeToFile(stats, d) + } } } func (s *statssrv) Stop() { panic("stop unimplemented") } + +func (s *statssrv) writeToFile(stats stats, secs float64) { + newLine := []byte("\n") + + var addrs int + row := s.db.QueryRow("SELECT COUNT(*) FROM Addresses") + if err := row.Scan(&addrs); err != nil { + log.Println("stats query:", err) + return + } + + fd, err := os.OpenFile(s.file, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + log.Println("stats file:", err) + return + } + + bs, err := ioutil.ReadAll(fd) + if err != nil { + log.Println("stats file:", err) + return + } + lines := bytes.Split(bytes.TrimSpace(bs), newLine) + if len(lines) > 12 { + lines = lines[len(lines)-12:] + } + + latest := fmt.Sprintf("%v: %6d addresses, %8.02f announces/s, %8.02f queries/s, %8.02f answers/s, %8.02f errors/s\n", + time.Now().UTC().Format(time.RFC3339), addrs, + float64(stats.announces)/secs, float64(stats.queries)/secs, float64(stats.answers)/secs, float64(stats.errors)/secs) + lines = append(lines, []byte(latest)) + + _, err = fd.Seek(0, 0) + if err != nil { + log.Println("stats file:", err) + return + } + err = fd.Truncate(0) + if err != nil { + log.Println("stats file:", err) + return + } + + _, err = fd.Write(bytes.Join(lines, newLine)) + if err != nil { + log.Println("stats file:", err) + return + } + + err = fd.Close() + if err != nil { + log.Println("stats file:", err) + return + } +}