mirror of
https://github.com/distribution/distribution
synced 2024-11-12 05:45:51 +01:00
1d33874951
Go 1.13 and up enforce import paths to be versioned if a project contains a go.mod and has released v2 or up. The current v2.x branches (and releases) do not yet have a go.mod, and therefore are still allowed to be imported with a non-versioned import path (go modules add a `+incompatible` annotation in that case). However, now that this project has a `go.mod` file, incompatible import paths will not be accepted by go modules, and attempting to use code from this repository will fail. This patch uses `v3` for the import-paths (not `v2`), because changing import paths itself is a breaking change, which means that the next release should increment the "major" version to comply with SemVer (as go modules dictate). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
184 lines
5.4 KiB
Go
184 lines
5.4 KiB
Go
package notifications
|
|
|
|
import (
|
|
"expvar"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
|
|
prometheus "github.com/distribution/distribution/v3/metrics"
|
|
events "github.com/docker/go-events"
|
|
"github.com/docker/go-metrics"
|
|
)
|
|
|
|
var (
|
|
// eventsCounter counts total events of incoming, success, failure, and errors
|
|
eventsCounter = prometheus.NotificationsNamespace.NewLabeledCounter("events", "The number of total events", "type", "endpoint")
|
|
// pendingGauge measures the pending queue size
|
|
pendingGauge = prometheus.NotificationsNamespace.NewLabeledGauge("pending", "The gauge of pending events in queue", metrics.Total, "endpoint")
|
|
// statusCounter counts the total notification call per each status code
|
|
statusCounter = prometheus.NotificationsNamespace.NewLabeledCounter("status", "The number of status code", "code", "endpoint")
|
|
)
|
|
|
|
// EndpointMetrics track various actions taken by the endpoint, typically by
|
|
// number of events. The goal of this to export it via expvar but we may find
|
|
// some other future solution to be better.
|
|
type EndpointMetrics struct {
|
|
Pending int // events pending in queue
|
|
Events int // total events incoming
|
|
Successes int // total events written successfully
|
|
Failures int // total events failed
|
|
Errors int // total events errored
|
|
Statuses map[string]int // status code histogram, per call event
|
|
}
|
|
|
|
// safeMetrics guards the metrics implementation with a lock and provides a
|
|
// safe update function.
|
|
type safeMetrics struct {
|
|
EndpointName string
|
|
EndpointMetrics
|
|
sync.Mutex // protects statuses map
|
|
}
|
|
|
|
// newSafeMetrics returns safeMetrics with map allocated.
|
|
func newSafeMetrics(name string) *safeMetrics {
|
|
var sm safeMetrics
|
|
sm.Statuses = make(map[string]int)
|
|
sm.EndpointName = name
|
|
return &sm
|
|
}
|
|
|
|
// httpStatusListener returns the listener for the http sink that updates the
|
|
// relevant counters.
|
|
func (sm *safeMetrics) httpStatusListener() httpStatusListener {
|
|
return &endpointMetricsHTTPStatusListener{
|
|
safeMetrics: sm,
|
|
}
|
|
}
|
|
|
|
// eventQueueListener returns a listener that maintains queue related counters.
|
|
func (sm *safeMetrics) eventQueueListener() eventQueueListener {
|
|
return &endpointMetricsEventQueueListener{
|
|
safeMetrics: sm,
|
|
}
|
|
}
|
|
|
|
// endpointMetricsHTTPStatusListener increments counters related to http sinks
|
|
// for the relevant events.
|
|
type endpointMetricsHTTPStatusListener struct {
|
|
*safeMetrics
|
|
}
|
|
|
|
var _ httpStatusListener = &endpointMetricsHTTPStatusListener{}
|
|
|
|
func (emsl *endpointMetricsHTTPStatusListener) success(status int, event events.Event) {
|
|
emsl.safeMetrics.Lock()
|
|
defer emsl.safeMetrics.Unlock()
|
|
emsl.Statuses[fmt.Sprintf("%d %s", status, http.StatusText(status))]++
|
|
emsl.Successes++
|
|
|
|
statusCounter.WithValues(fmt.Sprintf("%d %s", status, http.StatusText(status)), emsl.EndpointName).Inc(1)
|
|
eventsCounter.WithValues("Successes", emsl.EndpointName).Inc(1)
|
|
}
|
|
|
|
func (emsl *endpointMetricsHTTPStatusListener) failure(status int, event events.Event) {
|
|
emsl.safeMetrics.Lock()
|
|
defer emsl.safeMetrics.Unlock()
|
|
emsl.Statuses[fmt.Sprintf("%d %s", status, http.StatusText(status))]++
|
|
emsl.Failures++
|
|
|
|
statusCounter.WithValues(fmt.Sprintf("%d %s", status, http.StatusText(status)), emsl.EndpointName).Inc(1)
|
|
eventsCounter.WithValues("Failures", emsl.EndpointName).Inc(1)
|
|
}
|
|
|
|
func (emsl *endpointMetricsHTTPStatusListener) err(err error, event events.Event) {
|
|
emsl.safeMetrics.Lock()
|
|
defer emsl.safeMetrics.Unlock()
|
|
emsl.Errors++
|
|
|
|
eventsCounter.WithValues("Errors", emsl.EndpointName).Inc(1)
|
|
}
|
|
|
|
// endpointMetricsEventQueueListener maintains the incoming events counter and
|
|
// the queues pending count.
|
|
type endpointMetricsEventQueueListener struct {
|
|
*safeMetrics
|
|
}
|
|
|
|
func (eqc *endpointMetricsEventQueueListener) ingress(event events.Event) {
|
|
eqc.Lock()
|
|
defer eqc.Unlock()
|
|
eqc.Events++
|
|
eqc.Pending++
|
|
|
|
eventsCounter.WithValues("Events", eqc.EndpointName).Inc()
|
|
pendingGauge.WithValues(eqc.EndpointName).Inc(1)
|
|
}
|
|
|
|
func (eqc *endpointMetricsEventQueueListener) egress(event events.Event) {
|
|
eqc.Lock()
|
|
defer eqc.Unlock()
|
|
eqc.Pending--
|
|
|
|
pendingGauge.WithValues(eqc.EndpointName).Dec(1)
|
|
}
|
|
|
|
// endpoints is global registry of endpoints used to report metrics to expvar
|
|
var endpoints struct {
|
|
registered []*Endpoint
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// register places the endpoint into expvar so that stats are tracked.
|
|
func register(e *Endpoint) {
|
|
endpoints.mu.Lock()
|
|
defer endpoints.mu.Unlock()
|
|
|
|
endpoints.registered = append(endpoints.registered, e)
|
|
}
|
|
|
|
func init() {
|
|
// NOTE(stevvooe): Setup registry metrics structure to report to expvar.
|
|
// Ideally, we do more metrics through logging but we need some nice
|
|
// realtime metrics for queue state for now.
|
|
|
|
registry := expvar.Get("registry")
|
|
|
|
if registry == nil {
|
|
registry = expvar.NewMap("registry")
|
|
}
|
|
|
|
var notifications expvar.Map
|
|
notifications.Init()
|
|
notifications.Set("endpoints", expvar.Func(func() interface{} {
|
|
endpoints.mu.Lock()
|
|
defer endpoints.mu.Unlock()
|
|
|
|
var names []interface{}
|
|
for _, v := range endpoints.registered {
|
|
var epjson struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
EndpointConfig
|
|
|
|
Metrics EndpointMetrics
|
|
}
|
|
|
|
epjson.Name = v.Name()
|
|
epjson.URL = v.URL()
|
|
epjson.EndpointConfig = v.EndpointConfig
|
|
|
|
v.ReadMetrics(&epjson.Metrics)
|
|
|
|
names = append(names, epjson)
|
|
}
|
|
|
|
return names
|
|
}))
|
|
|
|
registry.(*expvar.Map).Set("notifications", ¬ifications)
|
|
|
|
// register prometheus metrics
|
|
metrics.Register(prometheus.NotificationsNamespace)
|
|
}
|