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"})

	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"})

	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(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(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)
}
