This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git

commit b9bff1c20e0f5523bef573c33b042c6e1de87be3
Author: Yang Luo <[email protected]>
AuthorDate: Sat Aug 15 14:39:03 2026 +0800

    feat(agentmonitor): make the record window and poll interval configurable
    
    RecordCapacity was a const, so the only bound on the live window was 1000
    records against a 64 KiB payload cap: a busy host could hold roughly 64 MiB 
with
    no way for an operator to trade retention for memory. The file tailers 
polled on
    a hardcoded one second tick, which is expensive on hosts with a long agent
    history and cannot be relaxed.
    
    Both are now read from app.conf as agentRecordCapacity and
    agentMonitorPollSeconds, with the poll default relaxed to five seconds. 
Session
    directories only ever grow and are only ever appended to, so a slower tick 
costs
    a little freshness and saves a great deal of disk traffic.
    
    Resizing the ring buffer keeps the most recent records that still fit rather
    than discarding the window.
---
 agentmonitor/monitor.go | 40 ++++++++++++++++++++++++++++++++--------
 agentmonitor/store.go   | 40 +++++++++++++++++++++++++++++++++++++---
 conf/app.conf           |  9 +++++++--
 conf/conf.go            | 22 ++++++++++++++++++++++
 main.go                 |  7 ++++++-
 5 files changed, 104 insertions(+), 14 deletions(-)

diff --git a/agentmonitor/monitor.go b/agentmonitor/monitor.go
index 0543fe8..b4d260c 100644
--- a/agentmonitor/monitor.go
+++ b/agentmonitor/monitor.go
@@ -21,27 +21,44 @@ import (
        "path/filepath"
        "strings"
        "sync"
+       "time"
 )
 
-// DefaultStateDir stores monitor declarations and tail cursors. Monitoring
-// records themselves are never written here.
-const DefaultStateDir = "./data/agent-patches"
+const (
+       // DefaultStateDir stores monitor declarations and tail cursors. 
Monitoring
+       // records themselves are never written here.
+       DefaultStateDir = "./data/agent-patches"
+
+       // DefaultPollInterval is how often file-tailed agents are rescanned. 
Agent
+       // session directories only ever grow, so polling faster than this 
costs real
+       // disk traffic for very little extra freshness.
+       DefaultPollInterval = 5 * time.Second
+)
 
 var monitorConfig = struct {
        sync.RWMutex
-       stateDir string
-}{stateDir: DefaultStateDir}
+       stateDir     string
+       pollInterval time.Duration
+}{stateDir: DefaultStateDir, pollInterval: DefaultPollInterval}
 
