Skip to content

Commit

Permalink
Adding an option to ignore some routes, simplified behavior and remov…
Browse files Browse the repository at this point in the history
…ing unnecessary check
  • Loading branch information
Paul Lhussiez committed Jan 3, 2018
1 parent 0c28c9e commit b8b7d9f
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 19 deletions.
60 changes: 41 additions & 19 deletions prom.go
Expand Up @@ -18,13 +18,19 @@ type pmap struct {
values map[string]string
}

type pmapb struct {
sync.RWMutex
values map[string]bool
}

// Prometheus contains the metrics gathered by the instance and its path
type Prometheus struct {
reqCnt *prometheus.CounterVec
reqDur, reqSz, resSz prometheus.Summary

MetricsPath string
Subsystem string
Ignored pmapb
Engine *gin.Engine
PathMap pmap
}
Expand All @@ -37,6 +43,17 @@ func Path(path string) func(*Prometheus) {
}
}

// Ignore is used to disable instrumentation on some routes
func Ignore(paths ...string) func(*Prometheus) {
return func(p *Prometheus) {
p.Ignored.Lock()
defer p.Ignored.Unlock()
for _, path := range paths {
p.Ignored.values[path] = true
}
}
}

// Subsystem is an option allowing to set the subsystem when intitializing
// with New.
// Example : ginprom.New(ginprom.Subsystem("my_system"))
Expand Down Expand Up @@ -65,38 +82,41 @@ func New(options ...func(*Prometheus)) *Prometheus {
MetricsPath: defaultPath,
Subsystem: defaultSys,
}
p.Ignored.values = make(map[string]bool)
for _, option := range options {
option(p)
}
p.register()
if p.Engine != nil {
p.Engine.GET(p.MetricsPath, prometheusHandler())
}
p.PathMap.values = make(map[string]string)

return p
}

func (p *Prometheus) updatePathMap() {
func (p *Prometheus) update() {
if p.PathMap.values == nil {
p.PathMap.values = make(map[string]string)
}
p.PathMap.Lock()
defer p.PathMap.Unlock()
p.Ignored.RLock()
defer func() {
p.PathMap.Unlock()
p.Ignored.RUnlock()
}()
for _, ri := range p.Engine.Routes() {
if _, ok := p.Ignored.values[ri.Path]; ok {
continue
}
p.PathMap.values[ri.Handler] = ri.Path
}
}

func (p *Prometheus) getPathFromHandler(handler string) string {
func (p *Prometheus) get(handler string) (string, bool) {
p.PathMap.RLock()
defer p.PathMap.RUnlock()
if in, ok := p.PathMap.values[handler]; ok {
return in
}
p.PathMap.RUnlock()
p.updatePathMap()
p.PathMap.RLock()
if in, ok := p.PathMap.values[handler]; ok {
return in
}
return ""
in, ok := p.PathMap.values[handler]
return in, ok
}

func (p *Prometheus) register() {
Expand Down Expand Up @@ -141,18 +161,20 @@ func (p *Prometheus) register() {
// Instrument is a gin middleware that can be used to generate metrics for a
// single handler
func (p *Prometheus) Instrument() gin.HandlerFunc {
p.updatePathMap()
return func(c *gin.Context) {
if p.PathMap.values == nil {
p.update()
}
var path string
var found bool

start := time.Now()
reqSz := computeApproximateRequestSize(c.Request)

if c.Request.URL.String() == p.MetricsPath {
if path, found = p.get(c.HandlerName()); !found {
c.Next()
return
}

path = p.getPathFromHandler(c.HandlerName())
reqSz := computeApproximateRequestSize(c.Request)

c.Next()

Expand Down
40 changes: 40 additions & 0 deletions prom_test.go
Expand Up @@ -115,3 +115,43 @@ func TestInstrument(t *testing.T) {
})
unregister(p)
}

func TestIgnore(t *testing.T) {
r := gin.New()
ipath := "/ping"
lipath := fmt.Sprintf(`path="%s"`, ipath)
p := New(Engine(r), Ignore(ipath))
r.Use(p.Instrument())

r.GET(ipath, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})

g := gofight.New()
g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
assert.NotContains(t, r.Body.String(), `requests_total`)
})

g.GET("/ping").Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) })

g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
assert.NotContains(t, r.Body.String(), `requests_total`)
assert.NotContains(t, r.Body.String(), lipath, "ignored path must not be present")
})
unregister(p)
}

func TestMetricsPathIgnored(t *testing.T) {
r := gin.New()
p := New(Engine(r))
r.Use(p.Instrument())

g := gofight.New()
g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
assert.NotContains(t, r.Body.String(), `requests_total`)
})
unregister(p)
}

0 comments on commit b8b7d9f

Please sign in to comment.