|
3 | 3 | // Use of this software is governed by the Business Source License |
4 | 4 | // included in the LICENSE file. |
5 | 5 |
|
6 | | -// Package state implements server state with clusters, affiliates, subscriptions, etc. |
| 6 | +// Package state contains internal state-related components such as affiliates, subscriptions, etc. |
7 | 7 | package state |
8 | | - |
9 | | -import ( |
10 | | - "context" |
11 | | - "time" |
12 | | - |
13 | | - prom "github.com/prometheus/client_golang/prometheus" |
14 | | - "github.com/siderolabs/gen/concurrent" |
15 | | - "go.uber.org/zap" |
16 | | -) |
17 | | - |
18 | | -// State keeps the discovery service state. |
19 | | -type State struct { |
20 | | - clusters *concurrent.HashTrieMap[string, *Cluster] |
21 | | - logger *zap.Logger |
22 | | - |
23 | | - mClustersDesc *prom.Desc |
24 | | - mAffiliatesDesc *prom.Desc |
25 | | - mEndpointsDesc *prom.Desc |
26 | | - mSubscriptionsDesc *prom.Desc |
27 | | - mGCRuns prom.Counter |
28 | | - mGCClusters prom.Counter |
29 | | - mGCAffiliates prom.Counter |
30 | | -} |
31 | | - |
32 | | -// NewState create new instance of State. |
33 | | -func NewState(logger *zap.Logger) *State { |
34 | | - return &State{ |
35 | | - clusters: concurrent.NewHashTrieMap[string, *Cluster](), |
36 | | - logger: logger, |
37 | | - mClustersDesc: prom.NewDesc( |
38 | | - "discovery_state_clusters", |
39 | | - "The current number of clusters in the state.", |
40 | | - nil, nil, |
41 | | - ), |
42 | | - mAffiliatesDesc: prom.NewDesc( |
43 | | - "discovery_state_affiliates", |
44 | | - "The current number of affiliates in the state.", |
45 | | - nil, nil, |
46 | | - ), |
47 | | - mEndpointsDesc: prom.NewDesc( |
48 | | - "discovery_state_endpoints", |
49 | | - "The current number of endpoints in the state.", |
50 | | - nil, nil, |
51 | | - ), |
52 | | - mSubscriptionsDesc: prom.NewDesc( |
53 | | - "discovery_state_subscriptions", |
54 | | - "The current number of subscriptions in the state.", |
55 | | - nil, nil, |
56 | | - ), |
57 | | - mGCRuns: prom.NewCounter(prom.CounterOpts{ |
58 | | - Name: "discovery_state_gc_runs_total", |
59 | | - Help: "The number of GC runs.", |
60 | | - }), |
61 | | - mGCClusters: prom.NewCounter(prom.CounterOpts{ |
62 | | - Name: "discovery_state_gc_clusters_total", |
63 | | - Help: "The total number of GC'ed clusters.", |
64 | | - }), |
65 | | - mGCAffiliates: prom.NewCounter(prom.CounterOpts{ |
66 | | - Name: "discovery_state_gc_affiliates_total", |
67 | | - Help: "The total number of GC'ed affiliates.", |
68 | | - }), |
69 | | - } |
70 | | -} |
71 | | - |
72 | | -// GetCluster returns cluster by ID, creating it if needed. |
73 | | -func (state *State) GetCluster(id string) *Cluster { |
74 | | - if cluster, ok := state.clusters.Load(id); ok { |
75 | | - return cluster |
76 | | - } |
77 | | - |
78 | | - cluster, loaded := state.clusters.LoadOrStore(id, NewCluster(id)) |
79 | | - if !loaded { |
80 | | - state.logger.Debug("cluster created", zap.String("cluster_id", id)) |
81 | | - } |
82 | | - |
83 | | - return cluster |
84 | | -} |
85 | | - |
86 | | -// GarbageCollect recursively each cluster, and remove empty clusters. |
87 | | -func (state *State) GarbageCollect(now time.Time) (removedClusters, removedAffiliates int) { |
88 | | - state.clusters.Enumerate(func(key string, cluster *Cluster) bool { |
89 | | - ra, empty := cluster.GarbageCollect(now) |
90 | | - removedAffiliates += ra |
91 | | - |
92 | | - if empty { |
93 | | - state.clusters.CompareAndDelete(key, cluster) |
94 | | - state.logger.Debug("cluster removed", zap.String("cluster_id", key)) |
95 | | - |
96 | | - removedClusters++ |
97 | | - } |
98 | | - |
99 | | - return true |
100 | | - }) |
101 | | - |
102 | | - state.mGCRuns.Inc() |
103 | | - state.mGCClusters.Add(float64(removedClusters)) |
104 | | - state.mGCAffiliates.Add(float64(removedAffiliates)) |
105 | | - |
106 | | - return |
107 | | -} |
108 | | - |
109 | | -// RunGC runs the garbage collection on interval. |
110 | | -func (state *State) RunGC(ctx context.Context, logger *zap.Logger, interval time.Duration) { |
111 | | - ticker := time.NewTicker(interval) |
112 | | - defer ticker.Stop() |
113 | | - |
114 | | - for ctx.Err() == nil { |
115 | | - removedClusters, removedAffiliates := state.GarbageCollect(time.Now()) |
116 | | - clusters, affiliates, endpoints, subscriptions := state.stats() |
117 | | - |
118 | | - logFunc := logger.Debug |
119 | | - if removedClusters > 0 || removedAffiliates > 0 { |
120 | | - logFunc = logger.Info |
121 | | - } |
122 | | - |
123 | | - logFunc( |
124 | | - "garbage collection run", |
125 | | - zap.Int("removed_clusters", removedClusters), |
126 | | - zap.Int("removed_affiliates", removedAffiliates), |
127 | | - zap.Int("current_clusters", clusters), |
128 | | - zap.Int("current_affiliates", affiliates), |
129 | | - zap.Int("current_endpoints", endpoints), |
130 | | - zap.Int("current_subscriptions", subscriptions), |
131 | | - ) |
132 | | - |
133 | | - select { |
134 | | - case <-ctx.Done(): |
135 | | - case <-ticker.C: |
136 | | - } |
137 | | - } |
138 | | -} |
139 | | - |
140 | | -func (state *State) stats() (clusters, affiliates, endpoints, subscriptions int) { |
141 | | - state.clusters.Enumerate(func(_ string, cluster *Cluster) bool { |
142 | | - clusters++ |
143 | | - |
144 | | - a, e, s := cluster.stats() |
145 | | - affiliates += a |
146 | | - endpoints += e |
147 | | - subscriptions += s |
148 | | - |
149 | | - return true |
150 | | - }) |
151 | | - |
152 | | - return |
153 | | -} |
154 | | - |
155 | | -// Describe implements prom.Collector interface. |
156 | | -func (state *State) Describe(ch chan<- *prom.Desc) { |
157 | | - prom.DescribeByCollect(state, ch) |
158 | | -} |
159 | | - |
160 | | -// Collect implements prom.Collector interface. |
161 | | -func (state *State) Collect(ch chan<- prom.Metric) { |
162 | | - clusters, affiliates, endpoints, subscriptions := state.stats() |
163 | | - |
164 | | - ch <- prom.MustNewConstMetric(state.mClustersDesc, prom.GaugeValue, float64(clusters)) |
165 | | - ch <- prom.MustNewConstMetric(state.mAffiliatesDesc, prom.GaugeValue, float64(affiliates)) |
166 | | - ch <- prom.MustNewConstMetric(state.mEndpointsDesc, prom.GaugeValue, float64(endpoints)) |
167 | | - ch <- prom.MustNewConstMetric(state.mSubscriptionsDesc, prom.GaugeValue, float64(subscriptions)) |
168 | | - |
169 | | - ch <- state.mGCRuns |
170 | | - ch <- state.mGCClusters |
171 | | - ch <- state.mGCAffiliates |
172 | | -} |
173 | | - |
174 | | -// Check interfaces. |
175 | | -var ( |
176 | | - _ prom.Collector = (*State)(nil) |
177 | | -) |
0 commit comments