Skip to content

Commit c96fa96

Browse files
author
Oleg Bulatov
committed
Add pullthrough metrics
1 parent ef08162 commit c96fa96

16 files changed

+741
-84
lines changed

Diff for: pkg/dockerregistry/server/app.go

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
1414
registryconfig "github.com/openshift/image-registry/pkg/dockerregistry/server/configuration"
1515
"github.com/openshift/image-registry/pkg/dockerregistry/server/maxconnections"
16+
"github.com/openshift/image-registry/pkg/dockerregistry/server/metrics"
1617
"github.com/openshift/image-registry/pkg/dockerregistry/server/supermiddleware"
1718
)
1819

@@ -55,6 +56,9 @@ type App struct {
5556

5657
// cache is a shared cache of digests and descriptors.
5758
cache cache.DigestCache
59+
60+
// metrics provide methods to collect statistics.
61+
metrics metrics.Metrics
5862
}
5963

6064
func (app *App) Storage(driver storagedriver.StorageDriver, options map[string]interface{}) (storagedriver.StorageDriver, error) {
@@ -85,6 +89,12 @@ func NewApp(ctx context.Context, registryClient client.RegistryClient, dockerCon
8589
quotaEnforcing: newQuotaEnforcingConfig(ctx, extraConfig.Quota),
8690
}
8791

92+
if app.config.Metrics.Enabled {
93+
app.metrics = metrics.NewMetrics(metrics.NewPrometheusSink())
94+
} else {
95+
app.metrics = metrics.NewNoopMetrics()
96+
}
97+
8898
cacheTTL := time.Duration(0)
8999
if !app.config.Cache.Disabled {
90100
cacheTTL = app.config.Cache.BlobRepositoryTTL

Diff for: pkg/dockerregistry/server/metrichandler.go

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
)
1212

1313
func RegisterMetricHandler(app *handlers.App) {
14-
metrics.Register()
1514
getMetricsAccess := func(r *http.Request) []auth.Access {
1615
return []auth.Access{
1716
{

Diff for: pkg/dockerregistry/server/metrics/cache.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package metrics
2+
3+
// Cache provides generic metrics for caches.
4+
type Cache interface {
5+
Request(hit bool)
6+
}
7+
8+
type cache struct {
9+
hitCounter Counter
10+
missCounter Counter
11+
}
12+
13+
func (c *cache) Request(hit bool) {
14+
if hit {
15+
c.hitCounter.Inc()
16+
} else {
17+
c.missCounter.Inc()
18+
}
19+
}
20+
21+
type noopCache struct{}
22+
23+
func (c noopCache) Request(hit bool) {
24+
}

Diff for: pkg/dockerregistry/server/metrics/metrics.go

+118-53
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,148 @@
11
package metrics
22

33
import (
4+
"net/url"
45
"strings"
5-
"time"
66

7-
"github.com/prometheus/client_golang/prometheus"
7+
gocontext "golang.org/x/net/context"
88

99
"github.com/docker/distribution"
1010
"github.com/docker/distribution/context"
11+
"github.com/docker/distribution/registry/api/errcode"
1112

1213
"github.com/openshift/image-registry/pkg/dockerregistry/server/wrapped"
14+
"github.com/openshift/image-registry/pkg/origin-common/image/registryclient"
1315
)
1416

15-
const (
16-
registryNamespace = "openshift"
17-
registrySubsystem = "registry"
18-
)
17+
// Observer captures individual observations.
18+
type Observer interface {
19+
Observe(float64)
20+
}
1921

20-
var (
21-
registryAPIRequests *prometheus.HistogramVec
22-
)
22+
// Counter represents a single numerical value that only goes up.
23+
type Counter interface {
24+
Inc()
25+
}
2326

24-
// Register the metrics.
25-
func Register() {
26-
registryAPIRequests = prometheus.NewHistogramVec(
27-
prometheus.HistogramOpts{
28-
Namespace: registryNamespace,
29-
Subsystem: registrySubsystem,
30-
Name: "request_duration_seconds",
31-
Help: "Request latency summary in microseconds for each operation",
32-
},
33-
[]string{"operation", "name"},
34-
)
35-
prometheus.MustRegister(registryAPIRequests)
36-
}
37-
38-
// Timer is a helper type to time functions.
39-
type Timer interface {
40-
// Stop records the duration passed since the Timer was created with NewTimer.
41-
Stop()
42-
}
43-
44-
// NewTimer wraps the HistogramVec and used to track amount of time passed since the Timer was created.
45-
func NewTimer(collector *prometheus.HistogramVec, labels []string) Timer {
46-
return &metricTimer{
47-
collector: collector,
48-
labels: labels,
49-
startTime: time.Now(),
27+
// Sink provides an interface for exposing metrics.
28+
type Sink interface {
29+
RequestDuration(funcname, reponame string) Observer
30+
PullthroughBlobstoreCacheRequests(resultType string) Counter
31+
PullthroughRepositoryDuration(registry, funcname string) Observer
32+
PullthroughRepositoryErrors(registry, funcname, errcode string) Counter
33+
}
34+
35+
// Metrics is a set of all metrics that can be provided.
36+
type Metrics interface {
37+
Core
38+
Pullthrough
39+
}
40+
41+
// Core is a set of metrics for the core functionality.
42+
type Core interface {
43+
// Repository wraps a distribution.Repository to collect statistics.
44+
Repository(r distribution.Repository, reponame string) distribution.Repository
45+
}
46+
47+
// Pullthrough is a set of metrics for the pullthrough subsystem.
48+
type Pullthrough interface {
49+
// RepositoryRetriever wraps RepositoryRetriever to collect statistics.
50+
RepositoryRetriever(retriever registryclient.RepositoryRetriever) registryclient.RepositoryRetriever
51+
52+
// DigestBlobStoreCache() returns an interface to count cache hits/misses
53+
// for pullthrough blobstores.
54+
DigestBlobStoreCache() Cache
55+
}
56+
57+
func dockerErrorCode(err error) string {
58+
if e, ok := err.(errcode.Error); ok {
59+
return e.ErrorCode().String()
5060
}
61+
return "UNKNOWN"
5162
}
5263

53-
type metricTimer struct {
54-
collector *prometheus.HistogramVec
55-
labels []string
56-
startTime time.Time
64+
func pullthroughRepositoryWrapper(ctx context.Context, sink Sink, registry string, funcname string, f func(ctx context.Context) error) error {
65+
registry = strings.ToLower(registry)
66+
defer NewTimer(sink.PullthroughRepositoryDuration(registry, funcname)).Stop()
67+
err := f(ctx)
68+
if err != nil {
69+
sink.PullthroughRepositoryErrors(registry, funcname, dockerErrorCode(err)).Inc()
70+
}
71+
return err
72+
}
73+
74+
type repositoryRetriever struct {
75+
retriever registryclient.RepositoryRetriever
76+
sink Sink
5777
}
5878

59-
func (m *metricTimer) Stop() {
60-
m.collector.WithLabelValues(m.labels...).Observe(float64(time.Since(m.startTime)) / float64(time.Second))
79+
func (rr repositoryRetriever) Repository(ctx gocontext.Context, registry *url.URL, repoName string, insecure bool) (repo distribution.Repository, err error) {
80+
err = pullthroughRepositoryWrapper(ctx, rr.sink, registry.Host, "Init", func(ctx context.Context) error {
81+
repo, err = rr.retriever.Repository(ctx, registry, repoName, insecure)
82+
return err
83+
})
84+
if err != nil {
85+
return repo, err
86+
}
87+
return wrapped.NewRepository(repo, func(ctx context.Context, funcname string, f func(ctx context.Context) error) error {
88+
return pullthroughRepositoryWrapper(ctx, rr.sink, registry.Host, funcname, f)
89+
}), nil
6190
}
6291

63-
func newWrapper(reponame string) wrapped.Wrapper {
64-
return func(ctx context.Context, funcname string, f func(ctx context.Context) error) error {
65-
defer NewTimer(registryAPIRequests, []string{strings.ToLower(funcname), reponame}).Stop()
92+
type metrics struct {
93+
sink Sink
94+
}
95+
96+
var _ Metrics = &metrics{}
97+
98+
// NewMetrics returns a helper that exposes the metrics through sink to
99+
// instrument the application.
100+
func NewMetrics(sink Sink) Metrics {
101+
return &metrics{
102+
sink: sink,
103+
}
104+
}
105+
106+
func (m *metrics) Repository(r distribution.Repository, reponame string) distribution.Repository {
107+
return wrapped.NewRepository(r, func(ctx context.Context, funcname string, f func(ctx context.Context) error) error {
108+
defer NewTimer(m.sink.RequestDuration(funcname, reponame)).Stop()
66109
return f(ctx)
110+
})
111+
}
112+
113+
func (m *metrics) RepositoryRetriever(retriever registryclient.RepositoryRetriever) registryclient.RepositoryRetriever {
114+
return repositoryRetriever{
115+
retriever: retriever,
116+
sink: m.sink,
67117
}
68118
}
69119

70-
// NewBlobStore wraps a distribution.BlobStore to collect statistics.
71-
func NewBlobStore(bs distribution.BlobStore, reponame string) distribution.BlobStore {
72-
return wrapped.NewBlobStore(bs, newWrapper(reponame))
120+
func (m *metrics) DigestBlobStoreCache() Cache {
121+
return &cache{
122+
hitCounter: m.sink.PullthroughBlobstoreCacheRequests("Hit"),
123+
missCounter: m.sink.PullthroughBlobstoreCacheRequests("Miss"),
124+
}
125+
}
126+
127+
type noopMetrics struct {
128+
}
129+
130+
var _ Metrics = noopMetrics{}
131+
132+
// NewNoopMetrics return a helper that implements the Metrics interface, but
133+
// does nothing.
134+
func NewNoopMetrics() Metrics {
135+
return noopMetrics{}
136+
}
137+
138+
func (m noopMetrics) Repository(r distribution.Repository, reponame string) distribution.Repository {
139+
return r
73140
}
74141

75-
// NewManifestService wraps a distribution.ManifestService to collect statistics
76-
func NewManifestService(ms distribution.ManifestService, reponame string) distribution.ManifestService {
77-
return wrapped.NewManifestService(ms, newWrapper(reponame))
142+
func (m noopMetrics) RepositoryRetriever(retriever registryclient.RepositoryRetriever) registryclient.RepositoryRetriever {
143+
return retriever
78144
}
79145

80-
// NewTagService wraps a distribution.TagService to collect statistics
81-
func NewTagService(ts distribution.TagService, reponame string) distribution.TagService {
82-
return wrapped.NewTagService(ts, newWrapper(reponame))
146+
func (m noopMetrics) DigestBlobStoreCache() Cache {
147+
return noopCache{}
83148
}

Diff for: pkg/dockerregistry/server/metrics/prometheus.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package metrics
2+
3+
import (
4+
"sync"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
)
8+
9+
const (
10+
namespace = "imageregistry"
11+
12+
pullthroughSubsystem = "pullthrough"
13+
)
14+
15+
var (
16+
requestDurationSeconds = prometheus.NewHistogramVec(
17+
prometheus.HistogramOpts{
18+
Namespace: namespace,
19+
Name: "request_duration_seconds",
20+
Help: "Request latency in seconds for each operation.",
21+
},
22+
[]string{"operation", "name"},
23+
)
24+
25+
pullthroughBlobstoreCacheRequestsTotal = prometheus.NewCounterVec(
26+
prometheus.CounterOpts{
27+
Namespace: namespace,
28+
Subsystem: pullthroughSubsystem,
29+
Name: "blobstore_cache_requests_total",
30+
Help: "Total number of requests to the BlobStore cache.",
31+
},
32+
[]string{"type"},
33+
)
34+
pullthroughRepositoryDurationSeconds = prometheus.NewHistogramVec(
35+
prometheus.HistogramOpts{
36+
Namespace: namespace,
37+
Subsystem: pullthroughSubsystem,
38+
Name: "repository_duration_seconds",
39+
Help: "Latency of operations with remote registries in seconds.",
40+
},
41+
[]string{"registry", "operation"},
42+
)
43+
pullthroughRepositoryErrorsTotal = prometheus.NewCounterVec(
44+
prometheus.CounterOpts{
45+
Namespace: namespace,
46+
Subsystem: pullthroughSubsystem,
47+
Name: "repository_errors_total",
48+
Help: "Cumulative number of failed operations with remote registries.",
49+
},
50+
[]string{"registry", "operation", "code"},
51+
)
52+
)
53+
54+
var prometheusOnce sync.Once
55+
56+
type prometheusSink struct{}
57+
58+
// NewPrometheusSink returns a sink for exposing Prometheus metrics.
59+
func NewPrometheusSink() Sink {
60+
prometheusOnce.Do(func() {
61+
prometheus.MustRegister(requestDurationSeconds)
62+
prometheus.MustRegister(pullthroughBlobstoreCacheRequestsTotal)
63+
prometheus.MustRegister(pullthroughRepositoryDurationSeconds)
64+
prometheus.MustRegister(pullthroughRepositoryErrorsTotal)
65+
})
66+
return prometheusSink{}
67+
}
68+
69+
func (s prometheusSink) RequestDuration(funcname, reponame string) Observer {
70+
return requestDurationSeconds.WithLabelValues(funcname, reponame)
71+
}
72+
73+
func (s prometheusSink) PullthroughBlobstoreCacheRequests(resultType string) Counter {
74+
return pullthroughBlobstoreCacheRequestsTotal.WithLabelValues(resultType)
75+
}
76+
77+
func (s prometheusSink) PullthroughRepositoryDuration(registry, funcname string) Observer {
78+
return pullthroughRepositoryDurationSeconds.WithLabelValues(registry, funcname)
79+
}
80+
81+
func (s prometheusSink) PullthroughRepositoryErrors(registry, funcname, errcode string) Counter {
82+
return pullthroughRepositoryErrorsTotal.WithLabelValues(registry, funcname, errcode)
83+
}

0 commit comments

Comments
 (0)