feat(lib/policy/expressions): add system load average to bot expression inputs

This lets Anubis dynamically react to system load in order to
increase and decrease the required level of scrutiny. High load? More
scrutiny required. Low load? Less scrutiny required.
This commit is contained in:
Xe Iaso
2025-07-06 18:38:41 +00:00
parent 94db16c0df
commit 9b67cedff2
8 changed files with 185 additions and 31 deletions

View File

@@ -73,6 +73,12 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
return expressions.URLValues{Values: cr.URL.Query()}, true
case "headers":
return expressions.HTTPHeaders{Header: cr.Header}, true
case "load_1m":
return expressions.Load1(), true
case "load_5m":
return expressions.Load5(), true
case "load_15m":
return expressions.Load15(), true
default:
return nil, false
}

View File

@@ -23,6 +23,9 @@ func BotEnvironment() (*cel.Env, error) {
cel.Variable("path", cel.StringType),
cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)),
cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)),
cel.Variable("load_1m", cel.DoubleType),
cel.Variable("load_5m", cel.DoubleType),
cel.Variable("load_15m", cel.DoubleType),
)
}

View File

@@ -0,0 +1,69 @@
package expressions
import (
"context"
"log/slog"
"sync"
"time"
"github.com/shirou/gopsutil/v4/load"
)
type loadAvg struct {
lock sync.RWMutex
data *load.AvgStat
}
func (l *loadAvg) updateThread(ctx context.Context) {
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
l.update()
for {
select {
case <-ticker.C:
l.update()
case <-ctx.Done():
return
}
}
}
func (l *loadAvg) update() {
l.lock.Lock()
defer l.lock.Unlock()
var err error
l.data, err = load.Avg()
if err != nil {
slog.Debug("can't get load average", "err", err)
}
}
var (
globalLoadAvg *loadAvg
)
func init() {
globalLoadAvg = &loadAvg{}
go globalLoadAvg.updateThread(context.Background())
}
func Load1() float64 {
globalLoadAvg.lock.RLock()
defer globalLoadAvg.lock.RUnlock()
return globalLoadAvg.data.Load1
}
func Load5() float64 {
globalLoadAvg.lock.RLock()
defer globalLoadAvg.lock.RUnlock()
return globalLoadAvg.data.Load5
}
func Load15() float64 {
globalLoadAvg.lock.RLock()
defer globalLoadAvg.lock.RUnlock()
return globalLoadAvg.data.Load15
}