Skip to content

Commit

Permalink
perf: use proctree expirable lru cache
Browse files Browse the repository at this point in the history
hashicorp LRU increases RSS over time. This change uses the proctree
lru cache to an expirable one, which will remove entries after a
certain time.

Two flags/options were added to the proctree command to control the
cache TTLs. The default is 120 seconds.

flags:

  --proctree process-cache-ttl=60
  --proctree thread-cache-ttl=60

yaml config:

    cache-ttl:
        process: 60
        thread: 60
  • Loading branch information
geyslan committed Aug 23, 2024
1 parent 976704f commit cb89e2f
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 19 deletions.
2 changes: 2 additions & 0 deletions docs/docs/advanced/data-sources/builtin/process-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Example:
both | process tree is built from both events and signals.
--proctree process-cache=8192 | will cache up to 8192 processes in the tree (LRU cache).
--proctree thread-cache=4096 | will cache up to 4096 threads in the tree (LRU cache).
--proctree process-cache-ttl=60 | will set the process cache element TTL to 60 seconds.
--proctree thread-cache-ttl=60 | will set the thread cache element TTL to 60 seconds.
--proctree disable-procfs-query | Will disable procfs quering during runtime

Use comma OR use the flag multiple times to choose multiple options:
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/install/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ proctree:
cache:
process: 8192
thread: 4096
cache-ttl:
process: 60
thread: 60

capabilities:
bypass: false
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/policies/usage/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ proctree:
cache:
process: 8192
thread: 8192
cache-ttl:
process: 120
thread: 120
# cri:
# - runtime:
# name: docker
Expand Down
3 changes: 3 additions & 0 deletions examples/config/global_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ proctree:
# cache:
# process: 8192
# thread: 4096
# cache-ttl:
# process: 120
# thread: 120

