diff --git a/base/stats.go b/base/stats.go index 8e7cc1d718..c27aa63c48 100644 --- a/base/stats.go +++ b/base/stats.go @@ -16,6 +16,7 @@ import ( "fmt" "log" "math" + "slices" "strconv" "strings" "sync" @@ -785,39 +786,58 @@ type SgwStat struct { statDesc *prometheus.Desc } +// Name returns the fully qualified name of the stat. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) Name() string { return s.statFQN } +// Unit returns the units the stat uses for example, seconds. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) Unit() string { return s.unit } +// Help returns the help text for the stat. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) Help() string { return s.help } +// AddedVersion returns the version of Sync Gateway this stat was added. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) AddedVersion() string { return s.addedVersion } +// DeprecatedVersion returns the version of Sync Gateway this stat was deprecated. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) DeprecatedVersion() string { return s.deprecatedVersion } +// Stability returns if there is a commitment to keep this stat stable. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) Stability() string { return s.stability } +// LabelKeys returns the label keys for the stat in deterministic order. +// Currently only used for the stat metadata exporter tool. func (s SgwStat) LabelKeys() []string { labelKeys := make([]string, 0, len(s.labels)) for labelKey := range s.labels { labelKeys = append(labelKeys, labelKey) } + // Sort the label keys so that the order is deterministic + slices.Sort(labelKeys) + return labelKeys } +// ValueTypeString returns the string representation of the prometheus.ValueType +// Currently only used for the stat metadata exporter tool. func (s SgwStat) ValueTypeString() string { switch s.statValueType { case prometheus.CounterValue: diff --git a/tools/stats-definition-exporter/main.go b/tools/stats-definition-exporter/main.go index 2a57184cef..c2ef1d1193 100644 --- a/tools/stats-definition-exporter/main.go +++ b/tools/stats-definition-exporter/main.go @@ -74,15 +74,24 @@ func closeAndLogError(logger *log.Logger, c io.Closer) { } } -func getStats(logger *log.Logger) ([]StatDefinition, error) { +func getStats(logger *log.Logger) (StatDefinitions, error) { globalStats, dbStats, err := registerStats() if err != nil { return nil, fmt.Errorf("could not register stats: %w", err) } - // Append the db stat definitions on to the global stat definitions - stats := traverseAndRetrieveStats(logger, globalStats) - stats = append(stats, traverseAndRetrieveStats(logger, dbStats)...) + // Get all the stats + globalStatsDefinitions := traverseAndRetrieveStats(logger, globalStats) + dbStatDefinitions := traverseAndRetrieveStats(logger, dbStats) + + // Merge the two maps + stats := make(StatDefinitions, len(globalStatsDefinitions)+len(dbStatDefinitions)) + for k, v := range globalStatsDefinitions { + stats[k] = v + } + for k, v := range dbStatDefinitions { + stats[k] = v + } return stats, nil } @@ -110,7 +119,7 @@ func registerStats() (*base.GlobalStat, *base.DbStats, error) { return sgStats.GlobalStats, dbStats, nil } -func writeStats(stats []StatDefinition, writer io.Writer) error { +func writeStats(stats StatDefinitions, writer io.Writer) error { encoder := json.NewEncoder(writer) encoder.SetIndent("", "\t") diff --git a/tools/stats-definition-exporter/stat_definition.go b/tools/stats-definition-exporter/stat_definition.go index 517950b5e1..aeb45cb365 100644 --- a/tools/stats-definition-exporter/stat_definition.go +++ b/tools/stats-definition-exporter/stat_definition.go @@ -12,8 +12,10 @@ import ( "github.com/couchbase/sync_gateway/base" ) +// StatDefinitions is a map of the stats fully qualified name to a StatDefinition +type StatDefinitions map[string]StatDefinition + type StatDefinition struct { - Name string `json:"name"` // The fully qualified name of the stat Unit string `json:"unit,omitempty"` // What units the stat value is using such as seconds. Labels []string `json:"labels,omitempty"` // The labels that Prometheus uses to organise some of the stats such as database, collection, etc Help string `json:"help,omitempty"` // A description of what the stat does @@ -26,7 +28,6 @@ type StatDefinition struct { func newStatDefinition(stat base.SgwStatWrapper) StatDefinition { return StatDefinition{ - Name: stat.Name(), Unit: stat.Unit(), Labels: stat.LabelKeys(), Help: stat.Help(), diff --git a/tools/stats-definition-exporter/stat_traverser.go b/tools/stats-definition-exporter/stat_traverser.go index e42fd81053..deeb5e86b0 100644 --- a/tools/stats-definition-exporter/stat_traverser.go +++ b/tools/stats-definition-exporter/stat_traverser.go @@ -15,8 +15,8 @@ import ( "github.com/couchbase/sync_gateway/base" ) -func traverseAndRetrieveStats(logger *log.Logger, s any) []StatDefinition { - var stats []StatDefinition +func traverseAndRetrieveStats(logger *log.Logger, s any) StatDefinitions { + stats := make(StatDefinitions) topLevel := reflect.ValueOf(s) if topLevel.IsNil() { @@ -40,7 +40,7 @@ func traverseAndRetrieveStats(logger *log.Logger, s any) []StatDefinition { stat := field.Interface() statWrapper, ok := stat.(base.SgwStatWrapper) if ok { - stats = append(stats, newStatDefinition(statWrapper)) + stats[statWrapper.Name()] = newStatDefinition(statWrapper) continue } @@ -56,8 +56,10 @@ func traverseAndRetrieveStats(logger *log.Logger, s any) []StatDefinition { field = values.Value() } - // Follow to struct down a level - stats = append(stats, traverseAndRetrieveStats(logger, field.Interface())...) + // Follow to struct down a level and append the stats to the map + for k, v := range traverseAndRetrieveStats(logger, field.Interface()) { + stats[k] = v + } } return stats