Skip to content

Commit

Permalink
Use a monotonic clock for reporting/metrics
Browse files Browse the repository at this point in the history
Upgrades github.com/kevinburke/handlers to a version that uses a monotonic
clock. Rewrites the code to call monotime.Now() instead of time.Now() and pass
around uint64's instead of `time.Time` objects, since these values no longer
have a fixed time.Time reference and only make sense as deltas.

Unfortunately this required rewriting quite a lot of code since the previous
implementation could pass a time.Time around fairly widely and then use
time.Since to compute a delta much later on.
  • Loading branch information
kevinburke committed Jan 2, 2017
1 parent f2d754f commit c2f7dc8
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 171 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ WRITE_MAILMAP := $(shell command -v write_mailmap)
STATICCHECK := $(shell command -v staticcheck)

WATCH_TARGETS = static/css/style.css \
templates/base.html \
templates/phone-numbers/list.html templates/phone-numbers/instance.html \
templates/conferences/instance.html templates/conferences/list.html \
templates/alerts/list.html templates/alerts/instance.html \
Expand Down
83 changes: 30 additions & 53 deletions assets/bindata.go

Large diffs are not rendered by default.

27 changes: 16 additions & 11 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"sync"
"time"

"github.com/aristanetworks/goarista/monotime"
"github.com/golang/groupcache/lru"
log "github.com/inconshreveable/log15"
)
Expand Down Expand Up @@ -52,23 +53,23 @@ func enc(data interface{}) []byte {
// Get gets the value at the key and decodes it into val. Returns the time the
// value was stored in the cache, or an error, if the value was not found,
// expired, or could not be decoded into val.
func (c *Cache) Get(key string, val interface{}) (time.Time, error) {
func (c *Cache) Get(key string, val interface{}) (uint64, error) {
c.mu.RLock()
defer c.mu.RUnlock()
cacheVal, ok := c.c.Get(key)
if !ok {
c.Debug("cache miss", "key", key)
return time.Time{}, errNotFound
return 0, errNotFound
}
e, ok := cacheVal.(*expiringBits)
if !ok {
c.Warn("Invalid value in cache", "val", cacheVal, "key", key)
return time.Time{}, errors.New("could not cast value to expiringBits")
return 0, errors.New("could not cast value to expiringBits")
}
if since := time.Since(e.Expires); since > 0 {
c.Debug("found expired value in cache", "key", key, "expired_ago", since)
if now, expires := monotime.Now(), e.Set+e.Timeout; now > expires {
c.Debug("found expired value in cache", "key", key, "expired_ago", time.Duration(now-expires))
c.c.Remove(key)
return time.Time{}, expired
return 0, expired
}
reader, err := gzip.NewReader(bytes.NewReader(e.Bits))
if err != nil {
Expand All @@ -77,27 +78,31 @@ func (c *Cache) Get(key string, val interface{}) (time.Time, error) {
defer reader.Close()
dec := gob.NewDecoder(reader)
if err := dec.Decode(val); err != nil {
return time.Time{}, err
return 0, err
}
c.Debug("cache hit", "key", key, "size", len(e.Bits))
return e.Set, nil
}

func (c *Cache) Set(key string, val interface{}, timeout time.Duration) {
now := time.Now().UTC()
if timeout < 0 {
panic("invalid timeout")
}
now := monotime.Now()
c.mu.Lock()
defer c.mu.Unlock()
e := &expiringBits{
Set: now,
Expires: now.Add(timeout),
Timeout: uint64(timeout),
Bits: enc(val),
}
c.c.Add(key, e)
c.Debug("stored data in cache", "key", key, "size", len(e.Bits), "cache_size", c.c.Len())
}

type expiringBits struct {
Set time.Time
Expires time.Time
Set uint64
// Expire values after Set + Timeout amount of time
Timeout uint64
Bits []byte // call enc() to get an encoded value
}
2 changes: 1 addition & 1 deletion cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"testing"
"time"

twilio "github.com/saintpete/twilio-go"
"github.com/saintpete/logrole/test"
twilio "github.com/saintpete/twilio-go"
)

func TestEncodeDecode(t *testing.T) {
Expand Down
17 changes: 10 additions & 7 deletions server/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (
"strings"
"time"

"github.com/aristanetworks/goarista/monotime"
log "github.com/inconshreveable/log15"
types "github.com/kevinburke/go-types"
"github.com/kevinburke/rest"
twilio "github.com/saintpete/twilio-go"
"github.com/saintpete/logrole/config"
"github.com/saintpete/logrole/services"
"github.com/saintpete/logrole/views"
twilio "github.com/saintpete/twilio-go"
"golang.org/x/net/context"
)

Expand Down Expand Up @@ -115,7 +116,7 @@ func (s *alertInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
ctx, cancel := getContext(r.Context(), 3*time.Second)
defer cancel()
sid := alertInstanceRoute.FindStringSubmatch(r.URL.Path)[1]
start := time.Now()
start := monotime.Now()
alert, err := s.Client.GetAlert(ctx, u, sid)
switch err {
case nil:
Expand All @@ -139,7 +140,7 @@ func (s *alertInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
}
data := &baseData{
LF: s.LocationFinder,
Duration: time.Since(start),
Duration: time.Duration(monotime.Now() - start),
Data: &alertInstanceData{
Alert: alert,
Loc: s.LocationFinder.GetLocationReq(r),
Expand Down Expand Up @@ -336,8 +337,8 @@ func (s *alertListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
var page *views.AlertPage
cachedAt := time.Time{}
start := time.Now()
var cachedAt uint64
start := monotime.Now()
if next != "" {
if !strings.HasPrefix(next, twilio.MonitorBaseURL) {
s.Warn("Invalid next page URI", "next", next, "opaque", query.Get("next"))
Expand Down Expand Up @@ -385,8 +386,10 @@ func (s *alertListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}(u, page.NextPageURI(), startTime, endTime)
data := &baseData{
LF: s.LocationFinder,
CachedAt: cachedAt,
Duration: time.Since(start),
Duration: time.Duration(monotime.Now() - start),
}
if cachedAt > 0 {
data.CachedDuration = time.Duration(monotime.Now() - cachedAt)
}
ad := &alertListData{
Page: page,
Expand Down
17 changes: 10 additions & 7 deletions server/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (
"sync"
"time"

"github.com/aristanetworks/goarista/monotime"
log "github.com/inconshreveable/log15"
types "github.com/kevinburke/go-types"
"github.com/kevinburke/rest"
twilio "github.com/saintpete/twilio-go"
"github.com/saintpete/logrole/config"
"github.com/saintpete/logrole/services"
"github.com/saintpete/logrole/views"
twilio "github.com/saintpete/twilio-go"
"golang.org/x/net/context"
)

Expand Down Expand Up @@ -194,8 +195,8 @@ func (s *callListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
var page *views.CallPage
cachedAt := time.Time{}
queryStart := time.Now()
var cachedAt uint64
queryStart := monotime.Now()
if next != "" {
if !strings.HasPrefix(next, "/"+twilio.APIVersion) {
s.Warn("Invalid next page URI", "next", next, "opaque", query.Get("next"))
Expand Down Expand Up @@ -232,8 +233,10 @@ func (s *callListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}(u, page.NextPageURI(), startTime, endTime)
data := &baseData{
LF: s.LocationFinder,
CachedAt: cachedAt,
Duration: time.Since(queryStart),
Duration: time.Duration(monotime.Now() - queryStart),
}
if cachedAt > 0 {
data.CachedDuration = time.Duration(monotime.Now() - cachedAt)
}
data.Data = &callListData{
Page: page,
Expand Down Expand Up @@ -340,7 +343,7 @@ func (c *callInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rch := make(chan *recordingResp, 1)
ctx, cancel := getContext(r.Context(), 3*time.Second)
defer cancel()
start := time.Now()
start := monotime.Now()
go c.fetchRecordings(ctx, sid, u, rch)
var wg sync.WaitGroup
wg.Add(1)
Expand Down Expand Up @@ -374,7 +377,7 @@ func (c *callInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
wg.Wait()
data := &baseData{
LF: c.LocationFinder,
Duration: time.Since(start),
Duration: time.Duration(monotime.Now() - start),
}
cid := &callInstanceData{
Call: call,
Expand Down
23 changes: 13 additions & 10 deletions server/conferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
"strings"
"time"

"github.com/aristanetworks/goarista/monotime"
log "github.com/inconshreveable/log15"
types "github.com/kevinburke/go-types"
"github.com/kevinburke/rest"
twilio "github.com/saintpete/twilio-go"
"github.com/saintpete/logrole/config"
"github.com/saintpete/logrole/services"
"github.com/saintpete/logrole/views"
twilio "github.com/saintpete/twilio-go"
"golang.org/x/net/context"
)

Expand Down Expand Up @@ -207,8 +208,8 @@ func (c *conferenceListServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
ctx, cancel := getContext(r.Context(), 3*time.Second)
defer cancel()
var page *views.ConferencePage
cachedAt := time.Time{}
start := time.Now()
var cachedAt uint64
start := monotime.Now()
if next != "" {
if !strings.HasPrefix(next, "/"+twilio.APIVersion) {
c.Warn("Invalid next page URI", "next", next, "opaque", query.Get("next"))
Expand Down Expand Up @@ -243,19 +244,21 @@ func (c *conferenceListServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
}
}(u, page.NextPageURI(), startTime, endTime)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err = render(w, r, c.tpl, "base", &baseData{
data := &baseData{
LF: c.LocationFinder,
CachedAt: cachedAt,
Duration: time.Since(start),
Duration: time.Duration(monotime.Now() - start),
Data: &conferenceListData{
Query: r.URL.Query(),
Page: page,
Loc: loc,
EncryptedNextPage: getEncryptedPage(page.NextPageURI(), c.secretKey),
EncryptedPreviousPage: getEncryptedPage(page.PreviousPageURI(), c.secretKey),
},
})
if err != nil {
}
if cachedAt > 0 {
data.CachedDuration = time.Duration(monotime.Now() - cachedAt)
}
if err = render(w, r, c.tpl, "base", data); err != nil {
rest.ServerError(w, r, err)
}
}
Expand All @@ -273,7 +276,7 @@ func (c *conferenceInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Requ
sid := conferenceInstanceRoute.FindStringSubmatch(r.URL.Path)[1]
ctx, cancel := getContext(r.Context(), 3*time.Second)
defer cancel()
start := time.Now()
start := monotime.Now()
conference, err := c.Client.GetConference(ctx, u, sid)
switch err {
case nil:
Expand All @@ -297,7 +300,7 @@ func (c *conferenceInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Requ
}
data := &baseData{
LF: c.LocationFinder,
Duration: time.Since(start),
Duration: time.Duration(monotime.Now() - start),
Data: &conferenceInstanceData{
Conference: conference,
Loc: c.LocationFinder.GetLocationReq(r),
Expand Down
22 changes: 14 additions & 8 deletions server/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
"strings"
"time"

"github.com/aristanetworks/goarista/monotime"
log "github.com/inconshreveable/log15"
types "github.com/kevinburke/go-types"
"github.com/kevinburke/rest"
twilio "github.com/saintpete/twilio-go"
"github.com/saintpete/logrole/config"
"github.com/saintpete/logrole/services"
"github.com/saintpete/logrole/views"
twilio "github.com/saintpete/twilio-go"
"golang.org/x/net/context"
)

Expand Down Expand Up @@ -74,7 +75,7 @@ func (s *messageInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request
sid := messageInstanceRoute.FindStringSubmatch(r.URL.Path)[1]
ctx, cancel := getContext(r.Context(), 3*time.Second)
defer cancel()
start := time.Now()
start := monotime.Now()
rch := make(chan *mediaResp, 1)
go func(sid string) {
urls, err := s.Client.GetMediaURLs(ctx, u, sid)
Expand Down Expand Up @@ -109,7 +110,10 @@ func (s *messageInstanceServer) ServeHTTP(w http.ResponseWriter, r *http.Request
rest.Forbidden(w, r, &rest.Error{Title: "Cannot view this message"})
return
}
baseData := &baseData{LF: s.LocationFinder, Duration: time.Since(start)}
baseData := &baseData{
LF: s.LocationFinder,
Duration: time.Duration(monotime.Now() - start),
}
data := &messageInstanceData{
Message: message,
Loc: s.LocationFinder.GetLocationReq(r),
Expand Down Expand Up @@ -272,7 +276,6 @@ func (s *messageListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.renderError(w, r, http.StatusBadRequest, query, err)
return
}
var page *views.MessagePage
loc := s.LocationFinder.GetLocationReq(r)
var err error
startTime, endTime, wroteError := getTimes(w, r, "start", "end", loc, query, s)
Expand All @@ -287,8 +290,9 @@ func (s *messageListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.renderError(w, r, http.StatusBadRequest, query, err)
return
}
cachedAt := time.Time{}
start := time.Now()
var page *views.MessagePage
var cachedAt uint64
start := monotime.Now()
if next != "" {
if !strings.HasPrefix(next, "/"+twilio.APIVersion) {
s.Warn("Invalid next page URI", "next", next, "opaque", query.Get("next"))
Expand Down Expand Up @@ -337,8 +341,7 @@ func (s *messageListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}(u, page.NextPageURI(), startTime, endTime)
data := &baseData{
LF: s.LocationFinder,
Duration: time.Since(start),
CachedAt: cachedAt,
Duration: time.Duration(monotime.Now() - start),
Data: &messageListData{
Page: page,
Loc: loc,
Expand All @@ -347,6 +350,9 @@ func (s *messageListServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
EncryptedPreviousPage: getEncryptedPage(page.PreviousPageURI(), s.secretKey),
EncryptedNextPage: getEncryptedPage(page.NextPageURI(), s.secretKey),
}}
if cachedAt > 0 {
data.CachedDuration = time.Duration(monotime.Now() - cachedAt)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := render(w, r, s.tpl, "base", data); err != nil {
s.renderError(w, r, http.StatusInternalServerError, query, err)
Expand Down
Loading

0 comments on commit c2f7dc8

Please sign in to comment.