capabilities:
bypass: false
Expand Down
16 changes: 14 additions & 2 deletions pkg/cmd/cobra/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,21 @@ func (c *RegoConfig) flags() []string {
//

type ProcTreeConfig struct {
Source string `mapstructure:"source"`
Cache ProcTreeCacheConfig `mapstructure:"cache"`
Source string `mapstructure:"source"`
Cache ProcTreeCacheConfig `mapstructure:"cache"`
CacheTTL ProcTreeCacheTTLConfig `mapstructure:"cache-ttl"`
}

type ProcTreeCacheConfig struct {
Process int `mapstructure:"process"`
Thread int `mapstructure:"thread"`
}

type ProcTreeCacheTTLConfig struct {
Process int `mapstructure:"process"`
Thread int `mapstructure:"thread"`
}

func (c *ProcTreeConfig) flags() []string {
flags := make([]string, 0)

Expand All @@ -198,6 +204,12 @@ func (c *ProcTreeConfig) flags() []string {
if c.Cache.Thread != 0 {
flags = append(flags, fmt.Sprintf("thread-cache=%d", c.Cache.Thread))
}
if c.CacheTTL.Process != 0 {
flags = append(flags, fmt.Sprintf("process-cache-ttl=%d", c.CacheTTL.Process))
}
if c.CacheTTL.Thread != 0 {
flags = append(flags, fmt.Sprintf("thread-cache-ttl=%d", c.CacheTTL.Thread))
}

return flags
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/cmd/cobra/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,17 @@ proctree:
cache:
process: 8192
thread: 4096
cache-ttl:
process: 5
thread: 10
`,
key: "proctree",
expectedFlags: []string{
"source=events",
"process-cache=8192",
"thread-cache=4096",
"process-cache-ttl=5",
"thread-cache-ttl=10",
},
},
{
Expand Down Expand Up @@ -591,6 +596,20 @@ func TestProcTreeConfigFlags(t *testing.T) {
"thread-cache=4096",
},
},
{
name: "process cache ttl set",
config: ProcTreeConfig{
Source: "",
CacheTTL: ProcTreeCacheTTLConfig{
Process: 5,
Thread: 10,
},
},
expected: []string{
"process-cache-ttl=5",
"thread-cache-ttl=10",
},
},
{
name: "all fields set",
config: ProcTreeConfig{
Expand Down
23 changes: 23 additions & 0 deletions pkg/cmd/flags/proctree.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"time"

"github.com/aquasecurity/tracee/pkg/logger"
"github.com/aquasecurity/tracee/pkg/proctree"
Expand All @@ -20,6 +21,8 @@ Example:
both | process tree is built from both events and signals.
--proctree process-cache=8192 | will cache up to 8192 processes in the tree (LRU cache).
--proctree thread-cache=4096 | will cache up to 4096 threads in the tree (LRU cache).
--proctree process-cache-ttl=60 | will set the process cache element TTL to 60 seconds.
--proctree thread-cache-ttl=60 | will set the thread cache element TTL to 60 seconds.
--proctree disable-procfs-query | Will disable procfs queries during runtime
Use comma OR use the flag multiple times to choose multiple options:
Expand All @@ -35,6 +38,8 @@ func PrepareProcTree(cacheSlice []string) (proctree.ProcTreeConfig, error) {
Source: proctree.SourceNone, // disabled by default
ProcessCacheSize: proctree.DefaultProcessCacheSize,
ThreadCacheSize: proctree.DefaultThreadCacheSize,
ProcessCacheTTL: proctree.DefaultProcessCacheTTL,
ThreadCacheTTL: proctree.DefaultThreadCacheTTL,
ProcfsInitialization: true,
ProcfsQuerying: true,
}
Expand Down Expand Up @@ -93,6 +98,24 @@ func PrepareProcTree(cacheSlice []string) (proctree.ProcTreeConfig, error) {
cacheSet = true
continue
}
if strings.HasPrefix(value, "process-cache-ttl=") {
num := strings.TrimPrefix(value, "process-cache-ttl=")
ttl, err := strconv.Atoi(num)
if err != nil {
return config, err
}
config.ProcessCacheTTL = time.Duration(ttl) * time.Second
continue
}
if strings.HasPrefix(value, "thread-cache-ttl=") {
num := strings.TrimPrefix(value, "thread-cache-ttl=")
ttl, err := strconv.Atoi(num)
if err != nil {
return config, err
}
config.ThreadCacheTTL = time.Duration(ttl) * time.Second
continue
}
if strings.HasPrefix(value, "disable-procfs-query") {
config.ProcfsQuerying = false
continue
Expand Down
41 changes: 24 additions & 17 deletions pkg/proctree/proctree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import (
"sync"
"time"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/hashicorp/golang-lru/v2/expirable"

"github.com/aquasecurity/tracee/pkg/errfmt"
"github.com/aquasecurity/tracee/pkg/logger"
traceetime "github.com/aquasecurity/tracee/pkg/time"
)
Expand Down Expand Up @@ -38,6 +37,8 @@ import (
const (
DefaultProcessCacheSize = 32768
DefaultThreadCacheSize = 32768
DefaultProcessCacheTTL = time.Second * 120
DefaultThreadCacheTTL = time.Second * 120
)

type SourceType int
Expand Down Expand Up @@ -67,17 +68,19 @@ type ProcTreeConfig struct {
Source SourceType
ProcessCacheSize int
ThreadCacheSize int
ProcessCacheTTL time.Duration
ThreadCacheTTL time.Duration
ProcfsInitialization bool // Determine whether to scan procfs data for process tree initialization
ProcfsQuerying bool // Determine whether to query procfs for missing information during runtime
}

// ProcessTree is a tree of processes and threads.
type ProcessTree struct {
processes *lru.Cache[uint32, *Process] // hash -> process
threads *lru.Cache[uint32, *Thread] // hash -> threads
procfsChan chan int // channel of pids to read from procfs
procfsOnce *sync.Once // busy loop debug message throttling
ctx context.Context // context for the process tree
processes *expirable.LRU[uint32, *Process] // hash -> process
threads *expirable.LRU[uint32, *Thread] // hash -> threads
procfsChan chan int // channel of pids to read from procfs
procfsOnce *sync.Once // busy loop debug message throttling
ctx context.Context // context for the process tree
procfsQuery bool
timeNormalizer traceetime.TimeNormalizer
}
Expand All @@ -88,22 +91,22 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig, timeNormalizer t
thrEvicted := 0

// Create caches for processes.
processes, err := lru.NewWithEvict[uint32, *Process](
processes := expirable.NewLRU[uint32, *Process](
config.ProcessCacheSize,
func(uint32, *Process) { procEvited++ },
func(k uint32, v *Process) {
procEvited++
},
config.ProcessCacheTTL,
)
if err != nil {
return nil, errfmt.WrapError(err)
}

// Create caches for threads.
threads, err := lru.NewWithEvict[uint32, *Thread](
threads := expirable.NewLRU[uint32, *Thread](
config.ThreadCacheSize,
func(uint32, *Thread) { thrEvicted++ },
func(k uint32, v *Thread) {
thrEvicted++
},
config.ThreadCacheTTL,
)
if err != nil {
return nil, errfmt.WrapError(err)
}

// Report cache stats if debug is enabled.
go func() {
Expand Down Expand Up @@ -159,6 +162,10 @@ func NewProcessTree(ctx context.Context, config ProcTreeConfig, timeNormalizer t
// GetProcessByHash returns a process by its hash.
func (pt *ProcessTree) GetProcessByHash(hash uint32) (*Process, bool) {
process, ok := pt.processes.Get(hash)
if !ok {
return nil, false
}

return process, ok
}

Expand Down

0 comments on commit cb89e2f

Please sign in to comment.