"time"
"git.curoverse.com/arvados.git/sdk/go/stats"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
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"),
})
}
-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(),
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
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)
}
func (w *responseWriter) WroteStatus() int {
+ if w.wroteStatus == 0 {
+ return http.StatusOK
+ }
return w.wroteStatus
}
"time"
"git.curoverse.com/arvados.git/sdk/go/arvados"
- log "github.com/Sirupsen/logrus"
"github.com/curoverse/azure-sdk-for-go/storage"
)
"testing"
"time"
- log "github.com/Sirupsen/logrus"
"github.com/curoverse/azure-sdk-for-go/storage"
check "gopkg.in/check.v1"
)
"sync"
"sync/atomic"
"time"
-
- log "github.com/Sirupsen/logrus"
)
type bufferPool struct {
package main
import (
- . "gopkg.in/check.v1"
"time"
+
+ . "gopkg.in/check.v1"
)
var _ = Suite(&BufferPoolSuite{})
"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 {
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"
// 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 {
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{}
package main
import (
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
func init() {
+ log.Level = logrus.DebugLevel
theConfig.debugLogf = log.Printf
}
"git.curoverse.com/arvados.git/sdk/go/health"
"git.curoverse.com/arvados.git/sdk/go/httpserver"
- log "github.com/Sirupsen/logrus"
)
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.
"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"
)
// 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)
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)
}
package main
import (
+ "bytes"
"context"
"encoding/json"
"net/http"
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) {
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
// 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")
}
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)
}
"time"
"git.curoverse.com/arvados.git/sdk/go/keepclient"
-
- log "github.com/Sirupsen/logrus"
)
// RunPullWorker receives PullRequests from pullq, invokes
"git.curoverse.com/arvados.git/sdk/go/arvados"
"github.com/AdRoll/goamz/aws"
"github.com/AdRoll/goamz/s3"
- log "github.com/Sirupsen/logrus"
)
const (
"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"
)
"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.
"sync"
"syscall"
"time"
-
- log "github.com/Sirupsen/logrus"
)
type unixVolumeAdder struct {
"revision": "d682213848ed68c0a260ca37d6dd5ace8423f5ba",
"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": "160de10b2537169b5ae3e7e221d28269ef40d311",
"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",
"revision": "83612a56d3dd153a94a629cd64925371c9adad78",
"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": "e881fd58d78e04cf6d0de1217f8707c8cc2249bc",
"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",