mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-07 14:57:48 +02:00
* filer: identify remote-mounted entries safe to drop under disk pressure ListEvictableRemoteEntries walks every mounted directory directly on the filer store (no lazy remote listing) and returns entries that hold local chunks fully synchronized with remote, ordered oldest-cached first. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: evict remote-cached chunks oldest-first and vacuum the garbage uncacheRemoteEntry applies the same transition remote.uncache does - cleared chunks plus a reset LastLocalSyncTsNs under the entry path lock - and evictRemoteCachedEntries serializes passes over all mounts until a byte target is met. Aged victims are preferred; a second pass accepts any synchronized cached entry when aged ones cannot cover the request, since a failed read is worse than a dropped hot object. Cleared chunks only become disk space after compaction, so reclaimRemoteCacheSpace pairs each pass with a rate-limited VacuumVolume call that also picks up orphaned partial fills. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: trigger remote cache eviction under storage pressure A periodic check (30s) reads disk usage from master topology and evicts remote-mounted cached chunks once any disk crosses -filer.remoteCacheEvictThreshold (default 0.9; 0 disables), with a vacuum pass to reclaim the tombstoned needles. The cold-read cache path also kicks the same reclaim when a fill fails on exhausted volumes - the request still falls back to streaming from the remote, but the cache stops being permanently wedged full. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: flush deletion queue before remote cache vacuum Vacuum ran immediately after eviction while evicted file IDs still sat in the asynchronous deletion queue, so compaction saw no garbage and the cache stayed wedged. Flush the queue synchronously first and shorten the vacuum cooldown so sustained pressure does not wait five minutes between reclaim passes. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: cover remote cache eviction under capacity pressure Unit tests pin the eligibility filter and oldest-first ordering; the integration test runs a constrained two-node setup that saturates the cache, verifies the oldest synced entry is evicted and vacuumed, and that a later read re-caches it. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: coalesce remote cache reclaim passes A failed cache fill used to queue behind any in-flight eviction, stacking full mount traversals during a write-failure storm. Skip the pass when one is already running; the caller falls back to streaming from remote regardless. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: stop the remote cache janitor on shutdown The eviction ticker kept running after Shutdown closed the metadata store and could traverse a closed store. Give the janitor a context cancelled from Shutdown and propagate it into its master RPCs and traversals. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: vacuum only tombstoned volumes and retry deferred passes VacuumVolume with no volume id swept every collection, compacting volumes unrelated to the cache fill that failed. Now the reclaim path collects the vids of file ids actually flushed from the deletion queue and compacts only those. Vids that land inside the vacuum cooldown stay in a pending set the janitor retries on each tick, so chunks evicted just after a sweep are not stranded until the next pressure event. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: count only pressured disks when evicting remote cache The janitor measured the largest excess on one disk but let bytes on healthy disks satisfy the reclaim target. Split the topology disk view per physical disk and count only chunk bytes whose volumes sit on an over-threshold disk; entries contributing nothing there are skipped. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: compare remote cache sync time at nanosecond precision Second-precision mtime comparisons let a local write in the same second as the last sync still qualify as evictable, discarding unsynced changes. Compare LastLocalSyncTsNs against full-precision mtime (mtime_ns round-trips through the entry codec), and apply the same fix to remote.uncache's inline check. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: invalidate remote sync stamp on local content change A local overwrite that keeps the remote entry's LastLocalSyncTsNs looks evictable even though the remote copy no longer matches, and some write paths stamp mtime at second precision so a timestamp comparison cannot catch it. UpdateEntry now clears the stamp when chunks change without a fresh stamp, leaving replicated updates authoritative. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: bound remote cache master rpcs and vacuum all evicted garbage VolumeList and VacuumVolume now run under a 30s context so a stalled master cannot wedge the eviction janitor. The targeted vacuum drops the garbage threshold so volumes with under 10% deleted bytes still compact. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: tolerate straggler fills in remote cache eviction test Detached fills from the concurrent wave keep racing the final checks: live chunks legitimately fill both volumes, and a re-cached object can be evicted again before its commit is observed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: start remote cache eviction loop after filer init The janitor's first tick dereferences fs.filer; starting the goroutine before NewFiler assigns it could panic when startup exceeds an interval. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: keep remote cache vacuum intent across retries Evicted entries now record their chunk volumes for vacuum directly, so the intent survives whoever consumes the shared deletion queue first. A pending volume keeps several vacuum attempts so tombstones that land late are still compacted, and the janitor retries pending volumes under the reclaim mutex instead of flushing unrelated deletes every tick. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: bound each remote cache vacuum request independently A shared 30s deadline across pending volumes let one slow compaction cancel the rest. Each VacuumVolume now gets its own context, and pending volumes keep more attempts since the master reports request acceptance rather than compaction. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: tighten remote cache reclamation bound Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: treat chunk timestamp changes as content changes chunksEqual now also compares ModifiedTsNs so an update that rewrites a chunk record still invalidates the remote sync stamp. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: run remote cache queue flush under the reclaim context BatchDelete for flushed file ids now uses the caller's context instead of context.Background(), so a reclaim pass bounded by shutdown or timeout stops its deletes too. Other callers keep their existing behavior. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: scope remote cache vacuum to evicted volumes The flush no longer feeds the shared deletion queue's ids into the pending set — only evicted chunks' volumes are tracked, so ordinary deletions no longer pick up repeated vacuum attempts. The flush also runs under a shutdown-immune bounded context and is skipped when no volume is pending. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: retry remote cache vacuums even after unmount Pending volumes were only retried while a remote mount existed; removing the last mount skipped every later pass and left evicted bytes allocated. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: reclaim partial cache fills that run out of capacity A fill that fails midway queues its written chunks for deletion, but when no entries remain evictable the reclaim pass found no pending volumes and skipped the flush and vacuum entirely, leaving the partial garbage to the slow periodic vacuum while the disk stayed full. Mark the failed fill's chunk volumes pending so the pass tombstones and compacts them even when nothing was evicted. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1286 lines
48 KiB
Go
1286 lines
48 KiB
Go
package stats
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/prometheus/client_golang/prometheus/push"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
// SetVersionInfo sets the version information for the BuildInfo metric
|
|
// This is called by the version package during initialization.
|
|
// It uses sync.Once to ensure the build information is set only once,
|
|
// making it safe to call multiple times while ensuring immutability.
|
|
var SetVersionInfo = func() func(string, string, string) {
|
|
var once sync.Once
|
|
return func(version, commitHash, sizeLimit string) {
|
|
once.Do(func() {
|
|
BuildInfo.WithLabelValues(version, commitHash, sizeLimit, runtime.GOOS, runtime.GOARCH).Set(1)
|
|
})
|
|
}
|
|
}()
|
|
|
|
// Readonly volume types
|
|
const (
|
|
Namespace = "SeaweedFS"
|
|
IsReadOnly = "IsReadOnly"
|
|
NoWriteOrDelete = "noWriteOrDelete"
|
|
NoWriteCanDelete = "noWriteCanDelete"
|
|
IsDiskSpaceLow = "isDiskSpaceLow"
|
|
bucketAtiveTTL = 10 * time.Minute
|
|
)
|
|
|
|
// Prometheus metric subsystems.
|
|
const (
|
|
subsystemBuild = "build"
|
|
subsystemMaster = "master"
|
|
subsystemWDClient = "wdclient"
|
|
subsystemFiler = "filer"
|
|
subsystemFilerStore = "filerStore"
|
|
subsystemFilerSync = "filerSync"
|
|
subsystemVolumeServer = "volumeServer"
|
|
subsystemS3 = "s3"
|
|
subsystemS3Lifecycle = "s3_lifecycle"
|
|
subsystemAdmin = "admin"
|
|
subsystemRemote = "remote"
|
|
)
|
|
|
|
var bucketLastActiveTsNs map[string]int64 = map[string]int64{}
|
|
var bucketLastActiveLock sync.Mutex
|
|
|
|
var (
|
|
Gather = prometheus.NewRegistry()
|
|
|
|
BuildInfo = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemBuild,
|
|
Name: "info",
|
|
Help: "A metric with a constant '1' value labeled by version, commit, sizelimit, goos, and goarch from which SeaweedFS was built.",
|
|
}, []string{"version", "commit", "sizelimit", "goos", "goarch"})
|
|
|
|
MasterStartTimeSeconds = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "start_time_seconds",
|
|
Help: "Start time of the master, as seconds since UNIX epoch.",
|
|
})
|
|
|
|
MasterClientConnectCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemWDClient,
|
|
Name: "connect_updates",
|
|
Help: "Counter of master client leader updates.",
|
|
}, []string{"type"})
|
|
|
|
MasterRaftIsleader = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "is_leader",
|
|
Help: "is leader",
|
|
})
|
|
|
|
MasterAdminLock = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "admin_lock",
|
|
Help: "admin lock",
|
|
}, []string{"client"})
|
|
|
|
MasterReceivedHeartbeatCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "received_heartbeats",
|
|
Help: "Counter of master received heartbeat.",
|
|
}, []string{"type"})
|
|
|
|
MasterReplicaPlacementMismatch = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "replica_placement_mismatch",
|
|
Help: "replica placement mismatch",
|
|
}, []string{"collection", "id"})
|
|
|
|
MasterVolumeLayoutWritable = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "volume_layout_writable",
|
|
Help: "Number of writable volumes in volume layouts",
|
|
}, []string{"collection", "disk", "rp", "ttl"})
|
|
|
|
MasterVolumeLayoutCrowded = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "volume_layout_crowded",
|
|
Help: "Number of crowded volumes in volume layouts",
|
|
}, []string{"collection", "disk", "rp", "ttl"})
|
|
|
|
// MasterUnderReplicatedVolumes tracks volumes that do not have enough replicas,
|
|
// partitioned by collection, disk type, replication type, and TTL.
|
|
MasterUnderReplicatedVolumes = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "under_replicated_volumes",
|
|
Help: "Current number of volumes that do not have enough replicas per collection/layout. 0 = healthy.",
|
|
}, []string{"collection", "disk", "rp", "ttl"})
|
|
|
|
// MasterVolumeCreationCounter counts volume creation (growth) operations by result (success, failure).
|
|
// Volume growth is orchestrated by the master, so this metric is exported by the master process.
|
|
MasterVolumeCreationCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "volume_creation_total",
|
|
Help: "Counter of volume creation operations by result (success, failure).",
|
|
}, []string{"result"})
|
|
|
|
MasterPickForWriteErrorCounter = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "pick_for_write_error",
|
|
Help: "Counter of master pick for write error",
|
|
})
|
|
|
|
MasterBroadcastToFullErrorCounter = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "broadcast_to_full",
|
|
Help: "Counter of master broadcast send to full message channel err",
|
|
})
|
|
|
|
MasterLeaderChangeCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemMaster,
|
|
Name: "leader_changes",
|
|
Help: "Counter of master leader changes.",
|
|
}, []string{"type"})
|
|
|
|
FilerRequestCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "request_total",
|
|
Help: "Counter of filer requests.",
|
|
}, []string{"type", "code"})
|
|
|
|
FilerHandlerCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "handler_total",
|
|
Help: "Counter of filer handlers.",
|
|
}, []string{"type"})
|
|
|
|
FilerRequestHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "request_seconds",
|
|
Help: "Bucketed histogram of filer request processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"type"})
|
|
|
|
FilerInFlightRequestsGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "in_flight_requests",
|
|
Help: "Current number of in-flight requests being handled by filer.",
|
|
}, []string{"type"})
|
|
|
|
FilerInFlightUploadBytesGauge = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "in_flight_upload_bytes",
|
|
Help: "Current number of bytes being uploaded to filer.",
|
|
})
|
|
|
|
FilerInFlightUploadCountGauge = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "in_flight_upload_count",
|
|
Help: "Current number of uploads in progress to filer.",
|
|
})
|
|
|
|
FilerServerLastSendTsOfSubscribeGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "last_send_timestamp_of_subscribe",
|
|
Help: "The last send timestamp of the filer subscription.",
|
|
}, []string{"sourceFiler", "clientName", "path"})
|
|
|
|
FilerSubscribeUnprovenGapCrossings = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "subscribe_unproven_gap_crossings",
|
|
Help: "Times a metadata subscriber moved past a log range without proof it was persisted: scope=aggregated means a peer may not have flushed it, scope=local means this filer's own log flush was wedged past the give-up bound.",
|
|
}, []string{"scope"})
|
|
|
|
FilerSubscribeWatermarkHolds = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "subscribe_watermark_holds",
|
|
Help: "Times an aggregated metadata read stopped at an entry newer than the peers' low-watermark and waited for a peer to report further progress: scope=memory held at the delivery watermark, scope=disk at the flush watermark.",
|
|
}, []string{"scope"})
|
|
|
|
FilerSubscribeGapStalledGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "subscribe_gap_stalled",
|
|
Help: "Number of metadata subscribers currently parked waiting to read past a gap in the metadata log.",
|
|
}, []string{"scope"})
|
|
|
|
FilerMetaAggregatorReplayFailures = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "meta_aggregator_replay_failures",
|
|
Help: "Number of peer metadata events skipped after replay retries were exhausted, leaving that entry diverged from the peer.",
|
|
}, []string{"peer"})
|
|
|
|
// Sampled only on first creation, so counts track distinct objects.
|
|
FilerObjectSizeBytesHistogram = prometheus.NewHistogram(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFiler,
|
|
Name: "object_size_bytes",
|
|
Help: "Distribution of object sizes in bytes, sampled when an object is first created.",
|
|
Buckets: []float64{1024, 102400, 1048576, 104857600, 1073741824},
|
|
})
|
|
|
|
FilerStoreCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerStore,
|
|
Name: "request_total",
|
|
Help: "Counter of filer store requests.",
|
|
}, []string{"store", "type"})
|
|
|
|
FilerStoreHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerStore,
|
|
Name: "request_seconds",
|
|
Help: "Bucketed histogram of filer store request processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"store", "type"})
|
|
|
|
FilerSyncOffsetGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "sync_offset",
|
|
Help: "The offset of the filer synchronization service.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncEventsReceivedCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "events_received_total",
|
|
Help: "Counter of metadata events read off the source subscription stream.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncEventsProcessedCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "events_processed_total",
|
|
Help: "Counter of metadata events successfully replicated to the target.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncEventsFailedCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "events_failed_total",
|
|
Help: "Counter of metadata events that failed after retries; the sync offset is held at the oldest failure so it is replayed on restart.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncInFlightJobsGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "in_flight_jobs",
|
|
Help: "Number of sync jobs currently being replicated; pinned at the concurrency limit means the sync itself is the bottleneck.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncReceivedBytesCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "received_bytes_total",
|
|
Help: "Counter of chunk data bytes carried by received events: new chunks the old entry does not already have, so deletes, renames, and attribute-only updates count zero.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncProcessedBytesCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "processed_bytes_total",
|
|
Help: "Counter of chunk data bytes carried by successfully replicated events.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncFailedBytesCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "failed_bytes_total",
|
|
Help: "Counter of chunk data bytes carried by events that failed after retries.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncInFlightBytesGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "in_flight_bytes",
|
|
Help: "Chunk data bytes carried by the jobs currently being replicated; distinguishes workers stuck on a few large files from many small ones.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
FilerSyncLagSecondsGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemFilerSync,
|
|
Name: "lag_seconds",
|
|
Help: "How far the replicated watermark trails the source filer, in seconds.",
|
|
}, []string{"sourceFiler", "targetFiler", "clientName", "path"})
|
|
|
|
VolumeServerStartTimeSeconds = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "start_time_seconds",
|
|
Help: "Start time of the volume server, as seconds since UNIX epoch.",
|
|
})
|
|
|
|
VolumeServerRequestCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "request_total",
|
|
Help: "Counter of volume server requests.",
|
|
}, []string{"type", "code"})
|
|
|
|
VolumeServerHandlerCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "handler_total",
|
|
Help: "Counter of volume server handlers.",
|
|
}, []string{"type"})
|
|
|
|
VolumeServerVacuumingCompactCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "vacuuming_compact_count",
|
|
Help: "Counter of volume vacuuming Compact counter",
|
|
}, []string{"success"})
|
|
|
|
VolumeServerVacuumingCommitCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "vacuuming_commit_count",
|
|
Help: "Counter of volume vacuuming commit counter",
|
|
}, []string{"success"})
|
|
|
|
VolumeServerVacuumingHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "vacuuming_seconds",
|
|
Help: "Bucketed histogram of volume server vacuuming processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"type"})
|
|
|
|
VolumeServerRequestHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "request_seconds",
|
|
Help: "Bucketed histogram of volume server request processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"type"})
|
|
|
|
VolumeServerInFlightRequestsGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "in_flight_requests",
|
|
Help: "Current number of in-flight requests being handled by volume server.",
|
|
}, []string{"type"})
|
|
|
|
VolumeServerVolumeGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "volumes",
|
|
Help: "Number of volumes or shards.",
|
|
}, []string{"collection", "type"})
|
|
|
|
VolumeServerReadOnlyVolumeGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "read_only_volumes",
|
|
Help: "Number of read only volumes.",
|
|
}, []string{"collection", "type"})
|
|
|
|
VolumeServerMaxVolumeCounter = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "max_volumes",
|
|
Help: "Maximum number of volumes.",
|
|
})
|
|
|
|
VolumeServerDiskSizeGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "total_disk_size",
|
|
Help: "Actual disk size used by volumes.",
|
|
}, []string{"collection", "type"})
|
|
|
|
VolumeServerResourceGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "resource",
|
|
Help: "Resource usage",
|
|
}, []string{"name", "type"})
|
|
|
|
VolumeServerDiskErrorGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "disk_error_status",
|
|
Help: "Disk error status",
|
|
}, []string{"name", "type"})
|
|
|
|
VolumeServerStorageIoErrorCounter = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "storage_io_error_total",
|
|
Help: "Counter of storage read/write EIO errors on volumes and EC shards.",
|
|
})
|
|
|
|
VolumeServerIoQuarantineGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "io_quarantine",
|
|
Help: "Number of volumes or EC shards quarantined due to storage IO errors.",
|
|
}, []string{"kind"})
|
|
|
|
VolumeServerConcurrentDownloadLimit = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "concurrent_download_limit",
|
|
Help: "Limit total concurrent download size.",
|
|
})
|
|
|
|
VolumeServerConcurrentUploadLimit = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "concurrent_upload_limit",
|
|
Help: "Limit total concurrent upload size.",
|
|
})
|
|
|
|
VolumeServerInFlightDownloadSize = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "in_flight_download_size",
|
|
Help: "In flight total download size.",
|
|
})
|
|
|
|
VolumeServerInFlightUploadSize = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "in_flight_upload_size",
|
|
Help: "In flight total upload size.",
|
|
})
|
|
|
|
VolumeServerMasterDisconnections = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "master_disconnections",
|
|
Help: "Number of master server disconnections.",
|
|
}, []string{"address"})
|
|
|
|
VolumeServerFileReadFailures = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "file_read_failures",
|
|
Help: "Counter of overall failed file read requests from clients.",
|
|
})
|
|
|
|
VolumeServerFileReadInvalidNeedles = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "file_read_invalid_needles",
|
|
Help: "Counter of failed file read requests due to invalid needle IDs from clients.",
|
|
})
|
|
|
|
VolumeServerFileWriteFailures = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "file_write_failures",
|
|
Help: "Counter of overall failed file write requests from clients.",
|
|
})
|
|
|
|
VolumeServerScrubLastTimeSeconds = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "scrub_last_time_seconds",
|
|
Help: "Last scrub execution time, as seconds since UNIX epoch.",
|
|
}, []string{"mode"})
|
|
|
|
VolumeServerScrubVolumeFailures = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "scrub_volume_failures",
|
|
Help: "Counter of overall volumes with issues detected during scrubbing.",
|
|
}, []string{"mode"})
|
|
|
|
VolumeServerScrubShardFailures = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "scrub_shard_failures",
|
|
Help: "Counter of overall EC shards with issues detected during scrubbing.",
|
|
}, []string{"mode"})
|
|
|
|
// VolumeServerReplicationCounter counts replication operations by operation type
|
|
// (write, delete) and result (success, failure).
|
|
VolumeServerReplicationCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "replication_operations_total",
|
|
Help: "Counter of replication operations by type (write, delete) and result (success, failure).",
|
|
}, []string{"operation", "result"})
|
|
|
|
// VolumeServerReplicationHistogram records replication operation duration in seconds,
|
|
// partitioned by operation type (write, delete).
|
|
VolumeServerReplicationHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "replication_seconds",
|
|
Help: "Bucketed histogram of replication operation duration in seconds.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"operation"})
|
|
|
|
// VolumeServerReplicationTargets records the number of replica targets per replication
|
|
// operation, useful for observing fan-out width.
|
|
VolumeServerReplicationTargets = prometheus.NewHistogram(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "replication_targets",
|
|
Help: "Histogram of replica targets count per replication operation.",
|
|
Buckets: []float64{1, 2, 3, 4, 5},
|
|
})
|
|
|
|
// VolumeServerReplicationFailures counts replication failures by operation type
|
|
// and failure reason (timeout, connection_refused, context_cancelled, server_error).
|
|
VolumeServerReplicationFailures = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "replication_failures_total",
|
|
Help: "Counter of replication failures by operation and reason (timeout, connection_refused, context_cancelled, server_error).",
|
|
}, []string{"operation", "reason"})
|
|
|
|
// VolumeServerECRebuildHistogram records the duration of EC shard rebuild operations by result (success, failure).
|
|
VolumeServerECRebuildHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "ec_rebuild_seconds",
|
|
Help: "Bucketed histogram of EC shard rebuild/reconstruct duration by result.",
|
|
Buckets: prometheus.ExponentialBuckets(0.01, 2, 20),
|
|
}, []string{"result"})
|
|
|
|
// VolumeServerECRebuildCounter counts EC shard rebuild operations by result (success, failure).
|
|
VolumeServerECRebuildCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemVolumeServer,
|
|
Name: "ec_rebuild_total",
|
|
Help: "Counter of EC shard rebuild operations by result.",
|
|
}, []string{"result"})
|
|
|
|
S3RequestCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "request_total",
|
|
Help: "Counter of s3 requests.",
|
|
}, []string{"type", "code", "bucket"})
|
|
|
|
S3HandlerCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "handler_total",
|
|
Help: "Counter of s3 server handlers.",
|
|
}, []string{"type"})
|
|
|
|
S3RequestHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "request_seconds",
|
|
Help: "Bucketed histogram of s3 request processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.0001, 2, 24),
|
|
}, []string{"type", "bucket"})
|
|
|
|
S3TimeToFirstByteHistogram = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "time_to_first_byte_millisecond",
|
|
Help: "Bucketed histogram of s3 time to first byte request processing time.",
|
|
Buckets: prometheus.ExponentialBuckets(0.001, 2, 27),
|
|
}, []string{"type", "bucket"})
|
|
S3InFlightRequestsGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "in_flight_requests",
|
|
Help: "Current number of in-flight requests being handled by s3.",
|
|
}, []string{"type"})
|
|
|
|
S3InFlightUploadBytesGauge = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "in_flight_upload_bytes",
|
|
Help: "Current number of bytes being uploaded to S3.",
|
|
})
|
|
|
|
S3InFlightUploadCountGauge = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "in_flight_upload_count",
|
|
Help: "Current number of uploads in progress to S3.",
|
|
})
|
|
|
|
S3BucketTrafficReceivedBytesCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_traffic_received_bytes_total",
|
|
Help: "Total number of bytes received by an S3 bucket from clients.",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketTrafficSentBytesCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_traffic_sent_bytes_total",
|
|
Help: "Total number of bytes sent from an S3 bucket to clients.",
|
|
}, []string{"bucket"})
|
|
|
|
S3DeletedObjectsCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "deleted_objects",
|
|
Help: "Number of objects deleted in each bucket.",
|
|
}, []string{"bucket"})
|
|
|
|
S3UploadedObjectsCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "uploaded_objects",
|
|
Help: "Number of objects uploaded in each bucket.",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketSizeBytesGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_size_bytes",
|
|
Help: "Current size of each S3 bucket in bytes (logical size, deduplicated across replicas).",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketPhysicalSizeBytesGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_physical_size_bytes",
|
|
Help: "Current physical size of each S3 bucket in bytes (including all replicas).",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketObjectCountGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_object_count",
|
|
Help: "Current number of objects in each S3 bucket (logical count, deduplicated across replicas).",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketQuotaBytesGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_quota_bytes",
|
|
Help: "Configured quota of each S3 bucket in bytes. Only present for buckets with an enabled quota.",
|
|
}, []string{"bucket"})
|
|
|
|
S3BucketReadOnlyGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3,
|
|
Name: "bucket_read_only",
|
|
Help: "Whether each S3 bucket is read-only (1) or writable (0), e.g. after exceeding its quota.",
|
|
}, []string{"bucket"})
|
|
|
|
RemoteCacheReadCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemRemote,
|
|
Name: "cache_read_total",
|
|
Help: "Remote-mount object read attempts by source, bucket and cache result. A cold object retried before caching completes records a miss per attempt; paths outside the buckets folder use bucket \"_other\".",
|
|
}, []string{"source", "bucket", "result"})
|
|
|
|
RemoteCacheEvictedCounter = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemRemote,
|
|
Name: "cache_evicted_total",
|
|
Help: "Remote-mounted objects whose local chunks were evicted under disk pressure.",
|
|
})
|
|
|
|
UploadErrorCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Name: "upload_error_total",
|
|
Help: "Counter of upload errors by HTTP status code. Code 0 means transport error (no response received).",
|
|
}, []string{"code"})
|
|
|
|
S3LifecycleDispatchCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "dispatch_total",
|
|
Help: "Counter of LifecycleDelete RPC outcomes by bucket, action kind, and outcome.",
|
|
}, []string{"bucket", "kind", "outcome"})
|
|
|
|
S3LifecycleScheduleDepthGauge = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "schedule_depth",
|
|
Help: "Number of pending matches in the dispatcher schedule per shard.",
|
|
}, []string{"shard"})
|
|
|
|
S3LifecycleCursorMinTsNs = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "cursor_min_ts_ns",
|
|
Help: "Per-shard min cursor timestamp in nanoseconds since epoch (lag = now - min).",
|
|
}, []string{"shard"})
|
|
|
|
S3LifecycleEventCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "events_total",
|
|
Help: "Counter of meta-log events the reader emitted to the router, partitioned by shard.",
|
|
}, []string{"shard"})
|
|
|
|
S3LifecycleBootstrapDispatchCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "bootstrap_dispatch_total",
|
|
Help: "Counter of bootstrap-walk Delete dispatches by bucket and action kind.",
|
|
}, []string{"bucket", "kind"})
|
|
|
|
// S3LifecycleMetadataOnlyCounter counts successful LifecycleDelete
|
|
// dispatches that took the metadata-only path — entry was removed
|
|
// without per-chunk DeleteFile RPCs because the entry's Attributes
|
|
// .TtlSec > 0 and the volume's natural TTL will reclaim chunks. Per-
|
|
// rule cardinality (rule_hash hex-encoded) lets operators identify
|
|
// which specific rule is exercising the optimization; in clusters
|
|
// with many rules this can be reduced via Prometheus relabeling.
|
|
S3LifecycleMetadataOnlyCounter = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "metadata_only_total",
|
|
Help: "Counter of LifecycleDelete completions that skipped per-chunk delete (volume TTL reclaim).",
|
|
}, []string{"bucket", "rule_hash"})
|
|
|
|
// S3LifecycleDispatchLimiterWaitSeconds is the cluster-wide rate
|
|
// limiter's per-dispatch wait time on the daily-replay path. The
|
|
// limiter blocks just before each LifecycleDelete RPC; near-zero
|
|
// observations mean the cluster cap isn't binding, a long-tail at
|
|
// the configured 1/rate ceiling means the cluster cap is the
|
|
// active throttle. Operators tune cluster_deletes_per_second by
|
|
// reading p95/p99 on this histogram.
|
|
S3LifecycleDispatchLimiterWaitSeconds = prometheus.NewHistogram(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "dispatch_limiter_wait_seconds",
|
|
Help: "Time spent waiting on the cluster rate limiter before issuing a LifecycleDelete RPC. Non-zero values indicate the cluster cap is binding.",
|
|
Buckets: []float64{0.0001, 0.001, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
|
|
})
|
|
|
|
S3LifecycleDailyRunShardDurationSeconds = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "daily_run_shard_duration_seconds",
|
|
Help: "Wall-clock seconds spent in one shard's daily_replay pass. p95 climbing toward MaxRuntime means the shard is brushing its budget.",
|
|
Buckets: []float64{0.1, 0.5, 1, 5, 15, 60, 300, 900, 1800, 3600},
|
|
}, []string{"shard"})
|
|
|
|
S3LifecycleDailyRunEventsScanned = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "daily_run_events_scanned_total",
|
|
Help: "Counter of meta-log events drainShardEvents processed on the daily_replay path, partitioned by shard.",
|
|
}, []string{"shard"})
|
|
|
|
// S3LifecycleDailyRunLastWalkedNs is the per-shard wall-clock
|
|
// timestamp (UnixNano) of the most recent successful steady-state /
|
|
// empty-replay walker fire. Set by dailyrun.runShard after each
|
|
// cursor save. Zero means the shard hasn't completed a walk yet
|
|
// (either cold start, or the walker never fired because the bucket
|
|
// has only replay-eligible rules and the throttle hasn't elapsed).
|
|
// Operators read (now - last_walked_ns) to confirm the walker
|
|
// cadence matches WalkerInterval; a stuck value means the
|
|
// scheduler isn't invoking the worker, the throttle is too long,
|
|
// or the walker is failing.
|
|
S3LifecycleDailyRunLastWalkedNs = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemS3Lifecycle,
|
|
Name: "daily_run_last_walked_ns",
|
|
Help: "Per-shard timestamp (UnixNano) of the most recent successful walker fire. 0 means the shard hasn't completed a walk yet.",
|
|
}, []string{"shard"})
|
|
|
|
AdminMaintenanceTasksByStatus = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_tasks_by_status",
|
|
Help: "Current number of maintenance tasks by status (pending, assigned, in_progress, completed, failed, cancelled).",
|
|
}, []string{"status"})
|
|
|
|
AdminMaintenanceTasksByType = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_tasks_by_type",
|
|
Help: "Current number of maintenance tasks by type.",
|
|
}, []string{"type"})
|
|
|
|
AdminMaintenanceTasksCompletedTotal = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_tasks_completed_total",
|
|
Help: "Counter of maintenance tasks that reached a terminal state, by type and outcome (completed, failed).",
|
|
}, []string{"type", "outcome"})
|
|
|
|
AdminMaintenanceTaskDurationSeconds = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_task_duration_seconds",
|
|
Help: "Execution time of maintenance tasks that reached a terminal state, by type.",
|
|
Buckets: prometheus.ExponentialBuckets(1, 2, 16),
|
|
}, []string{"type"})
|
|
|
|
AdminMaintenanceLastScanTimestampSeconds = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_last_scan_timestamp_seconds",
|
|
Help: "Unix timestamp of the most recent maintenance scan. 0 means no scan has run yet.",
|
|
})
|
|
|
|
AdminMaintenanceNextScanTimestampSeconds = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "maintenance_next_scan_timestamp_seconds",
|
|
Help: "Unix timestamp of the next expected maintenance scan.",
|
|
})
|
|
|
|
AdminWorkersConnected = prometheus.NewGauge(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "workers_connected",
|
|
Help: "Current number of maintenance workers known to the admin server.",
|
|
})
|
|
|
|
AdminWorkerSlots = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "worker_slots",
|
|
Help: "Maintenance worker task slots aggregated across workers, by state (used, max).",
|
|
}, []string{"state"})
|
|
|
|
AdminWorkerEventsTotal = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Namespace: Namespace,
|
|
Subsystem: subsystemAdmin,
|
|
Name: "worker_events_total",
|
|
Help: "Counter of maintenance worker lifecycle events by type (registered, unregistered, stale_removed).",
|
|
}, []string{"event"})
|
|
)
|
|
|
|
func init() {
|
|
Gather.MustRegister(BuildInfo)
|
|
|
|
Gather.MustRegister(MasterStartTimeSeconds)
|
|
Gather.MustRegister(MasterClientConnectCounter)
|
|
Gather.MustRegister(MasterRaftIsleader)
|
|
Gather.MustRegister(MasterAdminLock)
|
|
Gather.MustRegister(MasterReceivedHeartbeatCounter)
|
|
Gather.MustRegister(MasterLeaderChangeCounter)
|
|
Gather.MustRegister(MasterReplicaPlacementMismatch)
|
|
Gather.MustRegister(MasterVolumeLayoutWritable)
|
|
Gather.MustRegister(MasterVolumeLayoutCrowded)
|
|
Gather.MustRegister(MasterPickForWriteErrorCounter)
|
|
Gather.MustRegister(MasterBroadcastToFullErrorCounter)
|
|
|
|
Gather.MustRegister(FilerRequestCounter)
|
|
Gather.MustRegister(FilerHandlerCounter)
|
|
Gather.MustRegister(FilerRequestHistogram)
|
|
Gather.MustRegister(FilerInFlightRequestsGauge)
|
|
Gather.MustRegister(FilerInFlightUploadBytesGauge)
|
|
Gather.MustRegister(FilerInFlightUploadCountGauge)
|
|
Gather.MustRegister(FilerStoreCounter)
|
|
Gather.MustRegister(FilerStoreHistogram)
|
|
Gather.MustRegister(FilerSyncOffsetGauge)
|
|
Gather.MustRegister(FilerSyncEventsReceivedCounter)
|
|
Gather.MustRegister(FilerSyncEventsProcessedCounter)
|
|
Gather.MustRegister(FilerSyncEventsFailedCounter)
|
|
Gather.MustRegister(FilerSyncInFlightJobsGauge)
|
|
Gather.MustRegister(FilerSyncLagSecondsGauge)
|
|
Gather.MustRegister(FilerSyncReceivedBytesCounter)
|
|
Gather.MustRegister(FilerSyncProcessedBytesCounter)
|
|
Gather.MustRegister(FilerSyncFailedBytesCounter)
|
|
Gather.MustRegister(FilerSyncInFlightBytesGauge)
|
|
Gather.MustRegister(FilerServerLastSendTsOfSubscribeGauge)
|
|
Gather.MustRegister(FilerSubscribeGapStalledGauge)
|
|
Gather.MustRegister(FilerSubscribeUnprovenGapCrossings)
|
|
Gather.MustRegister(FilerSubscribeWatermarkHolds)
|
|
Gather.MustRegister(FilerMetaAggregatorReplayFailures)
|
|
Gather.MustRegister(FilerObjectSizeBytesHistogram)
|
|
Gather.MustRegister(collectors.NewGoCollector())
|
|
Gather.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
|
|
|
Gather.MustRegister(VolumeServerStartTimeSeconds)
|
|
Gather.MustRegister(VolumeServerRequestCounter)
|
|
Gather.MustRegister(VolumeServerHandlerCounter)
|
|
Gather.MustRegister(VolumeServerRequestHistogram)
|
|
Gather.MustRegister(VolumeServerInFlightRequestsGauge)
|
|
Gather.MustRegister(VolumeServerVacuumingCompactCounter)
|
|
Gather.MustRegister(VolumeServerVacuumingCommitCounter)
|
|
Gather.MustRegister(VolumeServerVacuumingHistogram)
|
|
Gather.MustRegister(VolumeServerVolumeGauge)
|
|
Gather.MustRegister(VolumeServerMaxVolumeCounter)
|
|
Gather.MustRegister(VolumeServerReadOnlyVolumeGauge)
|
|
Gather.MustRegister(VolumeServerDiskSizeGauge)
|
|
Gather.MustRegister(VolumeServerResourceGauge)
|
|
Gather.MustRegister(VolumeServerDiskErrorGauge)
|
|
Gather.MustRegister(VolumeServerStorageIoErrorCounter)
|
|
Gather.MustRegister(VolumeServerIoQuarantineGauge)
|
|
Gather.MustRegister(VolumeServerConcurrentDownloadLimit)
|
|
Gather.MustRegister(VolumeServerConcurrentUploadLimit)
|
|
Gather.MustRegister(VolumeServerInFlightDownloadSize)
|
|
Gather.MustRegister(VolumeServerInFlightUploadSize)
|
|
Gather.MustRegister(VolumeServerMasterDisconnections)
|
|
Gather.MustRegister(VolumeServerFileReadFailures)
|
|
Gather.MustRegister(VolumeServerFileReadInvalidNeedles)
|
|
Gather.MustRegister(VolumeServerFileWriteFailures)
|
|
Gather.MustRegister(VolumeServerScrubLastTimeSeconds)
|
|
Gather.MustRegister(VolumeServerScrubVolumeFailures)
|
|
Gather.MustRegister(VolumeServerScrubShardFailures)
|
|
Gather.MustRegister(VolumeServerReplicationCounter)
|
|
Gather.MustRegister(VolumeServerReplicationHistogram)
|
|
Gather.MustRegister(VolumeServerReplicationTargets)
|
|
Gather.MustRegister(VolumeServerReplicationFailures)
|
|
Gather.MustRegister(VolumeServerECRebuildHistogram)
|
|
Gather.MustRegister(VolumeServerECRebuildCounter)
|
|
Gather.MustRegister(MasterUnderReplicatedVolumes)
|
|
Gather.MustRegister(MasterVolumeCreationCounter)
|
|
|
|
Gather.MustRegister(S3RequestCounter)
|
|
Gather.MustRegister(S3HandlerCounter)
|
|
Gather.MustRegister(S3RequestHistogram)
|
|
Gather.MustRegister(S3InFlightRequestsGauge)
|
|
Gather.MustRegister(S3InFlightUploadBytesGauge)
|
|
Gather.MustRegister(S3InFlightUploadCountGauge)
|
|
Gather.MustRegister(S3TimeToFirstByteHistogram)
|
|
Gather.MustRegister(S3BucketTrafficReceivedBytesCounter)
|
|
Gather.MustRegister(S3BucketTrafficSentBytesCounter)
|
|
Gather.MustRegister(S3DeletedObjectsCounter)
|
|
Gather.MustRegister(S3UploadedObjectsCounter)
|
|
Gather.MustRegister(S3BucketSizeBytesGauge)
|
|
Gather.MustRegister(S3BucketPhysicalSizeBytesGauge)
|
|
Gather.MustRegister(S3BucketObjectCountGauge)
|
|
Gather.MustRegister(S3BucketQuotaBytesGauge)
|
|
Gather.MustRegister(S3BucketReadOnlyGauge)
|
|
|
|
Gather.MustRegister(RemoteCacheReadCounter)
|
|
Gather.MustRegister(RemoteCacheEvictedCounter)
|
|
|
|
Gather.MustRegister(S3LifecycleDispatchCounter)
|
|
Gather.MustRegister(S3LifecycleScheduleDepthGauge)
|
|
Gather.MustRegister(S3LifecycleCursorMinTsNs)
|
|
Gather.MustRegister(S3LifecycleEventCounter)
|
|
Gather.MustRegister(S3LifecycleBootstrapDispatchCounter)
|
|
Gather.MustRegister(S3LifecycleMetadataOnlyCounter)
|
|
Gather.MustRegister(S3LifecycleDispatchLimiterWaitSeconds)
|
|
Gather.MustRegister(S3LifecycleDailyRunShardDurationSeconds)
|
|
Gather.MustRegister(S3LifecycleDailyRunEventsScanned)
|
|
Gather.MustRegister(S3LifecycleDailyRunLastWalkedNs)
|
|
|
|
Gather.MustRegister(UploadErrorCounter)
|
|
|
|
Gather.MustRegister(AdminMaintenanceTasksByStatus)
|
|
Gather.MustRegister(AdminMaintenanceTasksByType)
|
|
Gather.MustRegister(AdminMaintenanceTasksCompletedTotal)
|
|
Gather.MustRegister(AdminMaintenanceTaskDurationSeconds)
|
|
Gather.MustRegister(AdminMaintenanceLastScanTimestampSeconds)
|
|
Gather.MustRegister(AdminMaintenanceNextScanTimestampSeconds)
|
|
Gather.MustRegister(AdminWorkersConnected)
|
|
Gather.MustRegister(AdminWorkerSlots)
|
|
Gather.MustRegister(AdminWorkerEventsTotal)
|
|
|
|
go bucketMetricTTLControl()
|
|
}
|
|
|
|
func LoopPushingMetric(name, instance, addr string, intervalSeconds int) {
|
|
if addr == "" || intervalSeconds == 0 {
|
|
return
|
|
}
|
|
|
|
glog.V(0).Infof("%s server sends metrics to %s every %d seconds", name, addr, intervalSeconds)
|
|
|
|
pusher := push.New(addr, name).Gatherer(Gather).Grouping("instance", instance)
|
|
|
|
for {
|
|
err := pusher.Push()
|
|
if err != nil && !strings.HasPrefix(err.Error(), "unexpected status code 200") {
|
|
glog.V(0).Infof("could not push metrics to prometheus push gateway %s: %v", addr, err)
|
|
}
|
|
if intervalSeconds <= 0 {
|
|
intervalSeconds = 15
|
|
}
|
|
time.Sleep(time.Duration(intervalSeconds) * time.Second)
|
|
}
|
|
}
|
|
|
|
func JoinHostPort(host string, port int) string {
|
|
portStr := strconv.Itoa(port)
|
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
|
return host + ":" + portStr
|
|
}
|
|
return net.JoinHostPort(host, portStr)
|
|
}
|
|
|
|
func StartMetricsServer(ip string, port int) {
|
|
if port == 0 {
|
|
return
|
|
}
|
|
http.Handle("/metrics", promhttp.HandlerFor(Gather, promhttp.HandlerOpts{}))
|
|
glog.Fatal(http.ListenAndServe(JoinHostPort(ip, port), nil))
|
|
}
|
|
|
|
func SourceName(port uint32) string {
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
return net.JoinHostPort(hostname, strconv.Itoa(int(port)))
|
|
}
|
|
|
|
func RecordBucketActiveTime(bucket string) {
|
|
bucketLastActiveLock.Lock()
|
|
bucketLastActiveTsNs[bucket] = time.Now().UnixNano()
|
|
bucketLastActiveLock.Unlock()
|
|
}
|
|
|
|
func RecordRemoteCacheRead(source, bucket string, hit bool) {
|
|
if bucket == "" {
|
|
bucket = "_other"
|
|
}
|
|
result := RemoteCacheResultMiss
|
|
if hit {
|
|
result = RemoteCacheResultHit
|
|
}
|
|
RemoteCacheReadCounter.WithLabelValues(source, bucket, result).Inc()
|
|
}
|
|
|
|
func DeleteBucketMetrics(bucket string) {
|
|
bucketLastActiveLock.Lock()
|
|
delete(bucketLastActiveTsNs, bucket)
|
|
bucketLastActiveLock.Unlock()
|
|
|
|
labels := prometheus.Labels{"bucket": bucket}
|
|
c := S3RequestCounter.DeletePartialMatch(labels)
|
|
c += S3RequestHistogram.DeletePartialMatch(labels)
|
|
c += S3TimeToFirstByteHistogram.DeletePartialMatch(labels)
|
|
c += S3BucketTrafficReceivedBytesCounter.DeletePartialMatch(labels)
|
|
c += S3BucketTrafficSentBytesCounter.DeletePartialMatch(labels)
|
|
c += S3DeletedObjectsCounter.DeletePartialMatch(labels)
|
|
c += S3UploadedObjectsCounter.DeletePartialMatch(labels)
|
|
c += S3BucketSizeBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketPhysicalSizeBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketObjectCountGauge.DeletePartialMatch(labels)
|
|
c += S3BucketQuotaBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketReadOnlyGauge.DeletePartialMatch(labels)
|
|
c += S3LifecycleDispatchCounter.DeletePartialMatch(labels)
|
|
c += S3LifecycleBootstrapDispatchCounter.DeletePartialMatch(labels)
|
|
c += S3LifecycleMetadataOnlyCounter.DeletePartialMatch(labels)
|
|
c += RemoteCacheReadCounter.DeletePartialMatch(labels)
|
|
|
|
glog.V(0).Infof("delete bucket metrics, %s: %d", bucket, c)
|
|
}
|
|
|
|
func DeleteCollectionMetrics(collection string) {
|
|
labels := prometheus.Labels{"collection": collection}
|
|
c := MasterReplicaPlacementMismatch.DeletePartialMatch(labels)
|
|
c += MasterVolumeLayoutWritable.DeletePartialMatch(labels)
|
|
c += MasterVolumeLayoutCrowded.DeletePartialMatch(labels)
|
|
c += MasterUnderReplicatedVolumes.DeletePartialMatch(labels)
|
|
c += VolumeServerDiskSizeGauge.DeletePartialMatch(labels)
|
|
c += VolumeServerVolumeGauge.DeletePartialMatch(labels)
|
|
c += VolumeServerReadOnlyVolumeGauge.DeletePartialMatch(labels)
|
|
|
|
glog.V(0).Infof("delete collection metrics, %s: %d", collection, c)
|
|
}
|
|
|
|
// DeleteVolumeServerCollectionMetrics drops a collection's volume server series
|
|
// once its last volume leaves this server. These gauges are only ever set for
|
|
// collections still present, so the values from the heartbeat that saw the last
|
|
// volume would otherwise stand until the process restarts.
|
|
func DeleteVolumeServerCollectionMetrics(collection string) {
|
|
VolumeServerDiskSizeGauge.DeleteLabelValues(collection, "normal")
|
|
VolumeServerDiskSizeGauge.DeleteLabelValues(collection, "deleted_bytes")
|
|
VolumeServerReadOnlyVolumeGauge.DeletePartialMatch(prometheus.Labels{"collection": collection})
|
|
}
|
|
|
|
func bucketMetricTTLControl() {
|
|
ttlNs := bucketAtiveTTL.Nanoseconds()
|
|
for {
|
|
now := time.Now().UnixNano()
|
|
|
|
// Collect expired buckets under the lock, then release before
|
|
// doing the expensive Prometheus DeletePartialMatch calls.
|
|
// This prevents blocking RecordBucketActiveTime during cleanup.
|
|
bucketLastActiveLock.Lock()
|
|
var expiredBuckets []string
|
|
for bucket, ts := range bucketLastActiveTsNs {
|
|
if (now - ts) > ttlNs {
|
|
expiredBuckets = append(expiredBuckets, bucket)
|
|
delete(bucketLastActiveTsNs, bucket)
|
|
}
|
|
}
|
|
bucketLastActiveLock.Unlock()
|
|
|
|
for _, bucket := range expiredBuckets {
|
|
labels := prometheus.Labels{"bucket": bucket}
|
|
// Only delete gauges and histograms, which represent current state.
|
|
// Counters (traffic, requests, objects) must persist for the process
|
|
// lifetime so that Prometheus rate()/increase() queries work correctly.
|
|
c := S3RequestHistogram.DeletePartialMatch(labels)
|
|
c += S3TimeToFirstByteHistogram.DeletePartialMatch(labels)
|
|
c += S3BucketSizeBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketPhysicalSizeBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketObjectCountGauge.DeletePartialMatch(labels)
|
|
c += S3BucketQuotaBytesGauge.DeletePartialMatch(labels)
|
|
c += S3BucketReadOnlyGauge.DeletePartialMatch(labels)
|
|
glog.V(0).Infof("delete inactive bucket metrics, %s: %d", bucket, c)
|
|
}
|
|
|
|
time.Sleep(bucketAtiveTTL)
|
|
}
|
|
|
|
}
|
|
|
|
// UpdateBucketSizeMetrics updates the bucket size gauges
|
|
// logicalSize is the deduplicated size (accounting for replication)
|
|
// physicalSize is the raw size including all replicas
|
|
// objectCount is the number of objects in the bucket (deduplicated)
|
|
func UpdateBucketSizeMetrics(bucket string, logicalSize, physicalSize float64, objectCount float64) {
|
|
S3BucketSizeBytesGauge.WithLabelValues(bucket).Set(logicalSize)
|
|
S3BucketPhysicalSizeBytesGauge.WithLabelValues(bucket).Set(physicalSize)
|
|
S3BucketObjectCountGauge.WithLabelValues(bucket).Set(objectCount)
|
|
RecordBucketActiveTime(bucket)
|
|
}
|
|
|
|
// UpdateBucketQuotaMetrics updates the per-bucket quota gauges. A non-positive
|
|
// quota removes the quota series so utilization queries like
|
|
// bucket_size_bytes / bucket_quota_bytes only see enforced quotas.
|
|
func UpdateBucketQuotaMetrics(bucket string, quota float64, readOnly bool) {
|
|
if quota > 0 {
|
|
S3BucketQuotaBytesGauge.WithLabelValues(bucket).Set(quota)
|
|
} else {
|
|
S3BucketQuotaBytesGauge.DeleteLabelValues(bucket)
|
|
}
|
|
readOnlyValue := float64(0)
|
|
if readOnly {
|
|
readOnlyValue = 1
|
|
}
|
|
S3BucketReadOnlyGauge.WithLabelValues(bucket).Set(readOnlyValue)
|
|
}
|