summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/rr/http/metrics.go86
-rw-r--r--cmd/rr/main.go4
-rw-r--r--go.mod5
-rw-r--r--service/metrics/config.go23
-rw-r--r--service/metrics/service.go53
5 files changed, 168 insertions, 3 deletions
diff --git a/cmd/rr/http/metrics.go b/cmd/rr/http/metrics.go
new file mode 100644
index 00000000..f9d3c079
--- /dev/null
+++ b/cmd/rr/http/metrics.go
@@ -0,0 +1,86 @@
+package http
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/spf13/cobra"
+ rr "github.com/spiral/roadrunner/cmd/rr/cmd"
+ rrhttp "github.com/spiral/roadrunner/service/http"
+ "github.com/spiral/roadrunner/service/metrics"
+ "strconv"
+)
+
+func init() {
+ cobra.OnInitialize(func() {
+ svc, _ := rr.Container.Get(metrics.ID)
+ mtr, ok := svc.(*metrics.Service)
+ if !ok || !mtr.Enabled() {
+ return
+ }
+
+ ht, _ := rr.Container.Get(rrhttp.ID)
+ if ht, ok := ht.(*rrhttp.Service); ok {
+ collector := newCollector()
+
+ // register metrics
+ mtr.MustRegister(collector.requestCounter)
+ mtr.MustRegister(collector.requestDuration)
+
+ // collect events
+ ht.AddListener(collector.listener)
+ }
+ })
+}
+
+// listener provide debug callback for system events. With colors!
+type metricCollector struct {
+ requestCounter *prometheus.CounterVec
+ requestDuration *prometheus.HistogramVec
+}
+
+func newCollector() *metricCollector {
+ return &metricCollector{
+ requestCounter: prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "rr_http_total",
+ Help: "Total number of handled http requests after server restart.",
+ },
+ []string{"status"},
+ ),
+ requestDuration: prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "rr_http_request_duration",
+ Help: "HTTP request duration.",
+ Buckets: []float64{0.25, 0.5, 1, 10, 20, 60},
+ },
+ []string{"status"},
+ ),
+ }
+}
+
+// listener listens to http events and generates nice looking output.
+func (c *metricCollector) listener(event int, ctx interface{}) {
+ // http events
+ switch event {
+ case rrhttp.EventResponse:
+ e := ctx.(*rrhttp.ResponseEvent)
+
+ c.requestCounter.With(prometheus.Labels{
+ "status": strconv.Itoa(e.Response.Status),
+ }).Inc()
+
+ c.requestDuration.With(prometheus.Labels{
+ "status": strconv.Itoa(e.Response.Status),
+ }).Observe(e.Elapsed().Seconds())
+
+ case rrhttp.EventError:
+ e := ctx.(*rrhttp.ErrorEvent)
+
+ c.requestCounter.With(prometheus.Labels{
+ "status": "500",
+ }).Inc()
+
+ c.requestDuration.With(prometheus.Labels{
+ "status": "500",
+ }).Observe(e.Elapsed().Seconds())
+ }
+}
diff --git a/cmd/rr/main.go b/cmd/rr/main.go
index 6f325fb4..6fb10ba6 100644
--- a/cmd/rr/main.go
+++ b/cmd/rr/main.go
@@ -24,12 +24,13 @@ package main
import (
rr "github.com/spiral/roadrunner/cmd/rr/cmd"
- "github.com/spiral/roadrunner/service/headers"
// services (plugins)
"github.com/spiral/roadrunner/service/env"
+ "github.com/spiral/roadrunner/service/headers"
"github.com/spiral/roadrunner/service/http"
"github.com/spiral/roadrunner/service/limit"
+ "github.com/spiral/roadrunner/service/metrics"
"github.com/spiral/roadrunner/service/rpc"
"github.com/spiral/roadrunner/service/static"
@@ -42,6 +43,7 @@ func main() {
rr.Container.Register(env.ID, &env.Service{})
rr.Container.Register(rpc.ID, &rpc.Service{})
rr.Container.Register(http.ID, &http.Service{})
+ rr.Container.Register(metrics.ID, &metrics.Service{})
rr.Container.Register(headers.ID, &headers.Service{})
rr.Container.Register(static.ID, &static.Service{})
rr.Container.Register(limit.ID, &limit.Service{})
diff --git a/go.mod b/go.mod
index 8ec6a1fb..104239f4 100644
--- a/go.mod
+++ b/go.mod
@@ -13,15 +13,16 @@ require (
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/olekukonko/tablewriter v0.0.1
github.com/pkg/errors v0.8.1
+ github.com/prometheus/client_golang v1.0.0
github.com/shirou/gopsutil v2.17.12+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/sirupsen/logrus v1.3.0
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.1
github.com/spiral/goridge v2.1.3+incompatible
- github.com/stretchr/testify v1.2.2
+ github.com/stretchr/testify v1.3.0
github.com/yookoala/gofast v0.3.0
- golang.org/x/net v0.0.0-20181017193950-04a2e542c03f
+ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
diff --git a/service/metrics/config.go b/service/metrics/config.go
new file mode 100644
index 00000000..799ba2d2
--- /dev/null
+++ b/service/metrics/config.go
@@ -0,0 +1,23 @@
+package metrics
+
+import "github.com/spiral/roadrunner/service"
+
+type Config struct {
+ // Address to listen
+ Address string
+
+ // Metrics define application specific metrics.
+ Metrics map[string]Metric
+}
+
+// Metric describes single application specific metric.
+type Metric struct {
+ Type string
+ Description string
+ Labels []string
+}
+
+// Hydrate configuration.
+func (c *Config) Hydrate(cfg service.Config) error {
+ return cfg.Unmarshal(c)
+}
diff --git a/service/metrics/service.go b/service/metrics/service.go
new file mode 100644
index 00000000..50b533f8
--- /dev/null
+++ b/service/metrics/service.go
@@ -0,0 +1,53 @@
+package metrics
+
+import (
+ "context"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "net/http"
+)
+
+// ID declares public service name.
+const ID = "metrics"
+
+// Service to manage application metrics using Prometheus.
+type Service struct {
+ cfg *Config
+ http *http.Server
+}
+
+// Init service.
+func (s *Service) Init(cfg *Config) (bool, error) {
+ s.cfg = cfg
+ return true, nil
+}
+
+// Enabled indicates that server is able to collect metrics.
+func (s *Service) Enabled() bool {
+ return s.cfg != nil
+}
+
+// Register new prometheus collector.
+func (s *Service) Register(c prometheus.Collector) error {
+ return prometheus.Register(c)
+}
+
+// MustRegister registers new collector or fails with panic.
+func (s *Service) MustRegister(c prometheus.Collector) {
+ if err := prometheus.Register(c); err != nil {
+ panic(err)
+ }
+}
+
+// Serve prometheus metrics service.
+func (s *Service) Serve() error {
+ s.http = &http.Server{Addr: s.cfg.Address, Handler: promhttp.Handler()}
+
+ return s.http.ListenAndServe()
+}
+
+// Stop prometheus metrics service.
+func (s *Service) Stop() {
+ // gracefully stop server
+ s.http.Shutdown(context.Background())
+}