-// Configure selects the directory used for declaration and cursor JSON files.
-// It must be called before Start when the default directory is unsuitable.
-func Configure(stateDir string) {
+// Configure selects the directory used for declaration and cursor JSON files,
+// how often local logs are rescanned, and how many records the live window
+// holds. It must be called before Start when the defaults are unsuitable.
+func Configure(stateDir string, pollInterval time.Duration, recordCapacity 
int) {
        monitorConfig.Lock()
        if strings.TrimSpace(stateDir) == "" {
                monitorConfig.stateDir = DefaultStateDir
        } else {
                monitorConfig.stateDir = filepath.Clean(stateDir)
        }
+       if pollInterval <= 0 {
+               monitorConfig.pollInterval = DefaultPollInterval
+       } else {
+               monitorConfig.pollInterval = pollInterval
+       }
        monitorConfig.Unlock()
+
+       SetRecordCapacity(recordCapacity)
 }
 
 func monitorStatePath(name string) string {
@@ -51,6 +68,13 @@ func monitorStatePath(name string) string {
        return path
 }
 
+func monitorPollInterval() time.Duration {
+       monitorConfig.RLock()
+       interval := monitorConfig.pollInterval
+       monitorConfig.RUnlock()
+       return interval
+}
+
 // Start restores persisted monitor declarations and begins polling their local
 // append-only logs. Each monitor starts independently.
 func Start() error {
diff --git a/agentmonitor/store.go b/agentmonitor/store.go
index d2a1b6b..1659a3b 100644
--- a/agentmonitor/store.go
+++ b/agentmonitor/store.go
@@ -21,8 +21,11 @@ import (
        "time"
 )
 
-// RecordCapacity is the fixed live diagnostic window retained by Gateway.
-const RecordCapacity = 1000
+// DefaultRecordCapacity is the live diagnostic window retained by Gateway when
+// no capacity is configured. Records live only in memory, so this value bounds
+// how much a busy host can retain: each record may hold up to
+// auditutil.MaxPayloadBytes of sanitized payload.
+const DefaultRecordCapacity = 1000
 
 // RecordQuery narrows the live diagnostic window. Empty fields match all
 // values and a non-positive Limit returns every matching record.
@@ -58,12 +61,43 @@ type Store struct {
        sequence uint64
 }
 
-var recordStore = newStore(RecordCapacity)
+var recordStore = newStore(DefaultRecordCapacity)
 
 func newStore(capacity int) *Store {
+       if capacity <= 0 {
+               capacity = DefaultRecordCapacity
+       }
        return &Store{entries: make([]storedRecord, capacity)}
 }
 
+// SetRecordCapacity resizes the live window, keeping the most recent records
+// that still fit. It is a no-op when the capacity is unchanged.
+func SetRecordCapacity(capacity int) {
+       recordStore.SetCapacity(capacity)
+}
+
+// SetCapacity resizes the ring buffer in place, preserving arrival order.
+func (store *Store) SetCapacity(capacity int) {
+       if capacity <= 0 {
+               capacity = DefaultRecordCapacity
+       }
+
+       store.mutex.Lock()
+       defer store.mutex.Unlock()
+       if capacity == len(store.entries) {
+               return
+       }
+
+       retained := store.snapshotLocked()
+       if len(retained) > capacity {
+               retained = retained[len(retained)-capacity:]
+       }
+       store.entries = make([]storedRecord, capacity)
+       copy(store.entries, retained)
+       store.count = len(retained)
+       store.next = store.count % capacity
+}
+
 // AddRecord appends one event to Gateway's process-local monitoring window.
 func AddRecord(record *Record) {
        recordStore.Add(record)
diff --git a/conf/app.conf b/conf/app.conf
index 374105b..1cd0a02 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -33,6 +33,11 @@ appMap =
 acmeEmail = ""
 acmePrivateKey = ""
 
-; Agent patch manifests, config backups, and file-tail cursors. Agent activity
-; records themselves are held only in memory.
+; Agent patch manifests, config backups, ingest tokens, and file-tail cursors.
+; Agent activity records themselves are held only in memory.
 agentPatchStateDir = "./data/agent-patches"
+; How many agent monitoring records to keep in memory. They are never 
persisted,
+; so this is also the memory bound for the live window.
+agentRecordCapacity = 1000
+; How often to rescan the append-only logs of file-tailed agents, in seconds.
+agentMonitorPollSeconds = 5
diff --git a/conf/conf.go b/conf/conf.go
index 77da0c2..856f3ce 100644
--- a/conf/conf.go
+++ b/conf/conf.go
@@ -133,6 +133,28 @@ func GetAgentPatchStateDir() string {
        return dir
 }
 
+// GetAgentRecordCapacity is how many agent monitoring records Gateway keeps in
+// memory. Records are never written to disk, so this value alone bounds the
+// memory the live window can occupy.
+func GetAgentRecordCapacity() int {
+       res, err := strconv.Atoi(GetConfigString("agentRecordCapacity"))
+       if err != nil || res <= 0 {
+               res = 1000
+       }
+       return res
+}
+
+// GetAgentMonitorPollSeconds is how often Gateway rescans the append-only logs
+// of agents that are monitored by tailing files. Larger values trade detection
+// latency for far less disk traffic on hosts with a long agent history.
+func GetAgentMonitorPollSeconds() int {
+       res, err := strconv.Atoi(GetConfigString("agentMonitorPollSeconds"))
+       if err != nil || res <= 0 {
+               res = 5
+       }
+       return res
+}
+
 func GetConfigRealDataSourceName(driverName string) string {
        var dataSourceName string
        if driverName != "mysql" {
diff --git a/main.go b/main.go
index 923e2b6..54d1379 100644
--- a/main.go
+++ b/main.go
@@ -16,6 +16,7 @@ package main
 
 import (
        "fmt"
+       "time"
 
        "github.com/apache/casbin-gateway/agenthook"
        "github.com/apache/casbin-gateway/agentmonitor"
@@ -54,7 +55,11 @@ func main() {
        run.InitSelfStart()
        object.StartMonitorSitesLoop()
 
-       agentmonitor.Configure(conf.GetAgentPatchStateDir())
+       agentmonitor.Configure(
+               conf.GetAgentPatchStateDir(),
+               time.Duration(conf.GetAgentMonitorPollSeconds())*time.Second,
+               conf.GetAgentRecordCapacity(),
+       )
        if err := agentmonitor.Start(); err != nil {
                beego.Error("agent monitor could not start:", err)
        }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to