[ARVADOS] created: 1.1.2-169-g8539844

Git user git at public.curoverse.com
Tue Feb 6 18:04:27 EST 2018


        at  85398442d095524c5b2f315c294ea81f9d17853b (commit)


commit 85398442d095524c5b2f315c294ea81f9d17853b
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date:   Tue Feb 6 17:52:18 2018 -0500

    13025: Add request time metrics at /metrics and /metrics.json.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>

diff --git a/sdk/go/httpserver/logger.go b/sdk/go/httpserver/logger.go
index decb2ff..fab850f 100644
--- a/sdk/go/httpserver/logger.go
+++ b/sdk/go/httpserver/logger.go
@@ -10,7 +10,7 @@ import (
 	"time"
 
 	"git.curoverse.com/arvados.git/sdk/go/stats"
-	log "github.com/Sirupsen/logrus"
+	"github.com/Sirupsen/logrus"
 )
 
 type contextKey struct {
@@ -19,13 +19,15 @@ type contextKey struct {
 
 var requestTimeContextKey = contextKey{"requestTime"}
 
+var Logger logrus.FieldLogger = logrus.StandardLogger()
+
 // LogRequests wraps an http.Handler, logging each request and
 // response via logrus.
 func LogRequests(h http.Handler) http.Handler {
 	return http.HandlerFunc(func(wrapped http.ResponseWriter, req *http.Request) {
 		w := &responseTimer{ResponseWriter: WrapResponseWriter(wrapped)}
 		req = req.WithContext(context.WithValue(req.Context(), &requestTimeContextKey, time.Now()))
-		lgr := log.WithFields(log.Fields{
+		lgr := Logger.WithFields(logrus.Fields{
 			"RequestID":       req.Header.Get("X-Request-Id"),
 			"remoteAddr":      req.RemoteAddr,
 			"reqForwardedFor": req.Header.Get("X-Forwarded-For"),
@@ -39,20 +41,20 @@ func LogRequests(h http.Handler) http.Handler {
 	})
 }
 
-func logRequest(w *responseTimer, req *http.Request, lgr *log.Entry) {
+func logRequest(w *responseTimer, req *http.Request, lgr *logrus.Entry) {
 	lgr.Info("request")
 }
 
-func logResponse(w *responseTimer, req *http.Request, lgr *log.Entry) {
+func logResponse(w *responseTimer, req *http.Request, lgr *logrus.Entry) {
 	if tStart, ok := req.Context().Value(&requestTimeContextKey).(time.Time); ok {
 		tDone := time.Now()
-		lgr = lgr.WithFields(log.Fields{
+		lgr = lgr.WithFields(logrus.Fields{
 			"timeTotal":     stats.Duration(tDone.Sub(tStart)),
 			"timeToStatus":  stats.Duration(w.writeTime.Sub(tStart)),
 			"timeWriteBody": stats.Duration(tDone.Sub(w.writeTime)),
 		})
 	}
-	lgr.WithFields(log.Fields{
+	lgr.WithFields(logrus.Fields{
 		"respStatusCode": w.WroteStatus(),
 		"respStatus":     http.StatusText(w.WroteStatus()),
 		"respBytes":      w.WroteBodyBytes(),
@@ -65,6 +67,13 @@ type responseTimer struct {
 	writeTime time.Time
 }
 
+func (rt *responseTimer) CloseNotify() <-chan bool {
+	if cn, ok := rt.ResponseWriter.(http.CloseNotifier); ok {
+		return cn.CloseNotify()
+	}
+	return nil
+}
+
 func (rt *responseTimer) WriteHeader(code int) {
 	if !rt.wrote {
 		rt.wrote = true
diff --git a/sdk/go/httpserver/responsewriter.go b/sdk/go/httpserver/responsewriter.go
index f17bc82..5fb810e 100644
--- a/sdk/go/httpserver/responsewriter.go
+++ b/sdk/go/httpserver/responsewriter.go
@@ -28,6 +28,13 @@ func WrapResponseWriter(orig http.ResponseWriter) ResponseWriter {
 	return &responseWriter{ResponseWriter: orig}
 }
 
+func (w *responseWriter) CloseNotify() <-chan bool {
+	if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok {
+		return cn.CloseNotify()
+	}
+	return nil
+}
+
 func (w *responseWriter) WriteHeader(s int) {
 	w.wroteStatus = s
 	w.ResponseWriter.WriteHeader(s)
@@ -41,6 +48,9 @@ func (w *responseWriter) Write(data []byte) (n int, err error) {
 }
 
 func (w *responseWriter) WroteStatus() int {
+	if w.wroteStatus == 0 {
+		return http.StatusOK
+	}
 	return w.wroteStatus
 }
 
diff --git a/services/keepstore/azure_blob_volume.go b/services/keepstore/azure_blob_volume.go
index 62c856d..2638bf0 100644
--- a/services/keepstore/azure_blob_volume.go
+++ b/services/keepstore/azure_blob_volume.go
@@ -21,7 +21,6 @@ import (
 	"time"
 
 	"git.curoverse.com/arvados.git/sdk/go/arvados"
-	log "github.com/Sirupsen/logrus"
 	"github.com/curoverse/azure-sdk-for-go/storage"
 )
 
diff --git a/services/keepstore/azure_blob_volume_test.go b/services/keepstore/azure_blob_volume_test.go
index 06216ed..31b79da 100644
--- a/services/keepstore/azure_blob_volume_test.go
+++ b/services/keepstore/azure_blob_volume_test.go
@@ -26,7 +26,6 @@ import (
 	"testing"
 	"time"
 
-	log "github.com/Sirupsen/logrus"
 	"github.com/curoverse/azure-sdk-for-go/storage"
 	check "gopkg.in/check.v1"
 )
diff --git a/services/keepstore/bufferpool.go b/services/keepstore/bufferpool.go
index 91417fd..d2e7c9e 100644
--- a/services/keepstore/bufferpool.go
+++ b/services/keepstore/bufferpool.go
@@ -8,8 +8,6 @@ import (
 	"sync"
 	"sync/atomic"
 	"time"
-
-	log "github.com/Sirupsen/logrus"
 )
 
 type bufferPool struct {
diff --git a/services/keepstore/bufferpool_test.go b/services/keepstore/bufferpool_test.go
index a6479e4..21b03ed 100644
--- a/services/keepstore/bufferpool_test.go
+++ b/services/keepstore/bufferpool_test.go
@@ -5,8 +5,9 @@
 package main
 
 import (
-	. "gopkg.in/check.v1"
 	"time"
+
+	. "gopkg.in/check.v1"
 )
 
 var _ = Suite(&BufferPoolSuite{})
diff --git a/services/keepstore/config.go b/services/keepstore/config.go
index 0a3ece4..bbce883 100644
--- a/services/keepstore/config.go
+++ b/services/keepstore/config.go
@@ -9,11 +9,17 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
+	"net/http"
+	"strconv"
 	"strings"
 	"time"
 
 	"git.curoverse.com/arvados.git/sdk/go/arvados"
-	log "github.com/Sirupsen/logrus"
+	"git.curoverse.com/arvados.git/sdk/go/stats"
+	"github.com/Sirupsen/logrus"
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 type Config struct {
@@ -42,9 +48,23 @@ type Config struct {
 	debugLogf       func(string, ...interface{})
 
 	ManagementToken string
+
+	metrics
 }
 
-var theConfig = DefaultConfig()
+var (
+	theConfig = DefaultConfig()
+	formatter = map[string]logrus.Formatter{
+		"text": &logrus.TextFormatter{
+			FullTimestamp:   true,
+			TimestampFormat: rfc3339NanoFixed,
+		},
+		"json": &logrus.JSONFormatter{
+			TimestampFormat: rfc3339NanoFixed,
+		},
+	}
+	log = logrus.StandardLogger()
+)
 
 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
 
@@ -66,25 +86,18 @@ func DefaultConfig() *Config {
 // fields, and before using the config.
 func (cfg *Config) Start() error {
 	if cfg.Debug {
-		log.SetLevel(log.DebugLevel)
+		log.Level = logrus.DebugLevel
 		cfg.debugLogf = log.Printf
 		cfg.debugLogf("debugging enabled")
 	} else {
+		log.Level = logrus.InfoLevel
 		cfg.debugLogf = func(string, ...interface{}) {}
 	}
 
-	switch strings.ToLower(cfg.LogFormat) {
-	case "text":
-		log.SetFormatter(&log.TextFormatter{
-			FullTimestamp:   true,
-			TimestampFormat: rfc3339NanoFixed,
-		})
-	case "json":
-		log.SetFormatter(&log.JSONFormatter{
-			TimestampFormat: rfc3339NanoFixed,
-		})
-	default:
+	if f := formatter[strings.ToLower(cfg.LogFormat)]; f == nil {
 		return fmt.Errorf(`unsupported log format %q (try "text" or "json")`, cfg.LogFormat)
+	} else {
+		log.Formatter = f
 	}
 
 	if cfg.MaxBuffers < 0 {
@@ -142,6 +155,62 @@ func (cfg *Config) Start() error {
 	return nil
 }
 
+type metrics struct {
+	registry     *prometheus.Registry
+	reqDuration  *prometheus.SummaryVec
+	timeToStatus *prometheus.SummaryVec
+	exportProm   http.Handler
+}
+
+func (*metrics) Levels() []logrus.Level {
+	return logrus.AllLevels
+}
+
+func (m *metrics) Fire(ent *logrus.Entry) error {
+	if tts, ok := ent.Data["timeToStatus"].(stats.Duration); !ok {
+	} else if method, ok := ent.Data["reqMethod"].(string); !ok {
+	} else if code, ok := ent.Data["respStatusCode"].(int); !ok {
+	} else {
+		m.timeToStatus.WithLabelValues(strconv.Itoa(code), strings.ToLower(method)).Observe(time.Duration(tts).Seconds())
+	}
+	return nil
+}
+
+func (m *metrics) setup() {
+	m.registry = prometheus.NewRegistry()
+	m.timeToStatus = prometheus.NewSummaryVec(prometheus.SummaryOpts{
+		Name: "time_to_status_seconds",
+		Help: "Summary of request TTFB.",
+	}, []string{"code", "method"})
+	m.reqDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{
+		Name: "request_duration_seconds",
+		Help: "Summary of request duration.",
+	}, []string{"code", "method"})
+	m.registry.MustRegister(m.timeToStatus)
+	m.registry.MustRegister(m.reqDuration)
+	m.exportProm = promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{
+		ErrorLog: log,
+	})
+	log.AddHook(m)
+}
+
+func (m *metrics) exportJSON(w http.ResponseWriter, req *http.Request) {
+	jm := jsonpb.Marshaler{Indent: "  "}
+	mfs, _ := m.registry.Gather()
+	w.Write([]byte{'['})
+	for i, mf := range mfs {
+		if i > 0 {
+			w.Write([]byte{','})
+		}
+		jm.Marshal(w, mf)
+	}
+	w.Write([]byte{']'})
+}
+
+func (m *metrics) Instrument(next http.Handler) http.Handler {
+	return promhttp.InstrumentHandlerDuration(m.reqDuration, next)
+}
+
 // VolumeTypes is built up by init() funcs in the source files that
 // define the volume types.
 var VolumeTypes = []func() VolumeWithExamples{}
diff --git a/services/keepstore/config_test.go b/services/keepstore/config_test.go
index 5e55042..d6471e3 100644
--- a/services/keepstore/config_test.go
+++ b/services/keepstore/config_test.go
@@ -5,9 +5,10 @@
 package main
 
 import (
-	log "github.com/Sirupsen/logrus"
+	"github.com/Sirupsen/logrus"
 )
 
 func init() {
+	log.Level = logrus.DebugLevel
 	theConfig.debugLogf = log.Printf
 }
diff --git a/services/keepstore/handlers.go b/services/keepstore/handlers.go
index daf4fc6..258604c 100644
--- a/services/keepstore/handlers.go
+++ b/services/keepstore/handlers.go
@@ -31,7 +31,6 @@ import (
 
 	"git.curoverse.com/arvados.git/sdk/go/health"
 	"git.curoverse.com/arvados.git/sdk/go/httpserver"
-	log "github.com/Sirupsen/logrus"
 )
 
 type router struct {
@@ -41,54 +40,63 @@ type router struct {
 
 // MakeRESTRouter returns a new router that forwards all Keep requests
 // to the appropriate handlers.
-func MakeRESTRouter() *router {
-	rest := mux.NewRouter()
-	rtr := &router{Router: rest}
+func MakeRESTRouter() http.Handler {
+	rtr := &router{Router: mux.NewRouter()}
 
-	rest.HandleFunc(
+	rtr.HandleFunc(
 		`/{hash:[0-9a-f]{32}}`, GetBlockHandler).Methods("GET", "HEAD")
-	rest.HandleFunc(
+	rtr.HandleFunc(
 		`/{hash:[0-9a-f]{32}}+{hints}`,
 		GetBlockHandler).Methods("GET", "HEAD")
 
-	rest.HandleFunc(`/{hash:[0-9a-f]{32}}`, PutBlockHandler).Methods("PUT")
-	rest.HandleFunc(`/{hash:[0-9a-f]{32}}`, DeleteHandler).Methods("DELETE")
+	rtr.HandleFunc(`/{hash:[0-9a-f]{32}}`, PutBlockHandler).Methods("PUT")
+	rtr.HandleFunc(`/{hash:[0-9a-f]{32}}`, DeleteHandler).Methods("DELETE")
 	// List all blocks stored here. Privileged client only.
-	rest.HandleFunc(`/index`, rtr.IndexHandler).Methods("GET", "HEAD")
+	rtr.HandleFunc(`/index`, rtr.IndexHandler).Methods("GET", "HEAD")
 	// List blocks stored here whose hash has the given prefix.
 	// Privileged client only.
-	rest.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, rtr.IndexHandler).Methods("GET", "HEAD")
+	rtr.HandleFunc(`/index/{prefix:[0-9a-f]{0,32}}`, rtr.IndexHandler).Methods("GET", "HEAD")
 
 	// Internals/debugging info (runtime.MemStats)
-	rest.HandleFunc(`/debug.json`, rtr.DebugHandler).Methods("GET", "HEAD")
+	rtr.HandleFunc(`/debug.json`, rtr.DebugHandler).Methods("GET", "HEAD")
 
 	// List volumes: path, device number, bytes used/avail.
-	rest.HandleFunc(`/status.json`, rtr.StatusHandler).Methods("GET", "HEAD")
+	rtr.HandleFunc(`/status.json`, rtr.StatusHandler).Methods("GET", "HEAD")
 
 	// List mounts: UUID, readonly, tier, device ID, ...
-	rest.HandleFunc(`/mounts`, rtr.MountsHandler).Methods("GET")
-	rest.HandleFunc(`/mounts/{uuid}/blocks`, rtr.IndexHandler).Methods("GET")
-	rest.HandleFunc(`/mounts/{uuid}/blocks/`, rtr.IndexHandler).Methods("GET")
+	rtr.HandleFunc(`/mounts`, rtr.MountsHandler).Methods("GET")
+	rtr.HandleFunc(`/mounts/{uuid}/blocks`, rtr.IndexHandler).Methods("GET")
+	rtr.HandleFunc(`/mounts/{uuid}/blocks/`, rtr.IndexHandler).Methods("GET")
 
 	// Replace the current pull queue.
-	rest.HandleFunc(`/pull`, PullHandler).Methods("PUT")
+	rtr.HandleFunc(`/pull`, PullHandler).Methods("PUT")
 
 	// Replace the current trash queue.
-	rest.HandleFunc(`/trash`, TrashHandler).Methods("PUT")
+	rtr.HandleFunc(`/trash`, TrashHandler).Methods("PUT")
 
 	// Untrash moves blocks from trash back into store
-	rest.HandleFunc(`/untrash/{hash:[0-9a-f]{32}}`, UntrashHandler).Methods("PUT")
+	rtr.HandleFunc(`/untrash/{hash:[0-9a-f]{32}}`, UntrashHandler).Methods("PUT")
 
-	rest.Handle("/_health/{check}", &health.Handler{
+	rtr.Handle("/_health/{check}", &health.Handler{
 		Token:  theConfig.ManagementToken,
 		Prefix: "/_health/",
 	}).Methods("GET")
 
 	// Any request which does not match any of these routes gets
 	// 400 Bad Request.
-	rest.NotFoundHandler = http.HandlerFunc(BadRequestHandler)
+	rtr.NotFoundHandler = http.HandlerFunc(BadRequestHandler)
 
-	return rtr
+	theConfig.metrics.setup()
+
+	rtr.limiter = httpserver.NewRequestLimiter(theConfig.MaxRequests, rtr)
+
+	mux := http.NewServeMux()
+	mux.Handle("/", theConfig.metrics.Instrument(
+		httpserver.AddRequestIDs(httpserver.LogRequests(rtr.limiter))))
+	mux.HandleFunc("/metrics.json", theConfig.metrics.exportJSON)
+	mux.Handle("/metrics", theConfig.metrics.exportProm)
+
+	return mux
 }
 
 // BadRequestHandler is a HandleFunc to address bad requests.
diff --git a/services/keepstore/keepstore.go b/services/keepstore/keepstore.go
index b8a0ffb..03eef7e 100644
--- a/services/keepstore/keepstore.go
+++ b/services/keepstore/keepstore.go
@@ -16,9 +16,7 @@ import (
 
 	"git.curoverse.com/arvados.git/sdk/go/arvadosclient"
 	"git.curoverse.com/arvados.git/sdk/go/config"
-	"git.curoverse.com/arvados.git/sdk/go/httpserver"
 	"git.curoverse.com/arvados.git/sdk/go/keepclient"
-	log "github.com/Sirupsen/logrus"
 	"github.com/coreos/go-systemd/daemon"
 )
 
@@ -160,9 +158,6 @@ func main() {
 
 	// Middleware/handler stack
 	router := MakeRESTRouter()
-	limiter := httpserver.NewRequestLimiter(theConfig.MaxRequests, router)
-	router.limiter = limiter
-	http.Handle("/", httpserver.AddRequestIDs(httpserver.LogRequests(limiter)))
 
 	// Set up a TCP listener.
 	listener, err := net.Listen("tcp", theConfig.Listen)
@@ -204,7 +199,7 @@ func main() {
 		log.Printf("Error notifying init daemon: %v", err)
 	}
 	log.Println("listening at", listener.Addr())
-	srv := &http.Server{}
+	srv := &http.Server{Handler: router}
 	srv.Serve(listener)
 }
 
diff --git a/services/keepstore/mounts_test.go b/services/keepstore/mounts_test.go
index b4544d0..883aa71 100644
--- a/services/keepstore/mounts_test.go
+++ b/services/keepstore/mounts_test.go
@@ -5,6 +5,7 @@
 package main
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"net/http"
@@ -24,8 +25,10 @@ type MountsSuite struct {
 func (s *MountsSuite) SetUpTest(c *check.C) {
 	s.vm = MakeTestVolumeManager(2)
 	KeepVM = s.vm
-	s.rtr = MakeRESTRouter()
+	theConfig = DefaultConfig()
 	theConfig.systemAuthToken = arvadostest.DataManagerToken
+	theConfig.Start()
+	s.rtr = MakeRESTRouter()
 }
 
 func (s *MountsSuite) TearDownTest(c *check.C) {
@@ -40,7 +43,7 @@ func (s *MountsSuite) TestMounts(c *check.C) {
 	vols[0].Put(context.Background(), TestHash, TestBlock)
 	vols[1].Put(context.Background(), TestHash2, TestBlock2)
 
-	resp := s.call("GET", "/mounts", "")
+	resp := s.call("GET", "/mounts", "", nil)
 	c.Check(resp.Code, check.Equals, http.StatusOK)
 	var mntList []struct {
 		UUID        string
@@ -64,7 +67,7 @@ func (s *MountsSuite) TestMounts(c *check.C) {
 
 	// Bad auth
 	for _, tok := range []string{"", "xyzzy"} {
-		resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks", tok)
+		resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks", tok, nil)
 		c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
 		c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
 	}
@@ -72,34 +75,74 @@ func (s *MountsSuite) TestMounts(c *check.C) {
 	tok := arvadostest.DataManagerToken
 
 	// Nonexistent mount UUID
-	resp = s.call("GET", "/mounts/X/blocks", tok)
+	resp = s.call("GET", "/mounts/X/blocks", tok, nil)
 	c.Check(resp.Code, check.Equals, http.StatusNotFound)
 	c.Check(resp.Body.String(), check.Equals, "mount not found\n")
 
 	// Complete index of first mount
-	resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks", tok)
+	resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks", tok, nil)
 	c.Check(resp.Code, check.Equals, http.StatusOK)
 	c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
 
 	// Partial index of first mount (one block matches prefix)
-	resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks?prefix="+TestHash[:2], tok)
+	resp = s.call("GET", "/mounts/"+mntList[0].UUID+"/blocks?prefix="+TestHash[:2], tok, nil)
 	c.Check(resp.Code, check.Equals, http.StatusOK)
 	c.Check(resp.Body.String(), check.Matches, TestHash+`\+[0-9]+ [0-9]+\n\n`)
 
 	// Complete index of second mount (note trailing slash)
-	resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/", tok)
+	resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/", tok, nil)
 	c.Check(resp.Code, check.Equals, http.StatusOK)
 	c.Check(resp.Body.String(), check.Matches, TestHash2+`\+[0-9]+ [0-9]+\n\n`)
 
 	// Partial index of second mount (no blocks match prefix)
-	resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/?prefix="+TestHash[:2], tok)
+	resp = s.call("GET", "/mounts/"+mntList[1].UUID+"/blocks/?prefix="+TestHash[:2], tok, nil)
 	c.Check(resp.Code, check.Equals, http.StatusOK)
 	c.Check(resp.Body.String(), check.Equals, "\n")
 }
 
-func (s *MountsSuite) call(method, path, tok string) *httptest.ResponseRecorder {
+func (s *MountsSuite) TestMetrics(c *check.C) {
+	s.call("PUT", "/"+TestHash, "", TestBlock)
+	s.call("PUT", "/"+TestHash2, "", TestBlock2)
+	resp := s.call("GET", "/metrics.json", "", nil)
+	c.Check(resp.Code, check.Equals, http.StatusOK)
+	var j []struct {
+		Name   string
+		Help   string
+		Type   string
+		Metric []struct {
+			Label []struct {
+				Name  string
+				Value string
+			}
+			Summary struct {
+				SampleCount string  `json:"sample_count"`
+				SampleSum   float64 `json:"sample_sum"`
+				Quantile    []struct {
+					Quantile float64
+					Value    float64
+				}
+			}
+		}
+	}
+	json.NewDecoder(resp.Body).Decode(&j)
+	found := make(map[string]bool)
+	for _, g := range j {
+		for _, m := range g.Metric {
+			if len(m.Label) == 2 && m.Label[0].Name == "code" && m.Label[0].Value == "200" && m.Label[1].Name == "method" && m.Label[1].Value == "put" {
+				c.Check(m.Summary.SampleCount, check.Equals, "2")
+				c.Check(len(m.Summary.Quantile), check.Not(check.Equals), 0)
+				c.Check(m.Summary.Quantile[0].Value, check.Not(check.Equals), float64(0))
+				found[g.Name] = true
+			}
+		}
+	}
+	c.Check(found["request_duration_seconds"], check.Equals, true)
+	c.Check(found["time_to_status_seconds"], check.Equals, true)
+}
+
+func (s *MountsSuite) call(method, path, tok string, body []byte) *httptest.ResponseRecorder {
 	resp := httptest.NewRecorder()
-	req, _ := http.NewRequest(method, path, nil)
+	req, _ := http.NewRequest(method, path, bytes.NewReader(body))
 	if tok != "" {
 		req.Header.Set("Authorization", "OAuth2 "+tok)
 	}
diff --git a/services/keepstore/pull_worker.go b/services/keepstore/pull_worker.go
index f821fb5..42b5d58 100644
--- a/services/keepstore/pull_worker.go
+++ b/services/keepstore/pull_worker.go
@@ -13,8 +13,6 @@ import (
 	"time"
 
 	"git.curoverse.com/arvados.git/sdk/go/keepclient"
-
-	log "github.com/Sirupsen/logrus"
 )
 
 // RunPullWorker receives PullRequests from pullq, invokes
diff --git a/services/keepstore/s3_volume.go b/services/keepstore/s3_volume.go
index 61e69f9..90e8a1b 100644
--- a/services/keepstore/s3_volume.go
+++ b/services/keepstore/s3_volume.go
@@ -23,7 +23,6 @@ import (
 	"git.curoverse.com/arvados.git/sdk/go/arvados"
 	"github.com/AdRoll/goamz/aws"
 	"github.com/AdRoll/goamz/s3"
-	log "github.com/Sirupsen/logrus"
 )
 
 const (
diff --git a/services/keepstore/s3_volume_test.go b/services/keepstore/s3_volume_test.go
index 3d4a195..acc1b11 100644
--- a/services/keepstore/s3_volume_test.go
+++ b/services/keepstore/s3_volume_test.go
@@ -19,7 +19,6 @@ import (
 	"git.curoverse.com/arvados.git/sdk/go/arvados"
 	"github.com/AdRoll/goamz/s3"
 	"github.com/AdRoll/goamz/s3/s3test"
-	log "github.com/Sirupsen/logrus"
 	check "gopkg.in/check.v1"
 )
 
diff --git a/services/keepstore/trash_worker.go b/services/keepstore/trash_worker.go
index 51fbb94..cbb831e 100644
--- a/services/keepstore/trash_worker.go
+++ b/services/keepstore/trash_worker.go
@@ -9,7 +9,6 @@ import (
 	"time"
 
 	"git.curoverse.com/arvados.git/sdk/go/arvados"
-	log "github.com/Sirupsen/logrus"
 )
 
 // RunTrashWorker is used by Keepstore to initiate trash worker channel goroutine.
diff --git a/services/keepstore/volume_unix.go b/services/keepstore/volume_unix.go
index da9b110..ea9aa48 100644
--- a/services/keepstore/volume_unix.go
+++ b/services/keepstore/volume_unix.go
@@ -20,8 +20,6 @@ import (
 	"sync"
 	"syscall"
 	"time"
-
-	log "github.com/Sirupsen/logrus"
 )
 
 type unixVolumeAdder struct {
diff --git a/vendor/vendor.json b/vendor/vendor.json
index aeac93e..483ab70 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -36,6 +36,12 @@
 			"revisionTime": "2017-12-05T20:32:29Z"
 		},
 		{
+			"checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=",
+			"path": "github.com/beorn7/perks/quantile",
+			"revision": "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9",
+			"revisionTime": "2016-08-04T10:47:26Z"
+		},
+		{
 			"checksumSHA1": "+Zz+leZHHC9C0rx8DoRuffSRPso=",
 			"path": "github.com/coreos/go-systemd/daemon",
 			"revision": "cc4f39464dc797b91c8025330de585294c2a6950",
@@ -198,6 +204,24 @@
 			"revisionTime": "2018-01-04T10:21:28Z"
 		},
 		{
+			"checksumSHA1": "iVfdaLxIDjfk2KLP8dCMIbsxZZM=",
+			"path": "github.com/golang/protobuf/jsonpb",
+			"revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845",
+			"revisionTime": "2017-11-13T18:07:20Z"
+		},
+		{
+			"checksumSHA1": "yqF125xVSkmfLpIVGrLlfE05IUk=",
+			"path": "github.com/golang/protobuf/proto",
+			"revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845",
+			"revisionTime": "2017-11-13T18:07:20Z"
+		},
+		{
+			"checksumSHA1": "Ylq6kq3KWBy6mu68oyEwenhNMdg=",
+			"path": "github.com/golang/protobuf/ptypes/struct",
+			"revision": "1e59b77b52bf8e4b449a57e6f79f21226d571845",
+			"revisionTime": "2017-11-13T18:07:20Z"
+		},
+		{
 			"checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=",
 			"origin": "github.com/docker/docker/vendor/github.com/gorilla/context",
 			"path": "github.com/gorilla/context",
@@ -247,6 +271,12 @@
 			"revisionTime": "2017-11-26T05:04:59Z"
 		},
 		{
+			"checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=",
+			"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
+			"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
+			"revisionTime": "2016-04-24T11:30:07Z"
+		},
+		{
 			"checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=",
 			"path": "github.com/mitchellh/go-homedir",
 			"revision": "b8bc1bf767474819792c23f32d8286a45736f1c6",
@@ -283,6 +313,66 @@
 			"revisionTime": "2017-12-16T07:03:16Z"
 		},
 		{
+			"checksumSHA1": "Ajt29IHVbX99PUvzn8Gc/lMCXBY=",
+			"path": "github.com/prometheus/client_golang/prometheus",
+			"revision": "9bb6ab929dcbe1c8393cd9ef70387cb69811bd1c",
+			"revisionTime": "2018-02-03T14:28:15Z"
+		},
+		{
+			"checksumSHA1": "c3Ui7nnLiJ4CAGWZ8dGuEgqHd8s=",
+			"path": "github.com/prometheus/client_golang/prometheus/promhttp",
+			"revision": "9bb6ab929dcbe1c8393cd9ef70387cb69811bd1c",
+			"revisionTime": "2018-02-03T14:28:15Z"
+		},
+		{
+			"checksumSHA1": "DvwvOlPNAgRntBzt3b3OSRMS2N4=",
+			"path": "github.com/prometheus/client_model/go",
+			"revision": "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c",
+			"revisionTime": "2017-11-17T10:05:41Z"
+		},
+		{
+			"checksumSHA1": "xfnn0THnqNwjwimeTClsxahYrIo=",
+			"path": "github.com/prometheus/common/expfmt",
+			"revision": "89604d197083d4781071d3c65855d24ecfb0a563",
+			"revisionTime": "2018-01-10T21:49:58Z"
+		},
+		{
+			"checksumSHA1": "GWlM3d2vPYyNATtTFgftS10/A9w=",
+			"path": "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg",
+			"revision": "89604d197083d4781071d3c65855d24ecfb0a563",
+			"revisionTime": "2018-01-10T21:49:58Z"
+		},
+		{
+			"checksumSHA1": "YU+/K48IMawQnToO4ETE6a+hhj4=",
+			"path": "github.com/prometheus/common/model",
+			"revision": "89604d197083d4781071d3c65855d24ecfb0a563",
+			"revisionTime": "2018-01-10T21:49:58Z"
+		},
+		{
+			"checksumSHA1": "lolK0h7LSVERIX8zLyVQ/+7wEyA=",
+			"path": "github.com/prometheus/procfs",
+			"revision": "cb4147076ac75738c9a7d279075a253c0cc5acbd",
+			"revisionTime": "2018-01-25T13:30:57Z"
+		},
+		{
+			"checksumSHA1": "lv9rIcjbVEGo8AT1UCUZXhXrfQc=",
+			"path": "github.com/prometheus/procfs/internal/util",
+			"revision": "cb4147076ac75738c9a7d279075a253c0cc5acbd",
+			"revisionTime": "2018-01-25T13:30:57Z"
+		},
+		{
+			"checksumSHA1": "BXJH5h2ri8SU5qC6kkDvTIGCky4=",
+			"path": "github.com/prometheus/procfs/nfs",
+			"revision": "cb4147076ac75738c9a7d279075a253c0cc5acbd",
+			"revisionTime": "2018-01-25T13:30:57Z"
+		},
+		{
+			"checksumSHA1": "yItvTQLUVqm/ArLEbvEhqG0T5a0=",
+			"path": "github.com/prometheus/procfs/xfs",
+			"revision": "cb4147076ac75738c9a7d279075a253c0cc5acbd",
+			"revisionTime": "2018-01-25T13:30:57Z"
+		},
+		{
 			"checksumSHA1": "UwtyqB7CaUWPlw0DVJQvw0IFQZs=",
 			"path": "github.com/sergi/go-diff/diffmatchpatch",
 			"revision": "1744e2970ca51c86172c8190fadad617561ed6e7",

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list