11package reqlimit
22
33import (
4+ "path"
5+ "slices"
46 "strings"
57 "sync"
68 "sync/atomic"
@@ -14,14 +16,25 @@ type windowCounts struct {
1416
1517type entry struct {
1618 sync.Mutex
17- windows map [int64 ]* windowCounts
18- lastAccess atomic.Value
19+ windows map [int64 ]* windowCounts
20+ lastAccess atomic.Value
21+ windowSeconds int64
22+ totalNormal int64
23+ totalOver int64
24+ lastCleanedCutoff int64
25+ aggregateInitialized bool
1926}
2027
2128type InMemoryRecord struct {
2229 entries sync.Map
2330}
2431
32+ type recordSnapshot struct {
33+ Keys []string
34+ TotalCount int64
35+ SecondCount int64
36+ }
37+
2538func NewInMemoryRecord () * InMemoryRecord {
2639 rl := & InMemoryRecord {
2740 entries : sync.Map {},
@@ -45,20 +58,49 @@ func (m *InMemoryRecord) getEntry(keys []string) *entry {
4558 return e
4659}
4760
48- func (m * InMemoryRecord ) cleanupAndCount (e * entry , cutoff int64 ) ( int64 , int64 ) {
61+ func (m * InMemoryRecord ) rebuildAggregateLocked (e * entry , windowSeconds , cutoff int64 ) {
4962 normalCount := int64 (0 )
50-
5163 overCount := int64 (0 )
64+
5265 for ts , wc := range e .windows {
5366 if ts < cutoff {
5467 delete (e .windows , ts )
55- } else {
56- normalCount += wc .normal
57- overCount += wc .over
68+ continue
69+ }
70+
71+ normalCount += wc .normal
72+ overCount += wc .over
73+ }
74+
75+ e .windowSeconds = windowSeconds
76+ e .totalNormal = normalCount
77+ e .totalOver = overCount
78+ e .lastCleanedCutoff = cutoff
79+ e .aggregateInitialized = true
80+ }
81+
82+ func (m * InMemoryRecord ) refreshAggregateLocked (e * entry , nowSecond , windowSeconds int64 ) {
83+ cutoff := nowSecond - windowSeconds
84+
85+ if ! e .aggregateInitialized || e .windowSeconds != windowSeconds {
86+ m .rebuildAggregateLocked (e , windowSeconds , cutoff )
87+ return
88+ }
89+
90+ for ts := e .lastCleanedCutoff ; ts < cutoff ; ts ++ {
91+ wc , ok := e .windows [ts ]
92+ if ! ok {
93+ continue
5894 }
95+
96+ e .totalNormal -= wc .normal
97+ e .totalOver -= wc .over
98+ delete (e .windows , ts )
5999 }
60100
61- return normalCount , overCount
101+ if e .lastCleanedCutoff < cutoff {
102+ e .lastCleanedCutoff = cutoff
103+ }
62104}
63105
64106func (m * InMemoryRecord ) PushRequest (
@@ -76,33 +118,52 @@ func (m *InMemoryRecord) PushRequest(
76118 e .lastAccess .Store (now )
77119
78120 windowStart := now .Unix ()
79- cutoff := windowStart - int64 (duration .Seconds ())
80-
81- normalCount , overCount = m .cleanupAndCount (e , cutoff )
121+ windowSeconds := int64 (duration .Seconds ())
122+ m .refreshAggregateLocked (e , windowStart , windowSeconds )
82123
83124 wc , exists := e .windows [windowStart ]
84125 if ! exists {
85126 wc = & windowCounts {}
86127 e .windows [windowStart ] = wc
87128 }
88129
89- if overed == 0 || normalCount <= overed {
130+ if overed == 0 || e . totalNormal <= overed {
90131 wc .normal += n
91- normalCount += n
132+ e . totalNormal += n
92133 } else {
93134 wc .over += n
94- overCount += n
135+ e . totalOver += n
95136 }
96137
97- return normalCount , overCount , wc .normal + wc .over
138+ return e . totalNormal , e . totalOver , wc .normal + wc .over
98139}
99140
100141func (m * InMemoryRecord ) GetRequest (
101142 duration time.Duration ,
102143 keys ... string ,
103144) (totalCount , secondCount int64 ) {
104145 nowSecond := time .Now ().Unix ()
105- cutoff := nowSecond - int64 (duration .Seconds ())
146+ windowSeconds := int64 (duration .Seconds ())
147+
148+ if ! hasWildcard (keys ) {
149+ value , ok := m .entries .Load (strings .Join (keys , ":" ))
150+ if ! ok {
151+ return 0 , 0
152+ }
153+
154+ e , _ := value .(* entry )
155+ e .Lock ()
156+ m .refreshAggregateLocked (e , nowSecond , windowSeconds )
157+ nowWindow := e .windows [nowSecond ]
158+ totalCount = e .totalNormal + e .totalOver
159+ e .Unlock ()
160+
161+ if nowWindow != nil {
162+ secondCount = nowWindow .normal + nowWindow .over
163+ }
164+
165+ return totalCount , secondCount
166+ }
106167
107168 m .entries .Range (func (key , value any ) bool {
108169 k , _ := key .(string )
@@ -111,11 +172,12 @@ func (m *InMemoryRecord) GetRequest(
111172 if matchKeys (keys , currentKeys ) {
112173 e , _ := value .(* entry )
113174 e .Lock ()
114- normalCount , overCount := m . cleanupAndCount (e , cutoff )
175+ m . refreshAggregateLocked (e , nowSecond , windowSeconds )
115176 nowWindow := e .windows [nowSecond ]
177+ entryTotalCount := e .totalNormal + e .totalOver
116178 e .Unlock ()
117179
118- totalCount += normalCount + overCount
180+ totalCount += entryTotalCount
119181
120182 if nowWindow != nil {
121183 secondCount += nowWindow .normal + nowWindow .over
@@ -151,6 +213,37 @@ func (m *InMemoryRecord) cleanupInactiveEntries(interval, maxInactivity time.Dur
151213 }
152214}
153215
216+ func (m * InMemoryRecord ) Snapshot (duration time.Duration ) []recordSnapshot {
217+ nowSecond := time .Now ().Unix ()
218+ windowSeconds := int64 (duration .Seconds ())
219+ snapshots := make ([]recordSnapshot , 0 )
220+
221+ m .entries .Range (func (key , value any ) bool {
222+ k , _ := key .(string )
223+ e , _ := value .(* entry )
224+ e .Lock ()
225+ m .refreshAggregateLocked (e , nowSecond , windowSeconds )
226+ nowWindow := e .windows [nowSecond ]
227+ totalCount := e .totalNormal + e .totalOver
228+ e .Unlock ()
229+
230+ secondCount := int64 (0 )
231+ if nowWindow != nil {
232+ secondCount = nowWindow .normal + nowWindow .over
233+ }
234+
235+ snapshots = append (snapshots , recordSnapshot {
236+ Keys : parseKeys (k ),
237+ TotalCount : totalCount ,
238+ SecondCount : secondCount ,
239+ })
240+
241+ return true
242+ })
243+
244+ return snapshots
245+ }
246+
154247func parseKeys (key string ) []string {
155248 return strings .Split (key , ":" )
156249}
@@ -161,10 +254,27 @@ func matchKeys(pattern, keys []string) bool {
161254 }
162255
163256 for i , p := range pattern {
164- if p != "*" && p != keys [i ] {
257+ if isGlobPattern (p ) {
258+ matched , err := path .Match (p , keys [i ])
259+ if err != nil || ! matched {
260+ return false
261+ }
262+
263+ continue
264+ }
265+
266+ if p != keys [i ] {
165267 return false
166268 }
167269 }
168270
169271 return true
170272}
273+
274+ func hasWildcard (keys []string ) bool {
275+ return slices .ContainsFunc (keys , isGlobPattern )
276+ }
277+
278+ func isGlobPattern (key string ) bool {
279+ return strings .ContainsAny (key , "*?[" )
280+ }
0 commit comments