fshttp: add prometheus metrics for HTTP status code

This patch adds rclone_http_status_code counter vector labeled by

* host,
* method,
* code.

It allows to see HTTP errors, backoffs etc.

The Metrics struct is designed for extensibility.
Adding new metrics is a matter of adding them to Metrics struct and including them in the response handling.

This feature has been discussed in the forum [1].

[1] https://forum.rclone.org/t/prometheus-metrics/14484
This commit is contained in:
Michał Matczuk 2021-09-06 16:42:23 +02:00 committed by Ivan Andreev
parent ba6730720d
commit 38dc3e93ee
3 changed files with 67 additions and 4 deletions

View File

@ -136,12 +136,14 @@ func NewClient(ctx context.Context) *http.Client {
// Transport is our http Transport which wraps an http.Transport // Transport is our http Transport which wraps an http.Transport
// * Sets the User Agent // * Sets the User Agent
// * Does logging // * Does logging
// * Updates metrics
type Transport struct { type Transport struct {
*http.Transport *http.Transport
dump fs.DumpFlags dump fs.DumpFlags
filterRequest func(req *http.Request) filterRequest func(req *http.Request)
userAgent string userAgent string
headers []*fs.HTTPOption headers []*fs.HTTPOption
metrics *Metrics
} }
// newTransport wraps the http.Transport passed in and logs all // newTransport wraps the http.Transport passed in and logs all
@ -152,6 +154,7 @@ func newTransport(ci *fs.ConfigInfo, transport *http.Transport) *Transport {
dump: ci.Dump, dump: ci.Dump,
userAgent: ci.UserAgent, userAgent: ci.UserAgent,
headers: ci.Headers, headers: ci.Headers,
metrics: DefaultMetrics,
} }
} }
@ -283,6 +286,9 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error
fs.Debugf(nil, "%s", separatorResp) fs.Debugf(nil, "%s", separatorResp)
logMutex.Unlock() logMutex.Unlock()
} }
// Update metrics
t.metrics.onResponse(req, resp)
if err == nil { if err == nil {
checkServerTime(req, resp) checkServerTime(req, resp)
} }

51
fs/fshttp/prometheus.go Normal file
View File

@ -0,0 +1,51 @@
package fshttp
import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
)
// Metrics provide Transport HTTP level metrics.
type Metrics struct {
StatusCode *prometheus.CounterVec
}
// NewMetrics creates a new metrics instance, the instance shall be assigned to
// DefaultMetrics before any processing takes place.
func NewMetrics(namespace string) *Metrics {
return &Metrics{
StatusCode: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: "http",
Name: "status_code",
}, []string{"host", "method", "code"}),
}
}
// DefaultMetrics specifies metrics used for new Transports.
var DefaultMetrics = (*Metrics)(nil)
// Collectors returns all prometheus metrics as collectors for registration.
func (m *Metrics) Collectors() []prometheus.Collector {
if m == nil {
return nil
}
return []prometheus.Collector{
m.StatusCode,
}
}
func (m *Metrics) onResponse(req *http.Request, resp *http.Response) {
if m == nil {
return
}
var statusCode = 0
if resp != nil {
statusCode = resp.StatusCode
}
m.StatusCode.WithLabelValues(req.Host, req.Method, fmt.Sprint(statusCode)).Inc()
}

View File

@ -18,23 +18,22 @@ import (
"sync" "sync"
"time" "time"
"github.com/rclone/rclone/fs/rc/webgui"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/skratchdot/open-golang/open"
"github.com/rclone/rclone/cmd/serve/httplib" "github.com/rclone/rclone/cmd/serve/httplib"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/cache" "github.com/rclone/rclone/fs/cache"
"github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/list" "github.com/rclone/rclone/fs/list"
"github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/rc"
"github.com/rclone/rclone/fs/rc/jobs" "github.com/rclone/rclone/fs/rc/jobs"
"github.com/rclone/rclone/fs/rc/rcflags" "github.com/rclone/rclone/fs/rc/rcflags"
"github.com/rclone/rclone/fs/rc/webgui"
"github.com/rclone/rclone/lib/http/serve" "github.com/rclone/rclone/lib/http/serve"
"github.com/rclone/rclone/lib/random" "github.com/rclone/rclone/lib/random"
"github.com/skratchdot/open-golang/open"
) )
var promHandler http.Handler var promHandler http.Handler
@ -43,6 +42,13 @@ var onlyOnceWarningAllowOrigin sync.Once
func init() { func init() {
rcloneCollector := accounting.NewRcloneCollector(context.Background()) rcloneCollector := accounting.NewRcloneCollector(context.Background())
prometheus.MustRegister(rcloneCollector) prometheus.MustRegister(rcloneCollector)
m := fshttp.NewMetrics("rclone")
for _, c := range m.Collectors() {
prometheus.MustRegister(c)
}
fshttp.DefaultMetrics = m
promHandler = promhttp.Handler() promHandler = promhttp.Handler()
} }