gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/monitoring/prometheus/prometheus.go (about)

     1  package prometheus
     2  
     3  import (
     4  	"github.com/gorilla/mux"
     5  	"github.com/prometheus/client_golang/prometheus"
     6  	"github.com/prometheus/client_golang/prometheus/promhttp"
     7  	"github.com/urfave/negroni"
     8  	"gitlab.com/ignitionrobotics/web/ign-go/monitoring"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  const (
    17  	// defaultMetricsRoute is the default route used for the metrics handler Prometheus calls.
    18  	defaultMetricsRoute = "/metrics"
    19  )
    20  
    21  var (
    22  	// RequestDurationSeconds tracks the seconds spent serving HTTP requests.
    23  	// Used to monitor service status and raise alerts (using the RED method)
    24  	RequestDurationSeconds *prometheus.HistogramVec
    25  	// TotalRequests tracks the total number of HTTP requests.
    26  	// Used for auto-scaling
    27  	TotalRequests *prometheus.CounterVec
    28  
    29  	// invalidCharsRE is a regex used to convert a route to a compatible Prometheus label value
    30  	invalidCharsRE = regexp.MustCompile(`[^a-zA-Z0-9]+`)
    31  )
    32  
    33  // provider is an implementation of a monitoring provider that generates Prometheus metrics.
    34  type provider struct {
    35  	// route is the route that the metrics handle will be served from.
    36  	route string
    37  	// requestDurationSeconds contains the RequestDurationSeconds metric
    38  	requestDurationSeconds *prometheus.HistogramVec
    39  	// totalRequests contains the TotalRequests metric
    40  	totalRequests *prometheus.CounterVec
    41  }
    42  
    43  // init initializes Prometheus metrics.
    44  func init() {
    45  	// RequestDurationSeconds
    46  	RequestDurationSeconds = prometheus.NewHistogramVec(
    47  		prometheus.HistogramOpts{
    48  			Subsystem: "http",
    49  			Name:      "request_duration_seconds",
    50  			Help:      "Seconds spent serving HTTP requests.",
    51  			Buckets:   prometheus.DefBuckets,
    52  		},
    53  		[]string{"method", "path", "status"},
    54  	)
    55  	prometheus.MustRegister(RequestDurationSeconds)
    56  
    57  	// TotalRequests
    58  	TotalRequests = prometheus.NewCounterVec(
    59  		prometheus.CounterOpts{
    60  			Subsystem: "http",
    61  			Name:      "requests_total",
    62  			Help:      "The total number of HTTP requests.",
    63  		},
    64  		[]string{"status"},
    65  	)
    66  	prometheus.MustRegister(TotalRequests)
    67  }
    68  
    69  // NewPrometheusProvider creates a new Prometheus metrics provider.
    70  // route is the route the Prometheus server will contact to get metric information.
    71  // If route is an empty string, it will default to "/metrics".
    72  // TODO Provide a mechanism to define additional application-specific metrics.
    73  func NewPrometheusProvider(route string) monitoring.Provider {
    74  	// Default route
    75  	if route == "" {
    76  		route = defaultMetricsRoute
    77  	}
    78  
    79  	return &provider{
    80  		route:                  route,
    81  		requestDurationSeconds: RequestDurationSeconds,
    82  		totalRequests:          TotalRequests,
    83  	}
    84  }
    85  
    86  // MetricsRoute returns the route to the metrics endpoint.
    87  func (p *provider) MetricsRoute() string {
    88  	return p.route
    89  }
    90  
    91  // MetricsHandler returns an HTTP handler to use with the metrics endpoint.
    92  // It uses the default Prometheus handler.
    93  func (p *provider) MetricsHandler() http.Handler {
    94  	return promhttp.Handler()
    95  }
    96  
    97  // Middleware returns a middleware that is used to gather metrics from incoming requests.
    98  func (p *provider) Middleware(args ...interface{}) negroni.Handler {
    99  	return negroni.HandlerFunc(
   100  		func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
   101  			begin := time.Now()
   102  
   103  			// An interceptor is used to access HTTP response properties
   104  			interceptor := negroni.NewResponseWriter(w)
   105  
   106  			// Process the request
   107  			next.ServeHTTP(interceptor, r)
   108  
   109  			// Process request and response values
   110  			path := p.getRouteName(r)
   111  			status := strconv.Itoa(interceptor.Status())
   112  			took := time.Since(begin).Seconds()
   113  
   114  			// Register metric data
   115  			p.requestDurationSeconds.WithLabelValues(r.Method, path, status).Observe(took)
   116  			p.totalRequests.WithLabelValues(status).Inc()
   117  		},
   118  	)
   119  }
   120  
   121  // getRouteName converts routes from a path to a string compatible with a Prometheus label value.
   122  // Example: '/api/delay/{example}' to 'api_delay_example'
   123  func (p *provider) getRouteName(r *http.Request) string {
   124  	if mux.CurrentRoute(r) != nil {
   125  		if name := mux.CurrentRoute(r).GetName(); len(name) > 0 {
   126  			return p.urlToLabel(name)
   127  		} else if path, err := mux.CurrentRoute(r).GetPathTemplate(); err == nil && len(path) > 0 {
   128  			return p.urlToLabel(path)
   129  		}
   130  	}
   131  	return p.urlToLabel(r.URL.Path)
   132  }
   133  
   134  // urlToLabel converts a URL path to a string compatible with a Prometheus label value.
   135  func (p *provider) urlToLabel(path string) string {
   136  	result := invalidCharsRE.ReplaceAllString(path, "_")
   137  	result = strings.ToLower(strings.Trim(result, "_"))
   138  	if result == "" {
   139  		result = "root"
   140  	}
   141  	return result
   142  }