Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 123 additions & 21 deletions base/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const (
StatUnitUnixTimestamp = "unix timestamp"

StatFormatInt = "int"
StatFormatUint = "uint"
StatFormatFloat = "float"
StatFormatDuration = "duration"
StatFormatBool = "bool"
Expand Down Expand Up @@ -472,10 +473,10 @@ type CacheStats struct {
// The highest sequence number cached.
//
// There may be skipped sequences lower than high_seq_cached.
HighSeqCached *SgwIntStat `json:"high_seq_cached"`
HighSeqCached *SgwUint64Stat `json:"high_seq_cached"`
// The highest contiguous sequence number that has been cached.
HighSeqStable *SgwIntStat `json:"high_seq_stable"`
NonMobileIgnoredCount *SgwIntStat `json:"non_mobile_ignored_count"`
HighSeqStable *SgwUint64Stat `json:"high_seq_stable"`
NonMobileIgnoredCount *SgwIntStat `json:"non_mobile_ignored_count"`
// The total number of active channels.
NumActiveChannels *SgwIntStat `json:"num_active_channels"`
// The total number of skipped sequences. This is a cumulative value.
Expand Down Expand Up @@ -606,9 +607,9 @@ type DatabaseStats struct {
ReplicationBytesReceived *SgwIntStat `json:"replication_bytes_received"`
ReplicationBytesSent *SgwIntStat `json:"replication_bytes_sent"`
// The compaction_attachment_start_time.
CompactionAttachmentStartTime *SgwIntStat `json:"compaction_attachment_start_time"`
CompactionAttachmentStartTime *SgwUint64Stat `json:"compaction_attachment_start_time"`
// The compaction_tombstone_start_time.
CompactionTombstoneStartTime *SgwIntStat `json:"compaction_tombstone_start_time"`
CompactionTombstoneStartTime *SgwUint64Stat `json:"compaction_tombstone_start_time"`
// The total number of writes that left the document in a conflicted state. Includes new conflicts, and mutations that don’t resolve existing conflicts.
ConflictWriteCount *SgwIntStat `json:"conflict_write_count"`
// The total number of instances during import when the document cas had changed, but the document was not imported because the document body had not changed.
Expand All @@ -630,7 +631,7 @@ type DatabaseStats struct {
// The total size of xattrs written (in bytes).
DocWritesXattrBytes *SgwIntStat `json:"doc_writes_xattr_bytes"`
// Highest sequence number seen on the caching DCP feed.
HighSeqFeed *SgwIntStat `json:"high_seq_feed"`
HighSeqFeed *SgwUint64Stat `json:"high_seq_feed"`
// The number of attachments compacted
NumAttachmentsCompacted *SgwIntStat `json:"num_attachments_compacted"`
// The total number of documents read via Couchbase Lite 2.x replication since Sync Gateway node startup.
Expand All @@ -653,19 +654,19 @@ type DatabaseStats struct {
// The total amount of bytes read over the public REST api
PublicRestBytesRead *SgwIntStat `json:"public_rest_bytes_read"`
// The value of the last sequence number assigned. Callers using Set should be holding a mutex or ensure concurrent updates to this value are otherwise safe.
LastSequenceAssignedValue *SgwIntStat `json:"last_sequence_assigned_value"` // TODO: CBG-4579 - Replace with SgwUintStat stat
LastSequenceAssignedValue *SgwUint64Stat `json:"last_sequence_assigned_value"`
// The total number of sequence numbers assigned.
SequenceAssignedCount *SgwIntStat `json:"sequence_assigned_count"`
SequenceAssignedCount *SgwUint64Stat `json:"sequence_assigned_count"`
// The total number of high sequence lookups.
SequenceGetCount *SgwIntStat `json:"sequence_get_count"`
// The total number of times the sequence counter document has been incremented.
SequenceIncrCount *SgwIntStat `json:"sequence_incr_count"`
// The total number of unused, reserved sequences released by Sync Gateway.
SequenceReleasedCount *SgwIntStat `json:"sequence_released_count"`
SequenceReleasedCount *SgwUint64Stat `json:"sequence_released_count"`
// The value of the last sequence number reserved (which may not yet be assigned). Callers using Set should be holding a mutex or ensure concurrent updates to this value are otherwise safe.
LastSequenceReservedValue *SgwIntStat `json:"last_sequence_reserved_value"` // TODO: CBG-4579 - Replace with SgwUintStat stat
LastSequenceReservedValue *SgwUint64Stat `json:"last_sequence_reserved_value"`
// The total number of sequences reserved by Sync Gateway.
SequenceReservedCount *SgwIntStat `json:"sequence_reserved_count"`
SequenceReservedCount *SgwUint64Stat `json:"sequence_reserved_count"`
// The total number of corrupt sequences above the MaxSequencesToRelease threshold seen at the sequence allocator
CorruptSequenceCount *SgwIntStat `json:"corrupt_sequence_count"`
// The total number of warnings relating to the channel name size.
Expand Down Expand Up @@ -986,6 +987,11 @@ type SgwIntStat struct {
AtomicInt
}

type SgwUint64Stat struct {
SgwStat
atomic.Uint64
}

// uint64 is used here because atomic ints do not support floats. Floats are encoded to uint64
type SgwFloatStat struct {
SgwStat
Expand Down Expand Up @@ -1079,6 +1085,102 @@ func (s *SgwIntStat) String() string {
return strconv.FormatInt(s.Value(), 10)
}

// NewUIntStat creates and returns a new unsigned integer Sync Gateway stat (SgwUint64Stat).
// The stat is initialized to initialValue and, unless SkipPrometheusStatsRegistration is true,
// is registered with Prometheus. It returns an error if required metadata is missing or if
// Prometheus registration fails.
//
// Parameters:
//
// subsystem: The Prometheus subsystem segment (e.g. resource_utilization, database) used to build the fully qualified metric name.
// key: The short metric key appended to the subsystem to form the final metric name.
// unit: The unit of measurement (e.g. bytes, seconds). Used for metadata export tooling.
// description: Human-readable help text for the metric. Must be non-empty.
// addedVersion: The Sync Gateway version the stat was introduced. Must be non-empty.
// deprecatedVersion: The version the stat was deprecated, or empty if not deprecated.
// stability: The stability level (committed, volatile, or internal). Must be non-empty.
// labelKeys: Slice of label keys for constant labels. Must align index-wise with labelVals.
// labelVals: Slice of label values corresponding to labelKeys. Length must match labelKeys.
// statValueType: The Prometheus value type (counter or gauge) controlling exposition semantics.
// initialValue: The initial uint64 value assigned to the stat's atomic counter.
//
// Behavior:
// - Validates required metadata (description, addedVersion, stability).
// - Builds a Prometheus descriptor with any constant labels.
// - Sets the initial value atomically.
// - Registers the metric with Prometheus unless SkipPrometheusStatsRegistration is true.
// - Returns (*SgwUint64Stat, error) where error is non-nil on validation or registration failure.
//
// Concurrency:
//
// The underlying value uses atomic.Uint64 for safe concurrent mutation by callers.
//
// Errors:
//
// Returned if required fields are missing or if prometheus.Register fails (e.g. duplicate registration).
func NewUIntStat(subsystem, key, unit, description, addedVersion, deprecatedVersion, stability string, labelKeys, labelVals []string, statValueType prometheus.ValueType, initialValue uint64) (*SgwUint64Stat, error) {
stat, err := newSGWStat(subsystem, key, unit, description, addedVersion, deprecatedVersion, stability, labelKeys, labelVals, statValueType)
if err != nil {
return nil, err
}

wrappedStat := &SgwUint64Stat{
SgwStat: *stat,
}

wrappedStat.Set(initialValue)

if !SkipPrometheusStatsRegistration {
err := prometheus.Register(wrappedStat)
if err != nil {
return nil, err
}
}

return wrappedStat, nil
}

func (s *SgwUint64Stat) FormatString() string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (s *SgwUint64Stat) FormatString() string {
// FormatString returns a name of the stat type used for generating documentation.
func (s *SgwUint64Stat) FormatString() string {

This shows up in documentation https://docs.couchbase.com/sync-gateway/current/manage/stats-monitoring-prometheus.html and https://docs.couchbase.com/sync-gateway/current/manage/stats-monitoring-json.html

but curiously neither page use this value and it isn't generated by the generator that is used to update these pages.

Format string `json:"-"` // The format of the value such as int, float, duration

return StatFormatUint
}

func (s *SgwUint64Stat) Describe(ch chan<- *prometheus.Desc) {
ch <- s.statDesc
}

func (s *SgwUint64Stat) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(s.statDesc, s.statValueType, float64(s.Value()))
}

func (s *SgwUint64Stat) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatUint(s.Value(), 10)), nil
}

func (s *SgwUint64Stat) String() string {
return strconv.FormatUint(s.Value(), 10)
}

func (s *SgwUint64Stat) Set(value uint64) {
s.Store(value)
}

func (s *SgwUint64Stat) SetIfMax(value uint64) {
for {
cur := s.Load()
if cur >= value {
return
}

if s.CompareAndSwap(cur, value) {
return
}
}
}

func (s *SgwUint64Stat) Value() uint64 {
return s.Load()
}

func NewFloatStat(subsystem, key, unit, description, addedVersion, deprecatedVersion, stability string, labelKeys, labelVals []string, statValueType prometheus.ValueType, initialValue float64) (*SgwFloatStat, error) {
stat, err := newSGWStat(subsystem, key, unit, description, addedVersion, deprecatedVersion, stability, labelKeys, labelVals, statValueType)
if err != nil {
Expand Down Expand Up @@ -1387,11 +1489,11 @@ func (d *DbStats) initCacheStats() error {
if err != nil {
return err
}
resUtil.HighSeqCached, err = NewIntStat(SubsystemCacheKey, "high_seq_cached", StatUnitNoUnits, HighSeqCachedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.HighSeqCached, err = NewUIntStat(SubsystemCacheKey, "high_seq_cached", StatUnitNoUnits, HighSeqCachedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
resUtil.HighSeqStable, err = NewIntStat(SubsystemCacheKey, "high_seq_stable", StatUnitNoUnits, HighStableSeqCachedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.HighSeqStable, err = NewUIntStat(SubsystemCacheKey, "high_seq_stable", StatUnitNoUnits, HighStableSeqCachedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
Expand Down Expand Up @@ -1684,11 +1786,11 @@ func (d *DbStats) initDatabaseStats() error {
if err != nil {
return err
}
resUtil.CompactionAttachmentStartTime, err = NewIntStat(SubsystemDatabaseKey, "compaction_attachment_start_time", StatUnitUnixTimestamp, CompactionAttachmentStartTimeDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.GaugeValue, 0)
resUtil.CompactionAttachmentStartTime, err = NewUIntStat(SubsystemDatabaseKey, "compaction_attachment_start_time", StatUnitUnixTimestamp, CompactionAttachmentStartTimeDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.GaugeValue, 0)
if err != nil {
return err
}
resUtil.CompactionTombstoneStartTime, err = NewIntStat(SubsystemDatabaseKey, "compaction_tombstone_start_time", StatUnitUnixTimestamp, CompactionTombstoneStartTimeDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.GaugeValue, 0)
resUtil.CompactionTombstoneStartTime, err = NewUIntStat(SubsystemDatabaseKey, "compaction_tombstone_start_time", StatUnitUnixTimestamp, CompactionTombstoneStartTimeDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.GaugeValue, 0)
if err != nil {
return err
}
Expand Down Expand Up @@ -1728,7 +1830,7 @@ func (d *DbStats) initDatabaseStats() error {
if err != nil {
return err
}
resUtil.HighSeqFeed, err = NewIntStat(SubsystemDatabaseKey, "high_seq_feed", StatUnitNoUnits, HighSeqFeedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.HighSeqFeed, err = NewUIntStat(SubsystemDatabaseKey, "high_seq_feed", StatUnitNoUnits, HighSeqFeedDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
Expand Down Expand Up @@ -1776,11 +1878,11 @@ func (d *DbStats) initDatabaseStats() error {
if err != nil {
return err
}
resUtil.SequenceAssignedCount, err = NewIntStat(SubsystemDatabaseKey, "sequence_assigned_count", StatUnitNoUnits, SequenceAssignedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.SequenceAssignedCount, err = NewUIntStat(SubsystemDatabaseKey, "sequence_assigned_count", StatUnitNoUnits, SequenceAssignedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
resUtil.LastSequenceAssignedValue, err = NewIntStat(SubsystemDatabaseKey, "last_sequence_assigned_value", StatUnitNoUnits, LastSequenceAssignedValueDesc, StatAddedVersion3dot2dot4, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.LastSequenceAssignedValue, err = NewUIntStat(SubsystemDatabaseKey, "last_sequence_assigned_value", StatUnitNoUnits, LastSequenceAssignedValueDesc, StatAddedVersion3dot2dot4, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
Expand All @@ -1792,15 +1894,15 @@ func (d *DbStats) initDatabaseStats() error {
if err != nil {
return err
}
resUtil.SequenceReleasedCount, err = NewIntStat(SubsystemDatabaseKey, "sequence_released_count", StatUnitNoUnits, SequenceReleasedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.SequenceReleasedCount, err = NewUIntStat(SubsystemDatabaseKey, "sequence_released_count", StatUnitNoUnits, SequenceReleasedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
resUtil.SequenceReservedCount, err = NewIntStat(SubsystemDatabaseKey, "sequence_reserved_count", StatUnitNoUnits, SequenceReservedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.SequenceReservedCount, err = NewUIntStat(SubsystemDatabaseKey, "sequence_reserved_count", StatUnitNoUnits, SequenceReservedCountDesc, StatAddedVersion3dot0dot0, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
resUtil.LastSequenceReservedValue, err = NewIntStat(SubsystemDatabaseKey, "last_sequence_reserved_value", StatUnitNoUnits, LastSequenceReservedValueDesc, StatAddedVersion3dot2dot4, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
resUtil.LastSequenceReservedValue, err = NewUIntStat(SubsystemDatabaseKey, "last_sequence_reserved_value", StatUnitNoUnits, LastSequenceReservedValueDesc, StatAddedVersion3dot2dot4, StatDeprecatedVersionNotDeprecated, StatStabilityCommitted, labelKeys, labelVals, prometheus.CounterValue, 0)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion db/background_mgr_attachment_compaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewAttachmentCompactionManager(metadataStore base.DataStore, metaKeys *base

func (a *AttachmentCompactionManager) Init(ctx context.Context, options map[string]any, clusterStatus []byte) error {
database := options["database"].(*Database)
database.DbStats.Database().CompactionAttachmentStartTime.Set(time.Now().UTC().Unix())
database.DbStats.Database().CompactionAttachmentStartTime.Set(uint64(time.Now().UTC().Unix()))

newRunInit := func() error {
uniqueUUID, err := uuid.NewRandom()
Expand Down
2 changes: 1 addition & 1 deletion db/background_mgr_tombstone_compaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func NewTombstoneCompactionManager() *BackgroundManager {

func (t *TombstoneCompactionManager) Init(ctx context.Context, options map[string]any, clusterStatus []byte) error {
database := options["database"].(*Database)
database.DbStats.Database().CompactionTombstoneStartTime.Set(time.Now().UTC().Unix())
database.DbStats.Database().CompactionTombstoneStartTime.Set(uint64(time.Now().UTC().Unix()))

return nil
}
Expand Down
4 changes: 2 additions & 2 deletions db/change_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ func (c *changeCache) updateStats(ctx context.Context) {
// grab skipped sequence stats
skippedSequenceListStats := c.skippedSeqs.getStats()

c.db.DbStats.Database().HighSeqFeed.SetIfMax(int64(c.internalStats.highSeqFeed))
c.db.DbStats.Database().HighSeqFeed.SetIfMax(c.internalStats.highSeqFeed)
c.db.DbStats.Cache().PendingSeqLen.Set(int64(c.internalStats.pendingSeqLen))
c.db.DbStats.CBLReplicationPull().MaxPending.SetIfMax(int64(c.internalStats.maxPending))
c.db.DbStats.Cache().HighSeqStable.Set(int64(c._getMaxStableCached(ctx)))
c.db.DbStats.Cache().HighSeqStable.Set(c._getMaxStableCached(ctx))
c.db.DbStats.Cache().NumCurrentSeqsSkipped.Set(skippedSequenceListStats.NumCurrentSkippedSequencesStat)
c.db.DbStats.Cache().NumSkippedSeqs.Set(skippedSequenceListStats.NumCumulativeSkippedSequencesStat)
c.db.DbStats.Cache().SkippedSequenceSkiplistNodes.Set(skippedSequenceListStats.ListLengthStat)
Expand Down
Loading
Loading