From f4ca9e2dbc4b4e6c16b51daf542f018095dd021e Mon Sep 17 00:00:00 2001 From: Las Zenow Date: Mon, 5 Jun 2017 16:17:14 +0000 Subject: [PATCH] Add instrumentation with prometheus --- lib/instrument/dummy.go | 14 ++++++++ lib/instrument/instrument.go | 8 +++++ lib/instrument/prometheus.go | 66 ++++++++++++++++++++++++++++++++++ lib/stats.go | 70 +++++++++++++++++++++--------------- lib/trantor.go | 4 +-- main.go | 8 +++-- 6 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 lib/instrument/dummy.go create mode 100644 lib/instrument/instrument.go create mode 100644 lib/instrument/prometheus.go diff --git a/lib/instrument/dummy.go b/lib/instrument/dummy.go new file mode 100644 index 0000000..fd71c47 --- /dev/null +++ b/lib/instrument/dummy.go @@ -0,0 +1,14 @@ +// +build noprometheus + +package instrument + +import "time" + +type dummyInst struct{} + +func Init() Instrument { + return &dummyInst{} +} + +func (i dummyInst) Visit(section string, id string, search string, fmt string) {} +func (i dummyInst) Duration(section string, duration time.Duration) {} diff --git a/lib/instrument/instrument.go b/lib/instrument/instrument.go new file mode 100644 index 0000000..a1f5e38 --- /dev/null +++ b/lib/instrument/instrument.go @@ -0,0 +1,8 @@ +package instrument + +import "time" + +type Instrument interface { + Visit(section string, id string, search string, fmt string) + Duration(section string, duration time.Duration) +} diff --git a/lib/instrument/prometheus.go b/lib/instrument/prometheus.go new file mode 100644 index 0000000..bc00fae --- /dev/null +++ b/lib/instrument/prometheus.go @@ -0,0 +1,66 @@ +// +build !noprometheus + +package instrument + +import ( + "time" + + log "github.com/cihub/seelog" + + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +const ( + promAddr = ":9123" +) + +type promInst struct { + visits *prometheus.CounterVec + reqDur *prometheus.HistogramVec +} + +func Init() Instrument { + go promHandle() + + visits := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "trantor_visits_total", + Help: "Number of visits.", + }, + []string{"section", "id", "search", "fmt"}, + ) + reqDur := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "trantor_request_duration_seconds", + Help: "Duration of the request in seconds.", + }, + []string{"section"}, + ) + + prometheus.MustRegister(visits) + prometheus.MustRegister(reqDur) + + return &promInst{ + visits: visits, + reqDur: reqDur, + } +} + +func promHandle() { + server := http.Server{ + Addr: promAddr, + Handler: promhttp.Handler(), + } + log.Error(server.ListenAndServe()) +} + +func (in promInst) Visit(section string, id string, search string, fmt string) { + in.visits.WithLabelValues(section, id, search, fmt).Inc() +} + +func (in promInst) Duration(section string, duration time.Duration) { + in.reqDur.WithLabelValues(section).Observe(duration.Seconds()) +} diff --git a/lib/stats.go b/lib/stats.go index 302c7d7..963c9cf 100644 --- a/lib/stats.go +++ b/lib/stats.go @@ -5,14 +5,15 @@ import ( "net/http" "strings" + "time" "github.com/gorilla/mux" "gitlab.com/trantor/trantor/lib/database" + "gitlab.com/trantor/trantor/lib/instrument" "gitlab.com/trantor/trantor/lib/storage" ) const ( - stats_version = 2 statsChanSize = 100 ) @@ -28,22 +29,26 @@ type handler struct { } type StatsGatherer struct { - db database.DB - store storage.Store - template *Template - hostname string - channel chan statsRequest - ro bool + db database.DB + store storage.Store + template *Template + instrument instrument.Instrument + hostname string + channel chan statsRequest + ro bool } func InitStats(database database.DB, store storage.Store, hostname string, template *Template, ro bool) *StatsGatherer { + in := instrument.Init() + sg := StatsGatherer{ - channel: make(chan statsRequest, statsChanSize), - db: database, - store: store, - template: template, - hostname: hostname, - ro: ro, + channel: make(chan statsRequest, statsChanSize), + db: database, + store: store, + instrument: in, + template: template, + hostname: hostname, + ro: ro, } go sg.worker() @@ -65,33 +70,42 @@ func (sg StatsGatherer) Gather(function func(handler)) func(http.ResponseWriter, ro: sg.ro, } + t0 := time.Now() function(h) - sg.channel <- statsRequest{r} + t1 := time.Now() + sg.channel <- statsRequest{r, t1.Sub(t0)} } } type statsRequest struct { - r *http.Request + r *http.Request + duration time.Duration } func (sg StatsGatherer) worker() { for req := range sg.channel { var err error - pattern := strings.Split(req.r.URL.Path, "/") id := mux.Vars(req.r)["id"] - if len(pattern) > 1 && pattern[1] != "" && id != "" { - switch pattern[1] { - case "download": - id := mux.Vars(req.r)["id"] - err = sg.db.IncDownloads(id) - case "book": - id := mux.Vars(req.r)["id"] - err = sg.db.IncViews(id) - case "read": - id := mux.Vars(req.r)["id"] - err = sg.db.IncViews(id) - } + search := strings.Join(req.r.Form["q"], " ") + fmt := req.r.FormValue("fmt") + pattern := strings.Split(req.r.URL.Path, "/") + + section := "/" + if len(pattern) > 1 && pattern[1] != "" { + section = pattern[1] } + + sg.instrument.Visit(section, id, search, fmt) + sg.instrument.Duration(section, req.duration*time.Microsecond) + switch section { + case "download": + err = sg.db.IncDownloads(id) + case "book": + err = sg.db.IncViews(id) + case "read": + err = sg.db.IncViews(id) + } + if err != nil { log.Warn("Problem incrementing visits: ", err) } diff --git a/lib/trantor.go b/lib/trantor.go index dc96aa0..97520d4 100644 --- a/lib/trantor.go +++ b/lib/trantor.go @@ -152,7 +152,7 @@ func UpdateLogger(loggerConfig string) error { return log.ReplaceLogger(logger) } -func InitRouter(db database.DB, sg *StatsGatherer, assetsPath string) { +func InitRouter(db database.DB, sg *StatsGatherer, assetsPath string) http.Handler { const idPattern = "[0-9a-zA-Z\\-\\_]{16}" r := mux.NewRouter() @@ -203,7 +203,7 @@ func InitRouter(db database.DB, sg *StatsGatherer, assetsPath string) { r.HandleFunc("/news/edit", sg.Gather(editNewsHandler)).Methods("GET") r.HandleFunc("/news/edit", sg.Gather(postNewsHandler)).Methods("POST") - http.Handle("/", r) + return r } func fileServer(servePath string, prefix string) func(w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index 927c89b..65e39fb 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,10 @@ func main() { trantor.InitUpload(db, store) trantor.InitTasks(db, *loggerConfig) - trantor.InitRouter(db, sg, *assetsPath) - log.Error(http.ListenAndServe(*httpAddr, nil)) + router := trantor.InitRouter(db, sg, *assetsPath) + server := http.Server{ + Addr: *httpAddr, + Handler: router, + } + log.Error(server.ListenAndServe()) }