fix(remote): Unregister metrics emitted by remote.WriteStorage when closed (#16868)

* Unregister metrics emitted by `remote.WriteStorage` when closed

Signed-off-by: Charles Korn <charles.korn@grafana.com>

* Address PR feedback: add test

Signed-off-by: Charles Korn <charles.korn@grafana.com>

---------

Signed-off-by: Charles Korn <charles.korn@grafana.com>
This commit is contained in:
Charles Korn 2025-07-17 19:32:15 +10:00 committed by GitHub
parent 9dc274687b
commit 46acc974c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 0 deletions

View File

@ -268,6 +268,14 @@ func (rws *WriteStorage) Close() error {
q.Stop() q.Stop()
} }
close(rws.quit) close(rws.quit)
rws.watcherMetrics.Unregister()
rws.liveReaderMetrics.Unregister()
if rws.reg != nil {
rws.reg.Unregister(rws.highestTimestamp.Gauge)
}
return nil return nil
} }

View File

@ -980,3 +980,12 @@ func (s syncAppender) AppendHistogram(ref storage.SeriesRef, l labels.Labels, t
defer s.lock.Unlock() defer s.lock.Unlock()
return s.Appender.AppendHistogram(ref, l, t, h, f) return s.Appender.AppendHistogram(ref, l, t, h, f)
} }
func TestWriteStorage_CanRegisterMetricsAfterClosing(t *testing.T) {
dir := t.TempDir()
reg := prometheus.NewPedanticRegistry()
s := NewWriteStorage(nil, reg, dir, time.Millisecond, nil)
require.NoError(t, s.Close())
require.NotPanics(t, func() { NewWriteStorage(nil, reg, dir, time.Millisecond, nil) })
}

View File

@ -29,6 +29,7 @@ import (
// LiveReaderMetrics holds all metrics exposed by the LiveReader. // LiveReaderMetrics holds all metrics exposed by the LiveReader.
type LiveReaderMetrics struct { type LiveReaderMetrics struct {
reg prometheus.Registerer
readerCorruptionErrors *prometheus.CounterVec readerCorruptionErrors *prometheus.CounterVec
} }
@ -36,6 +37,7 @@ type LiveReaderMetrics struct {
// at LiveReader instantiation. // at LiveReader instantiation.
func NewLiveReaderMetrics(reg prometheus.Registerer) *LiveReaderMetrics { func NewLiveReaderMetrics(reg prometheus.Registerer) *LiveReaderMetrics {
m := &LiveReaderMetrics{ m := &LiveReaderMetrics{
reg: reg,
readerCorruptionErrors: prometheus.NewCounterVec(prometheus.CounterOpts{ readerCorruptionErrors: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "prometheus_tsdb_wal_reader_corruption_errors_total", Name: "prometheus_tsdb_wal_reader_corruption_errors_total",
Help: "Errors encountered when reading the WAL.", Help: "Errors encountered when reading the WAL.",
@ -49,6 +51,15 @@ func NewLiveReaderMetrics(reg prometheus.Registerer) *LiveReaderMetrics {
return m return m
} }
// Unregister unregisters metrics emitted by this instance.
func (m *LiveReaderMetrics) Unregister() {
if m.reg == nil {
return
}
m.reg.Unregister(m.readerCorruptionErrors)
}
// NewLiveReader returns a new live reader. // NewLiveReader returns a new live reader.
func NewLiveReader(logger *slog.Logger, metrics *LiveReaderMetrics, r io.Reader) *LiveReader { func NewLiveReader(logger *slog.Logger, metrics *LiveReaderMetrics, r io.Reader) *LiveReader {
lr := &LiveReader{ lr := &LiveReader{

View File

@ -73,6 +73,7 @@ type WriteNotified interface {
} }
type WatcherMetrics struct { type WatcherMetrics struct {
reg prometheus.Registerer
recordsRead *prometheus.CounterVec recordsRead *prometheus.CounterVec
recordDecodeFails *prometheus.CounterVec recordDecodeFails *prometheus.CounterVec
samplesSentPreTailing *prometheus.CounterVec samplesSentPreTailing *prometheus.CounterVec
@ -113,6 +114,7 @@ type Watcher struct {
func NewWatcherMetrics(reg prometheus.Registerer) *WatcherMetrics { func NewWatcherMetrics(reg prometheus.Registerer) *WatcherMetrics {
m := &WatcherMetrics{ m := &WatcherMetrics{
reg: reg,
recordsRead: prometheus.NewCounterVec( recordsRead: prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Namespace: "prometheus", Namespace: "prometheus",
@ -171,6 +173,19 @@ func NewWatcherMetrics(reg prometheus.Registerer) *WatcherMetrics {
return m return m
} }
// Unregister unregisters metrics emitted by this instance.
func (m *WatcherMetrics) Unregister() {
if m.reg == nil {
return
}
m.reg.Unregister(m.recordsRead)
m.reg.Unregister(m.recordDecodeFails)
m.reg.Unregister(m.samplesSentPreTailing)
m.reg.Unregister(m.currentSegment)
m.reg.Unregister(m.notificationsSkipped)
}
// NewWatcher creates a new WAL watcher for a given WriteTo. // NewWatcher creates a new WAL watcher for a given WriteTo.
func NewWatcher(metrics *WatcherMetrics, readerMetrics *LiveReaderMetrics, logger *slog.Logger, name string, writer WriteTo, dir string, sendExemplars, sendHistograms, sendMetadata bool) *Watcher { func NewWatcher(metrics *WatcherMetrics, readerMetrics *LiveReaderMetrics, logger *slog.Logger, name string, writer WriteTo, dir string, sendExemplars, sendHistograms, sendMetadata bool) *Watcher {
if logger == nil { if logger == nil {