diff --git a/log/metrics.go b/log/metrics.go new file mode 100644 index 00000000..c16f1e84 --- /dev/null +++ b/log/metrics.go @@ -0,0 +1,62 @@ +package log + +import ( + "container/list" + "expvar" + "strings" + "time" +) + +// TickerFunc is the type of metrics function accepted by AddTickerFunc +type TickerFunc func() + +var tickerFuncChan = make(chan TickerFunc) + +func init() { + go metricsTicker() +} + +// AddTickerFunc adds a new function callback to the list of metrics TickerFuncs that get +// called each minute. +func AddTickerFunc(f TickerFunc) { + tickerFuncChan <- f +} + +// PushMetric adds the metric to the end of the list and returns a comma separated string of the +// previous 61 entries. We return 61 instead of 60 (an hour) because the chart on the client +// tracks deltas between these values - there is nothing to compare the first value against. +func PushMetric(history *list.List, ev expvar.Var) string { + history.PushBack(ev.String()) + if history.Len() > 61 { + history.Remove(history.Front()) + } + return joinStringList(history) +} + +// joinStringList joins a List containing strings by commas +func joinStringList(listOfStrings *list.List) string { + if listOfStrings.Len() == 0 { + return "" + } + s := make([]string, 0, listOfStrings.Len()) + for e := listOfStrings.Front(); e != nil; e = e.Next() { + s = append(s, e.Value.(string)) + } + return strings.Join(s, ",") +} + +func metricsTicker() { + funcs := make([]TickerFunc, 0) + ticker := time.NewTicker(time.Minute) + + for { + select { + case <-ticker.C: + for _, f := range funcs { + f() + } + case f := <-tickerFuncChan: + funcs = append(funcs, f) + } + } +} diff --git a/smtpd/listener.go b/smtpd/listener.go index 8c477a23..06bbf9ce 100644 --- a/smtpd/listener.go +++ b/smtpd/listener.go @@ -16,6 +16,28 @@ import ( "github.com/jhillyerd/inbucket/msghub" ) +func init() { + m := expvar.NewMap("smtp") + m.Set("ConnectsTotal", expConnectsTotal) + m.Set("ConnectsHist", expConnectsHist) + m.Set("ConnectsCurrent", expConnectsCurrent) + m.Set("ReceivedTotal", expReceivedTotal) + m.Set("ReceivedHist", expReceivedHist) + m.Set("ErrorsTotal", expErrorsTotal) + m.Set("ErrorsHist", expErrorsHist) + m.Set("WarnsTotal", expWarnsTotal) + m.Set("WarnsHist", expWarnsHist) + + log.AddTickerFunc(func() { + expReceivedHist.Set(log.PushMetric(deliveredHist, expReceivedTotal)) + expConnectsHist.Set(log.PushMetric(connectsHist, expConnectsTotal)) + expErrorsHist.Set(log.PushMetric(errorsHist, expErrorsTotal)) + expWarnsHist.Set(log.PushMetric(warnsHist, expWarnsTotal)) + expRetentionDeletesHist.Set(log.PushMetric(retentionDeletesHist, expRetentionDeletesTotal)) + expRetainedHist.Set(log.PushMetric(retainedHist, expRetainedCurrent)) + }) +} + // Server holds the configuration and state of our SMTP server type Server struct { // Configuration @@ -179,44 +201,3 @@ func (s *Server) Drain() { log.Tracef("SMTP connections have drained") s.retentionScanner.Join() } - -// When the provided Ticker ticks, we update our metrics history -func metricsTicker(t *time.Ticker) { - ok := true - for ok { - _, ok = <-t.C - expReceivedHist.Set(pushMetric(deliveredHist, expReceivedTotal)) - expConnectsHist.Set(pushMetric(connectsHist, expConnectsTotal)) - expErrorsHist.Set(pushMetric(errorsHist, expErrorsTotal)) - expWarnsHist.Set(pushMetric(warnsHist, expWarnsTotal)) - expRetentionDeletesHist.Set(pushMetric(retentionDeletesHist, expRetentionDeletesTotal)) - expRetainedHist.Set(pushMetric(retainedHist, expRetainedCurrent)) - } -} - -// pushMetric adds the metric to the end of the list and returns a comma separated string of the -// previous 61 entries. We return 61 instead of 60 (an hour) because the chart on the client -// tracks deltas between these values - there is nothing to compare the first value against. -func pushMetric(history *list.List, ev expvar.Var) string { - history.PushBack(ev.String()) - if history.Len() > 61 { - history.Remove(history.Front()) - } - return JoinStringList(history) -} - -func init() { - m := expvar.NewMap("smtp") - m.Set("ConnectsTotal", expConnectsTotal) - m.Set("ConnectsHist", expConnectsHist) - m.Set("ConnectsCurrent", expConnectsCurrent) - m.Set("ReceivedTotal", expReceivedTotal) - m.Set("ReceivedHist", expReceivedHist) - m.Set("ErrorsTotal", expErrorsTotal) - m.Set("ErrorsHist", expErrorsHist) - m.Set("WarnsTotal", expWarnsTotal) - m.Set("WarnsHist", expWarnsHist) - - t := time.NewTicker(time.Minute) - go metricsTicker(t) -} diff --git a/smtpd/utils.go b/smtpd/utils.go index a8dcfb75..bd57e3c8 100644 --- a/smtpd/utils.go +++ b/smtpd/utils.go @@ -2,7 +2,6 @@ package smtpd import ( "bytes" - "container/list" "crypto/sha1" "fmt" "io" @@ -53,18 +52,6 @@ func HashMailboxName(mailbox string) string { return fmt.Sprintf("%x", h.Sum(nil)) } -// JoinStringList joins a List containing strings by commas -func JoinStringList(listOfStrings *list.List) string { - if listOfStrings.Len() == 0 { - return "" - } - s := make([]string, 0, listOfStrings.Len()) - for e := listOfStrings.Front(); e != nil; e = e.Next() { - s = append(s, e.Value.(string)) - } - return strings.Join(s, ",") -} - // ValidateDomainPart returns true if the domain part complies to RFC3696, RFC1035 func ValidateDomainPart(domain string) bool { if len(domain) == 0 {