Add instrumentation with prometheus
This commit is contained in:
parent
2a72154308
commit
f4ca9e2dbc
6 changed files with 138 additions and 32 deletions
14
lib/instrument/dummy.go
Normal file
14
lib/instrument/dummy.go
Normal file
|
@ -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) {}
|
8
lib/instrument/instrument.go
Normal file
8
lib/instrument/instrument.go
Normal file
|
@ -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)
|
||||||
|
}
|
66
lib/instrument/prometheus.go
Normal file
66
lib/instrument/prometheus.go
Normal file
|
@ -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())
|
||||||
|
}
|
70
lib/stats.go
70
lib/stats.go
|
@ -5,14 +5,15 @@ import (
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"gitlab.com/trantor/trantor/lib/database"
|
"gitlab.com/trantor/trantor/lib/database"
|
||||||
|
"gitlab.com/trantor/trantor/lib/instrument"
|
||||||
"gitlab.com/trantor/trantor/lib/storage"
|
"gitlab.com/trantor/trantor/lib/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
stats_version = 2
|
|
||||||
statsChanSize = 100
|
statsChanSize = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,22 +29,26 @@ type handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatsGatherer struct {
|
type StatsGatherer struct {
|
||||||
db database.DB
|
db database.DB
|
||||||
store storage.Store
|
store storage.Store
|
||||||
template *Template
|
template *Template
|
||||||
hostname string
|
instrument instrument.Instrument
|
||||||
channel chan statsRequest
|
hostname string
|
||||||
ro bool
|
channel chan statsRequest
|
||||||
|
ro bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitStats(database database.DB, store storage.Store, hostname string, template *Template, ro bool) *StatsGatherer {
|
func InitStats(database database.DB, store storage.Store, hostname string, template *Template, ro bool) *StatsGatherer {
|
||||||
|
in := instrument.Init()
|
||||||
|
|
||||||
sg := StatsGatherer{
|
sg := StatsGatherer{
|
||||||
channel: make(chan statsRequest, statsChanSize),
|
channel: make(chan statsRequest, statsChanSize),
|
||||||
db: database,
|
db: database,
|
||||||
store: store,
|
store: store,
|
||||||
template: template,
|
instrument: in,
|
||||||
hostname: hostname,
|
template: template,
|
||||||
ro: ro,
|
hostname: hostname,
|
||||||
|
ro: ro,
|
||||||
}
|
}
|
||||||
|
|
||||||
go sg.worker()
|
go sg.worker()
|
||||||
|
@ -65,33 +70,42 @@ func (sg StatsGatherer) Gather(function func(handler)) func(http.ResponseWriter,
|
||||||
ro: sg.ro,
|
ro: sg.ro,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t0 := time.Now()
|
||||||
function(h)
|
function(h)
|
||||||
sg.channel <- statsRequest{r}
|
t1 := time.Now()
|
||||||
|
sg.channel <- statsRequest{r, t1.Sub(t0)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type statsRequest struct {
|
type statsRequest struct {
|
||||||
r *http.Request
|
r *http.Request
|
||||||
|
duration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg StatsGatherer) worker() {
|
func (sg StatsGatherer) worker() {
|
||||||
for req := range sg.channel {
|
for req := range sg.channel {
|
||||||
var err error
|
var err error
|
||||||
pattern := strings.Split(req.r.URL.Path, "/")
|
|
||||||
id := mux.Vars(req.r)["id"]
|
id := mux.Vars(req.r)["id"]
|
||||||
if len(pattern) > 1 && pattern[1] != "" && id != "" {
|
search := strings.Join(req.r.Form["q"], " ")
|
||||||
switch pattern[1] {
|
fmt := req.r.FormValue("fmt")
|
||||||
case "download":
|
pattern := strings.Split(req.r.URL.Path, "/")
|
||||||
id := mux.Vars(req.r)["id"]
|
|
||||||
err = sg.db.IncDownloads(id)
|
section := "/"
|
||||||
case "book":
|
if len(pattern) > 1 && pattern[1] != "" {
|
||||||
id := mux.Vars(req.r)["id"]
|
section = pattern[1]
|
||||||
err = sg.db.IncViews(id)
|
|
||||||
case "read":
|
|
||||||
id := mux.Vars(req.r)["id"]
|
|
||||||
err = sg.db.IncViews(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
log.Warn("Problem incrementing visits: ", err)
|
log.Warn("Problem incrementing visits: ", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ func UpdateLogger(loggerConfig string) error {
|
||||||
return log.ReplaceLogger(logger)
|
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}"
|
const idPattern = "[0-9a-zA-Z\\-\\_]{16}"
|
||||||
|
|
||||||
r := mux.NewRouter()
|
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(editNewsHandler)).Methods("GET")
|
||||||
r.HandleFunc("/news/edit", sg.Gather(postNewsHandler)).Methods("POST")
|
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) {
|
func fileServer(servePath string, prefix string) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
8
main.go
8
main.go
|
@ -62,6 +62,10 @@ func main() {
|
||||||
trantor.InitUpload(db, store)
|
trantor.InitUpload(db, store)
|
||||||
trantor.InitTasks(db, *loggerConfig)
|
trantor.InitTasks(db, *loggerConfig)
|
||||||
|
|
||||||
trantor.InitRouter(db, sg, *assetsPath)
|
router := trantor.InitRouter(db, sg, *assetsPath)
|
||||||
log.Error(http.ListenAndServe(*httpAddr, nil))
|
server := http.Server{
|
||||||
|
Addr: *httpAddr,
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
log.Error(server.ListenAndServe())
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue