Skip to content

Commit f0d5411

Browse files
authored
feat: redis perf and web runtime monitor (#498)
1 parent 812dc48 commit f0d5411

28 files changed

Lines changed: 2453 additions & 198 deletions

File tree

core/common/reqlimit/main.go

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,24 @@ package reqlimit
22

33
import (
44
"context"
5+
"strconv"
56
"time"
67

78
"github.com/labring/aiproxy/core/common"
9+
"github.com/redis/go-redis/v9"
810
log "github.com/sirupsen/logrus"
911
)
1012

13+
type ChannelModelRate struct {
14+
RPM int64 `json:"rpm"`
15+
TPM int64 `json:"tpm"`
16+
RPS int64 `json:"rps"`
17+
TPS int64 `json:"tps"`
18+
}
19+
1120
var (
1221
memoryGroupModelLimiter = NewInMemoryRecord()
13-
redisGroupModelLimiter = newRedisGroupModelRecord()
22+
redisGroupModelLimiter = newRedisGroupModelRecord(func() *redis.Client { return common.RDB })
1423
)
1524

1625
func PushGroupModelRequest(
@@ -61,7 +70,9 @@ func GetGroupModelRequest(ctx context.Context, group, model string) (int64, int6
6170

6271
var (
6372
memoryGroupModelTokennameLimiter = NewInMemoryRecord()
64-
redisGroupModelTokennameLimiter = newRedisGroupModelTokennameRecord()
73+
redisGroupModelTokennameLimiter = newRedisGroupModelTokennameRecord(
74+
func() *redis.Client { return common.RDB },
75+
)
6576
)
6677

6778
func PushGroupModelTokennameRequest(
@@ -120,7 +131,9 @@ func GetGroupModelTokennameRequest(
120131

121132
var (
122133
memoryChannelModelRecord = NewInMemoryRecord()
123-
redisChannelModelRecord = newRedisChannelModelRecord()
134+
redisChannelModelRecord = newRedisChannelModelRecord(
135+
func() *redis.Client { return common.RDB },
136+
)
124137
)
125138

126139
func PushChannelModelRequest(ctx context.Context, channel, model string) (int64, int64, int64) {
@@ -171,7 +184,9 @@ func GetChannelModelRequest(ctx context.Context, channel, model string) (int64,
171184

172185
var (
173186
memoryGroupModelTokensLimiter = NewInMemoryRecord()
174-
redisGroupModelTokensLimiter = newRedisGroupModelTokensRecord()
187+
redisGroupModelTokensLimiter = newRedisGroupModelTokensRecord(
188+
func() *redis.Client { return common.RDB },
189+
)
175190
)
176191

177192
func PushGroupModelTokensRequest(
@@ -222,7 +237,9 @@ func GetGroupModelTokensRequest(ctx context.Context, group, model string) (int64
222237

223238
var (
224239
memoryGroupModelTokennameTokensLimiter = NewInMemoryRecord()
225-
redisGroupModelTokennameTokensLimiter = newRedisGroupModelTokennameTokensRecord()
240+
redisGroupModelTokennameTokensLimiter = newRedisGroupModelTokennameTokensRecord(
241+
func() *redis.Client { return common.RDB },
242+
)
226243
)
227244

228245
func PushGroupModelTokennameTokensRequest(
@@ -289,7 +306,9 @@ func GetGroupModelTokennameTokensRequest(
289306

290307
var (
291308
memoryChannelModelTokensRecord = NewInMemoryRecord()
292-
redisChannelModelTokensRecord = newRedisChannelModelTokensRecord()
309+
redisChannelModelTokensRecord = newRedisChannelModelTokensRecord(
310+
func() *redis.Client { return common.RDB },
311+
)
293312
)
294313

295314
func PushChannelModelTokensRequest(
@@ -341,3 +360,69 @@ func GetChannelModelTokensRequest(ctx context.Context, channel, model string) (i
341360

342361
return memoryChannelModelTokensRecord.GetRequest(time.Minute, channel, model)
343362
}
363+
364+
func GetAllChannelModelRates(ctx context.Context) (map[int64]map[string]ChannelModelRate, error) {
365+
requests := make(map[int64]map[string]ChannelModelRate)
366+
appendSnapshot := func(snapshot recordSnapshot, assign func(rate *ChannelModelRate)) {
367+
if len(snapshot.Keys) != 2 {
368+
return
369+
}
370+
371+
channelID, err := strconv.ParseInt(snapshot.Keys[0], 10, 64)
372+
if err != nil {
373+
return
374+
}
375+
376+
model := snapshot.Keys[1]
377+
378+
if _, ok := requests[channelID]; !ok {
379+
requests[channelID] = make(map[string]ChannelModelRate)
380+
}
381+
382+
rate := requests[channelID][model]
383+
assign(&rate)
384+
requests[channelID][model] = rate
385+
}
386+
387+
var (
388+
requestSnapshots []recordSnapshot
389+
tokenSnapshots []recordSnapshot
390+
err error
391+
)
392+
393+
if common.RedisEnabled {
394+
requestSnapshots, err = redisChannelModelRecord.Snapshot(ctx, time.Minute)
395+
if err != nil {
396+
log.Error("redis snapshot request error: " + err.Error())
397+
}
398+
399+
tokenSnapshots, err = redisChannelModelTokensRecord.Snapshot(ctx, time.Minute)
400+
if err != nil {
401+
log.Error("redis snapshot token error: " + err.Error())
402+
}
403+
}
404+
405+
if len(requestSnapshots) == 0 {
406+
requestSnapshots = memoryChannelModelRecord.Snapshot(time.Minute)
407+
}
408+
409+
if len(tokenSnapshots) == 0 {
410+
tokenSnapshots = memoryChannelModelTokensRecord.Snapshot(time.Minute)
411+
}
412+
413+
for _, snapshot := range requestSnapshots {
414+
appendSnapshot(snapshot, func(rate *ChannelModelRate) {
415+
rate.RPM = snapshot.TotalCount
416+
rate.RPS = snapshot.SecondCount
417+
})
418+
}
419+
420+
for _, snapshot := range tokenSnapshots {
421+
appendSnapshot(snapshot, func(rate *ChannelModelRate) {
422+
rate.TPM = snapshot.TotalCount
423+
rate.TPS = snapshot.SecondCount
424+
})
425+
}
426+
427+
return requests, nil
428+
}

core/common/reqlimit/mem.go

Lines changed: 129 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package reqlimit
22

33
import (
4+
"path"
5+
"slices"
46
"strings"
57
"sync"
68
"sync/atomic"
@@ -14,14 +16,25 @@ type windowCounts struct {
1416

1517
type 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

2128
type InMemoryRecord struct {
2229
entries sync.Map
2330
}
2431

32+
type recordSnapshot struct {
33+
Keys []string
34+
TotalCount int64
35+
SecondCount int64
36+
}
37+
2538
func 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

64106
func (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

100141
func (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+
154247
func 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

Comments
 (0)