mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-21 17:38:00 +00:00
feat(auto): quota-aware AUTO slot scheduling (model tiers, hour/week/month resets) + key scope periods; fix key/slot create/delete UX
This commit is contained in:
@ -2,6 +2,7 @@ package gateway
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Req is one recorded gateway request (audit trail + per-key/per-model stats).
|
||||
@ -54,8 +55,11 @@ type Stats struct {
|
||||
byKeySrc map[string]map[string]*Stat
|
||||
recs []Req
|
||||
maxRecs int
|
||||
modelHour map[string]map[int64]int64 // model -> unix-hour bucket -> tokens
|
||||
}
|
||||
|
||||
const hourSec = 3600
|
||||
|
||||
func NewStats(maxRecords int) *Stats {
|
||||
if maxRecords <= 0 {
|
||||
maxRecords = 3000
|
||||
@ -66,6 +70,7 @@ func NewStats(maxRecords int) *Stats {
|
||||
bySrc: map[string]*Stat{},
|
||||
byKeyModel: map[string]map[string]*Stat{},
|
||||
byKeySrc: map[string]map[string]*Stat{},
|
||||
modelHour: map[string]map[int64]int64{},
|
||||
maxRecs: maxRecords,
|
||||
}
|
||||
}
|
||||
@ -123,12 +128,99 @@ func (s *Stats) Record(r Req) {
|
||||
s.byKeySrc[r.Key] = ks
|
||||
}
|
||||
inc(ks, r.Source, r)
|
||||
// window bucket for quota enforcement (per model, per unix hour)
|
||||
tok := r.Prompt + r.Compl
|
||||
if tok > 0 && r.Model != "" {
|
||||
h := r.Time / hourSec
|
||||
hm := s.modelHour[r.Model]
|
||||
if hm == nil {
|
||||
hm = map[int64]int64{}
|
||||
s.modelHour[r.Model] = hm
|
||||
}
|
||||
hm[h] += tok
|
||||
if len(hm) > 24*40 {
|
||||
for k := range hm {
|
||||
if k < h-24*40 {
|
||||
delete(hm, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.recs = append(s.recs, r)
|
||||
if len(s.recs) > s.maxRecs {
|
||||
s.recs = s.recs[len(s.recs)-s.maxRecs:]
|
||||
}
|
||||
}
|
||||
|
||||
// ModelTokens returns the tokens consumed per model for one gateway key id
|
||||
// (used for per-model token quota enforcement).
|
||||
func (s *Stats) ModelTokens(key string) map[string]int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := map[string]int64{}
|
||||
for k, v := range s.byKeyModel[key] {
|
||||
out[k] = v.Tokens
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// KeyTokens returns the total tokens consumed by one gateway key id.
|
||||
func (s *Stats) KeyTokens(key string) int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
a := s.byKey[key]
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
return a.Tokens
|
||||
}
|
||||
|
||||
// AutoPeriodSeconds maps a quota reset period to its window length in
|
||||
// seconds. "" → 0 (never resets); "hour" → 1h; "week" → 7d; "month" → 30d;
|
||||
// "nhour" → Hours (>=1) hours.
|
||||
func AutoPeriodSeconds(period string, hours int64) int64 {
|
||||
switch period {
|
||||
case "hour":
|
||||
return hourSec
|
||||
case "week":
|
||||
return 7 * 24 * hourSec
|
||||
case "month":
|
||||
return 30 * 24 * hourSec
|
||||
case "nhour":
|
||||
if hours < 1 {
|
||||
hours = 1
|
||||
}
|
||||
return hours * hourSec
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// WindowTokens returns the tokens billed for the model within the last `sec`
|
||||
// seconds (0 = since forever).
|
||||
func (s *Stats) WindowTokens(model string, sec int64) int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
now := time.Now().Unix()
|
||||
hm := s.modelHour[model]
|
||||
if len(hm) == 0 {
|
||||
return 0
|
||||
}
|
||||
var total int64
|
||||
if sec <= 0 {
|
||||
for _, v := range hm {
|
||||
total += v
|
||||
}
|
||||
return total
|
||||
}
|
||||
cut := now - sec
|
||||
for h, v := range hm {
|
||||
if h*hourSec >= cut {
|
||||
total += v
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func rows(m map[string]*Stat) []StatsRow {
|
||||
out := make([]StatsRow, 0, len(m))
|
||||
for k, v := range m {
|
||||
|
||||
Reference in New Issue
Block a user