diff --git a/docs/docs/advanced/data-sources/builtin/process-tree.md b/docs/docs/advanced/data-sources/builtin/process-tree.md index bbe96cd841a5..8d0eb400c55d 100644 --- a/docs/docs/advanced/data-sources/builtin/process-tree.md +++ b/docs/docs/advanced/data-sources/builtin/process-tree.md @@ -14,7 +14,7 @@ The underlying structure is populated using the core `sched_process_fork`, `sche > Introducing this secondary event source is strategic: it reduces interference with actively traced events, leading to more accurate and granular updates in the process tree. -The number of processes retained in the tree hinges on cache size. We have two separate caches at play: one for processes and another for threads. Both default to a size of 32K, supporting tracking for up to 32,768 processes and the same number of threads. It's worth noting that these are LRU caches: once full, they'll evict the least recently accessed entries to accommodate fresh ones. +The number of processes retained in the tree hinges on cache size. We have two separate caches at play: one for processes and another for threads. The default cache size for processes is 16K, supporting tracking for up to 16,384 processes, while the thread cache is 32K, supporting tracking for up to 32,768 threads. On average, a configuration ratio of 2:1 (thread:cache) is defined, as one thread is created for every process. It's worth noting that these are LRU caches: once full, they'll evict the least recently accessed entries to accommodate fresh ones. The process tree query the procfs upon initialization and during runtime to fill missing data: * During initialization, it runs over all procfs to fill all existing processes and threads @@ -34,7 +34,7 @@ Example: signals | process tree is built from signals. 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 thread-cache=16384 | will cache up to 16384 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 diff --git a/pkg/changelog/changelog.go b/pkg/changelog/changelog.go index 6aef4c269112..19ba2c423c04 100644 --- a/pkg/changelog/changelog.go +++ b/pkg/changelog/changelog.go @@ -6,219 +6,56 @@ import ( "github.com/aquasecurity/tracee/pkg/logger" ) -type comparable interface { - ~int | ~float64 | ~string -} - -type item[T comparable] struct { - timestamp time.Time // timestamp of the change - value T // value of the change -} - -// The changelog package provides a changelog data structure. It is a list of changes, each with a -// timestamp. The changelog can be queried for the value at a given time. - +// Changelog manages a list of changes (entries) for a single type. +// When instantiating a Changelog struct, one must supply the maximum amount of changes +// that can be tracked. +// // ATTENTION: You should use Changelog within a struct and provide methods to access it, -// coordinating access through your struct mutexes. DO NOT EXPOSE the changelog object directly to +// coordinating access through your struct mutexes. DO NOT EXPOSE the Changelog object directly to // the outside world as it is not thread-safe. - type Changelog[T comparable] struct { - changes []item[T] // list of changes - timestamps map[time.Time]struct{} // set of timestamps (used to avoid duplicates) - maxSize int // maximum amount of changes to keep track of + list entryList[T] } -// NewChangelog creates a new changelog. -func NewChangelog[T comparable](maxSize int) *Changelog[T] { - return &Changelog[T]{ - changes: []item[T]{}, - timestamps: map[time.Time]struct{}{}, - maxSize: maxSize, +// NewChangelog initializes a new `Changelog` with the specified maximum number of entries. +func NewChangelog[T comparable](maxEntries MaxEntries) *Changelog[T] { + if maxEntries <= 0 { + logger.Fatalw("maxEntries must be greater than 0") } -} - -// Getters - -// GetCurrent: Observation on single element changelog. -// -// If there's one element in the changelog, after the loop, left would be set to 1 if the single -// timestamp is before the targetTime, and 0 if it's equal or after. -// -// BEFORE: If the single timestamp is before the targetTime, when we return -// clv.changes[left-1].value, returns clv.changes[0].value, which is the expected behavior. -// -// AFTER: If the single timestamp is equal to, or after the targetTime, the current logic would -// return a "zero" value because of the condition if left == 0. -// -// We need to find the last change that occurred before or exactly at the targetTime. The binary -// search loop finds the position where a new entry with the targetTime timestamp would be inserted -// to maintain chronological order: -// -// This position is stored in "left". -// -// So, to get the last entry that occurred before the targetTime, we need to access the previous -// position, which is left-1. -// -// GetCurrent returns the latest value of the changelog. -func (clv *Changelog[T]) GetCurrent() T { - if len(clv.changes) == 0 { - return returnZero[T]() - } - - return clv.changes[len(clv.changes)-1].value -} - -// Get returns the value of the changelog at the given time. -func (clv *Changelog[T]) Get(targetTime time.Time) T { - if len(clv.changes) == 0 { - return returnZero[T]() - } - - idx := clv.findIndex(targetTime) - if idx == 0 { - return returnZero[T]() - } - - return clv.changes[idx-1].value -} -// GetAll returns all the values of the changelog. -func (clv *Changelog[T]) GetAll() []T { - values := make([]T, 0, len(clv.changes)) - for _, change := range clv.changes { - values = append(values, change.value) + newList := newEntryList[T](maxEntries) + // DEBUG: uncomment this to populate entries to measure memory footprint. + // newList.populateEntries() + return &Changelog[T]{ + list: newList, } - return values -} - -// Setters - -// SetCurrent sets the latest value of the changelog. -func (clv *Changelog[T]) SetCurrent(value T) { - clv.setAt(value, time.Now()) } -// Set sets the value of the changelog at the given time. -func (clv *Changelog[T]) Set(value T, targetTime time.Time) { - clv.setAt(value, targetTime) +// Set adds or updates an entry in the Changelog, ordered by timestamp. +// If the new entry has the same value as the latest one, only the timestamp is updated. +// If there are already the maximum number of entries, it reuses or replaces an existing entry. +func (c *Changelog[T]) Set(value T, timestamp time.Time) { + c.list = c.list.set(value, timestamp) } -// private - -// setAt sets the value of the changelog at the given time. -func (clv *Changelog[T]) setAt(value T, targetTime time.Time) { - // If the timestamp is already set, update that value only. - _, ok := clv.timestamps[targetTime] - if ok { - index := clv.findIndex(targetTime) - 1 - if index < 0 { - logger.Debugw("changelog internal error: illegal index for existing timestamp") - } - if !clv.changes[index].timestamp.Equal(targetTime) { // sanity check only (time exists already) - logger.Debugw("changelog internal error: timestamp mismatch") - return - } - if clv.changes[index].value != value { - logger.Debugw("changelog error: value mismatch for same timestamp") - } - clv.changes[index].value = value - return - } - - entry := item[T]{ - timestamp: targetTime, - value: value, - } - - idx := clv.findIndex(entry.timestamp) - // If the changelog has reached its maximum size and the new change would be inserted as the oldest, - // there is no need to add the new change. We can simply return without making any modifications. - if len(clv.changes) >= clv.maxSize && idx == 0 { - return - } - // Insert the new entry in the changelog, keeping the list sorted by timestamp. - clv.changes = append(clv.changes, item[T]{}) - copy(clv.changes[idx+1:], clv.changes[idx:]) - clv.changes[idx] = entry - // Mark the timestamp as set. - clv.timestamps[targetTime] = struct{}{} - - clv.enforceSizeBoundary() +// Get retrieves the value of the entry at or before the given timestamp. +// If no matching entry is found, it returns the default value for the entry type. +func (c *Changelog[T]) Get(timestamp time.Time) T { + return c.list.get(timestamp) } -// findIndex returns the index of the first item in the changelog that is after the given time. -func (clv *Changelog[T]) findIndex(target time.Time) int { - left, right := 0, len(clv.changes) - - for left < right { - middle := (left + right) / 2 - if clv.changes[middle].timestamp.After(target) { - right = middle - } else { - left = middle + 1 - } - } - - return left +// GetCurrent retrieves the most recent value. +// If no entry is found, it returns the default value for the entry type. +func (c *Changelog[T]) GetCurrent() T { + return c.list.getCurrent() } -// enforceSizeBoundary ensures that the size of the inner array doesn't exceed the limit. -// It applies two methods to reduce the log size to the maximum allowed: -// 1. Unite duplicate values that are trailing one another, removing the oldest of the pair. -// 2. Remove the oldest logs as they are likely less important. - -func (clv *Changelog[T]) enforceSizeBoundary() { - if len(clv.changes) <= clv.maxSize { - // Nothing to do - return - } - - boundaryDiff := len(clv.changes) - clv.maxSize - changed := false - - // Compact the slice in place - writeIdx := 0 - for readIdx := 0; readIdx < len(clv.changes); readIdx++ { - nextIdx := readIdx + 1 - if nextIdx < len(clv.changes) && - clv.changes[nextIdx].value == clv.changes[readIdx].value && - boundaryDiff > 0 { - // Remove the oldest (readIdx) from the duplicate pair - delete(clv.timestamps, clv.changes[readIdx].timestamp) - boundaryDiff-- - changed = true - continue - } - - // If elements have been removed or moved, update the map and the slice - if changed { - clv.changes[writeIdx] = clv.changes[readIdx] - } - - writeIdx++ - } - - if changed { - clear(clv.changes[writeIdx:]) - clv.changes = clv.changes[:writeIdx] - } - - if len(clv.changes) <= clv.maxSize { - // Size is within limits after compaction - return - } - - // As it still exceeds maxSize, remove the oldest entries in the remaining slice - boundaryDiff = len(clv.changes) - clv.maxSize - for i := 0; i < boundaryDiff; i++ { - delete(clv.timestamps, clv.changes[i].timestamp) - } - clear(clv.changes[:boundaryDiff]) - clv.changes = clv.changes[boundaryDiff:] +// GetAll retrieves all values, from the newest to the oldest. +func (c *Changelog[T]) GetAll() []T { + return c.list.getAll() } -// returnZero returns the zero value of the type T. -func returnZero[T any]() T { - var zero T - return zero +// Count returns the number of entries recorded. +func (c *Changelog[T]) Count() int { + return len(c.list.entries) } diff --git a/pkg/changelog/changelog_benchmark_test.go b/pkg/changelog/changelog_benchmark_test.go index fff34f286301..044ce87b0441 100644 --- a/pkg/changelog/changelog_benchmark_test.go +++ b/pkg/changelog/changelog_benchmark_test.go @@ -5,229 +5,216 @@ import ( "time" ) -func Benchmark_enforceSizeBoundary(b *testing.B) { - testCases := []struct { - name string - changelog Changelog[int] - }{ - { - name: "No change needed", - changelog: Changelog[int]{ - changes: []item[int]{ - {value: 1, timestamp: getTimeFromSec(1)}, - {value: 2, timestamp: getTimeFromSec(2)}, - }, - timestamps: map[time.Time]struct{}{ - getTimeFromSec(1): {}, - getTimeFromSec(2): {}, - }, - maxSize: 5, - }, - }, - { - name: "Trim excess with duplicates", - changelog: Changelog[int]{ - changes: []item[int]{ - {value: 1, timestamp: getTimeFromSec(1)}, - {value: 1, timestamp: getTimeFromSec(2)}, - {value: 2, timestamp: getTimeFromSec(3)}, - {value: 3, timestamp: getTimeFromSec(4)}, - {value: 3, timestamp: getTimeFromSec(5)}, - }, - timestamps: map[time.Time]struct{}{ - getTimeFromSec(1): {}, - getTimeFromSec(2): {}, - getTimeFromSec(3): {}, - getTimeFromSec(4): {}, - getTimeFromSec(5): {}, - }, - maxSize: 3, - }, - }, - { - name: "Remove oldest entries", - changelog: Changelog[int]{ - changes: []item[int]{ - {value: 1, timestamp: getTimeFromSec(1)}, - {value: 2, timestamp: getTimeFromSec(2)}, - {value: 3, timestamp: getTimeFromSec(3)}, - {value: 4, timestamp: getTimeFromSec(4)}, - }, - timestamps: map[time.Time]struct{}{ - getTimeFromSec(1): {}, - getTimeFromSec(2): {}, - getTimeFromSec(3): {}, - getTimeFromSec(4): {}, - }, - maxSize: 2, - }, - }, - } - - for _, tc := range testCases { - b.Run(tc.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - clv := tc.changelog // Create a copy for each iteration - clv.enforceSizeBoundary() - } - }) - } +// Test cases where the Changelog needs to enforce the size boundary +var testCasesAllScenarios = []struct { + value int + time time.Time +}{ + { + value: 42, + time: getTimeFromSec(0), + }, + { + value: 72, + time: getTimeFromSec(1), + }, + { + value: 642, + time: getTimeFromSec(2), + }, + { + value: 672, + time: getTimeFromSec(3), + }, + { + value: 642, + time: getTimeFromSec(4), + }, + { + value: 672, + time: getTimeFromSec(5), + }, + { + value: 6642, + time: getTimeFromSec(6), + }, + { + value: 672, + time: getTimeFromSec(7), + }, + { + value: 642, + time: getTimeFromSec(8), + }, + { + value: 6672, + time: getTimeFromSec(9), + }, + { + value: 9642, + time: getTimeFromSec(10), + }, + { + value: 0, + time: getTimeFromSec(0), + }, + { + value: 0, + time: getTimeFromSec(1), + }, + { + value: 0, + time: getTimeFromSec(2), + }, + { + value: 0, + time: getTimeFromSec(3), + }, } -func Benchmark_setAt(b *testing.B) { - // Test cases where the Changelog needs to enforce the size boundary - testCasesAllScenarios := []struct { - value int - time time.Time - }{ - { - value: 42, - time: getTimeFromSec(0), - }, - { - value: 72, - time: getTimeFromSec(1), - }, - { - value: 642, - time: getTimeFromSec(2), - }, - { - value: 672, - time: getTimeFromSec(3), // will trigger removal of oldest entry - }, - { - value: 642, - time: getTimeFromSec(4), // will trigger coalescing of duplicate values - }, - { - value: 672, - time: getTimeFromSec(5), // will trigger coalescing of duplicate values - }, - { - value: 6642, - time: getTimeFromSec(6), // will trigger removal of oldest entry - }, - { - value: 672, - time: getTimeFromSec(7), // will trigger coalescing of duplicate values - }, - { - value: 642, - time: getTimeFromSec(8), // will trigger coalescing of duplicate values - }, - { - value: 6672, - time: getTimeFromSec(9), // will trigger removal of oldest entry - }, - { - value: 9642, - time: getTimeFromSec(10), // will trigger removal of oldest entry - }, - { - value: 0, - time: getTimeFromSec(0), // will just update the value - }, - { - value: 0, - time: getTimeFromSec(1), // will just update the value - }, - { - value: 0, - time: getTimeFromSec(2), // will just update the value - }, - { - value: 0, - time: getTimeFromSec(3), // will just update the value - }, - } +// Test cases where the changelog is within the maximum size limit +var testCasesWithinLimit = []struct { + value int + time time.Time +}{ + { + value: 0, + time: getTimeFromSec(0), + }, + { + value: 1, + time: getTimeFromSec(1), + }, + { + value: 2, + time: getTimeFromSec(2), + }, + { + value: 3, + time: getTimeFromSec(3), + }, + { + value: 4, + time: getTimeFromSec(4), + }, + { + value: 5, + time: getTimeFromSec(5), + }, + { + value: 6, + time: getTimeFromSec(6), + }, + { + value: 7, + time: getTimeFromSec(7), + }, + { + value: 8, + time: getTimeFromSec(8), + }, + { + value: 9, + time: getTimeFromSec(9), + }, + { + value: 10, + time: getTimeFromSec(10), + }, + { + value: 11, + time: getTimeFromSec(11), + }, + { + value: 12, + time: getTimeFromSec(12), + }, + { + value: 13, + time: getTimeFromSec(13), + }, + { + value: 14, + time: getTimeFromSec(14), + }, +} +func Benchmark_Set(b *testing.B) { b.Run("All Scenarios", func(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() clv := NewChangelog[int](3) b.StartTimer() for _, tc := range testCasesAllScenarios { - clv.setAt(tc.value, tc.time) + clv.Set(tc.value, tc.time) } } }) - // Test cases where the changelog is within the maximum size limit - testCasesWithinLimit := []struct { - value int - time time.Time - }{ - { - value: 0, - time: getTimeFromSec(0), - }, - { - value: 1, - time: getTimeFromSec(1), - }, - { - value: 2, - time: getTimeFromSec(2), - }, - { - value: 3, - time: getTimeFromSec(3), - }, - { - value: 4, - time: getTimeFromSec(4), - }, - { - value: 5, - time: getTimeFromSec(5), - }, - { - value: 6, - time: getTimeFromSec(6), - }, - { - value: 7, - time: getTimeFromSec(7), - }, - { - value: 8, - time: getTimeFromSec(8), - }, - { - value: 9, - time: getTimeFromSec(9), - }, - { - value: 10, - time: getTimeFromSec(10), - }, - { - value: 11, - time: getTimeFromSec(11), - }, - { - value: 12, - time: getTimeFromSec(12), - }, - { - value: 13, - time: getTimeFromSec(13), - }, - { - value: 14, - time: getTimeFromSec(14), - }, - } - b.Run("Within Limit", func(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() clv := NewChangelog[int](15) b.StartTimer() for _, tc := range testCasesWithinLimit { - clv.setAt(tc.value, tc.time) + clv.Set(tc.value, tc.time) + } + } + }) +} + +func Benchmark_Get(b *testing.B) { + b.Run("All Scenarios", func(b *testing.B) { + clv := NewChangelog[int](3) + for _, tc := range testCasesAllScenarios { + clv.Set(tc.value, tc.time) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, tc := range testCasesAllScenarios { + _ = clv.Get(tc.time) + } + } + }) + + b.Run("Within Limit", func(b *testing.B) { + clv := NewChangelog[int](15) + for _, tc := range testCasesWithinLimit { + clv.Set(tc.value, tc.time) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, tc := range testCasesWithinLimit { + _ = clv.Get(tc.time) } } }) } + +func Benchmark_GetCurrent(b *testing.B) { + b.Run("All Scenarios", func(b *testing.B) { + clv := NewChangelog[int](3) + for _, tc := range testCasesAllScenarios { + clv.Set(tc.value, tc.time) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = clv.GetCurrent() + } + }) + + b.Run("Within Limit", func(b *testing.B) { + clv := NewChangelog[int](15) + for _, tc := range testCasesWithinLimit { + clv.Set(tc.value, tc.time) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = clv.GetCurrent() + } + }) +} diff --git a/pkg/changelog/changelog_test.go b/pkg/changelog/changelog_test.go index f831aa7e5231..78a99454476e 100644 --- a/pkg/changelog/changelog_test.go +++ b/pkg/changelog/changelog_test.go @@ -1,350 +1,204 @@ package changelog import ( - "reflect" + "fmt" + "os" "testing" "time" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/tracee/pkg/utils" ) -func TestChangelog(t *testing.T) { - t.Parallel() - - t.Run("GetCurrent on an empty changelog", func(t *testing.T) { - cl := NewChangelog[int](3) +func getTimeFromSec(second int) time.Time { + return time.Unix(int64(second), 0) +} + +func TestChangelog_GetZeroValue(t *testing.T) { + changelog := NewChangelog[int](1) + time0 := getTimeFromSec(0) + + // Assert zero value before any set + assert.Equal(t, 0, changelog.Get(time0), "Expected zero value for testInt0") + assert.Equal(t, 0, changelog.GetCurrent(), "Expected zero value for testInt0") - // Test GetCurrent on an empty changelog - assert.Zero(t, cl.GetCurrent()) - }) - - t.Run("Set and get", func(t *testing.T) { - cl := NewChangelog[int](3) - testVal := 42 - - cl.SetCurrent(testVal) - assert.Equal(t, testVal, cl.GetCurrent()) - }) - - t.Run("Set and get on set time", func(t *testing.T) { - cl := NewChangelog[int](3) - testVal1 := 42 - testVal2 := 76 - testVal3 := 76 - - // Test with 3 stages of the changelog to make sure the binary search works well for - // different lengths (both odd and even). - now := time.Now() - cl.Set(testVal1, now) - assert.Equal(t, testVal1, cl.Get(now)) - - cl.Set(testVal2, now.Add(time.Second)) - assert.Equal(t, testVal1, cl.Get(now)) - assert.Equal(t, testVal2, cl.Get(now.Add(time.Second))) - - cl.Set(testVal3, now.Add(2*time.Second)) - assert.Equal(t, testVal1, cl.Get(now)) - assert.Equal(t, testVal2, cl.Get(now.Add(time.Second))) - assert.Equal(t, testVal3, cl.Get(now.Add(2*time.Second))) - }) - - t.Run("Set twice on the same time", func(t *testing.T) { - cl := NewChangelog[int](3) - testVal := 42 - - now := time.Now() - cl.Set(testVal, now) - cl.Set(testVal, now) - assert.Equal(t, testVal, cl.Get(now)) - assert.Len(t, cl.GetAll(), 1) - assert.Equal(t, testVal, cl.Get(now)) - }) - - t.Run("Get on an empty changelog", func(t *testing.T) { - cl := NewChangelog[int](3) - - assert.Zero(t, cl.GetCurrent()) - }) - - t.Run("Test 1 second interval among changes", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(2 * time.Second) - cl.SetCurrent(2) - time.Sleep(2 * time.Second) - cl.SetCurrent(3) - - now := time.Now() - - assert.Equal(t, 1, cl.Get(now.Add(-4*time.Second))) - assert.Equal(t, 2, cl.Get(now.Add(-2*time.Second))) - assert.Equal(t, 3, cl.Get(now)) - }) - - t.Run("Test 100 milliseconds interval among changes", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - - now := time.Now() - - assert.Equal(t, 1, cl.Get(now.Add(-200*time.Millisecond))) - assert.Equal(t, 2, cl.Get(now.Add(-100*time.Millisecond))) - assert.Equal(t, 3, cl.Get(now)) - }) - - t.Run("Test getting all values at once", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - - expected := []int{1, 2, 3} - assert.Equal(t, expected, cl.GetAll()) - }) - - t.Run("Pass max size wit repeated values", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - - now := time.Now() - assert.Equal(t, 1, cl.Get(now.Add(-300*time.Millisecond))) - assert.Equal(t, 1, cl.Get(now.Add(-200*time.Millisecond))) // oldest 2 is removed, so 1 is returned - assert.Equal(t, 2, cl.Get(now.Add(-100*time.Millisecond))) - assert.Equal(t, 3, cl.Get(now)) - assert.Len(t, cl.GetAll(), 3) - }) - - t.Run("Pass max size with unique values", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(4) - - now := time.Now() - assert.Equal(t, 0, cl.Get(now.Add(-300*time.Millisecond))) - assert.Equal(t, 2, cl.Get(now.Add(-200*time.Millisecond))) - assert.Equal(t, 3, cl.Get(now.Add(-100*time.Millisecond))) - assert.Equal(t, 4, cl.Get(now.Add(time.Millisecond))) - assert.Len(t, cl.GetAll(), 3) - }) - - t.Run("Pass max size with new old value", func(t *testing.T) { - cl := NewChangelog[int](3) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - - now := time.Now() - cl.Set(4, now.Add(-400*time.Millisecond)) - - // Make sure that the new value was not added - assert.Equal(t, 0, cl.Get(now.Add(-300*time.Millisecond))) - - // Sanity check - assert.Equal(t, 1, cl.Get(now.Add(-200*time.Millisecond))) - assert.Equal(t, 2, cl.Get(now.Add(-100*time.Millisecond))) - assert.Equal(t, 3, cl.Get(now)) - assert.Len(t, cl.GetAll(), 3) - }) - - t.Run("Zero sized changelog", func(t *testing.T) { - cl := NewChangelog[int](0) - - cl.SetCurrent(1) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(2) - time.Sleep(100 * time.Millisecond) - cl.SetCurrent(3) - - now := time.Now() - cl.Set(4, now.Add(-400*time.Millisecond)) - - // Make sure that the new value was not added - - // Sanity check - assert.Equal(t, 0, cl.Get(now.Add(-300*time.Millisecond))) - assert.Equal(t, 0, cl.Get(now.Add(-200*time.Millisecond))) - assert.Equal(t, 0, cl.Get(now.Add(-100*time.Millisecond))) - assert.Equal(t, 0, cl.Get(now)) - assert.Empty(t, cl.GetAll()) - }) - - t.Run("Test enforceSizeBoundary", func(t *testing.T) { - type TestCase struct { - name string - maxSize int - initialChanges []item[int] - expectedChanges []item[int] - expectedTimestamps map[time.Time]struct{} - } - - testCases := []TestCase{ - { - name: "No Action Required", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 3}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 3}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(42): {}, - getTimeFromSec(43): {}, - getTimeFromSec(44): {}, - }, - }, - { - name: "Basic Removal of Oldest Entries", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 3}, - {timestamp: getTimeFromSec(45), value: 4}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 3}, - {timestamp: getTimeFromSec(45), value: 4}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(43): {}, - getTimeFromSec(44): {}, - getTimeFromSec(45): {}, - }, - }, - { - name: "Compacting Duplicate Values - Start", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 1}, - {timestamp: getTimeFromSec(44), value: 2}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(43), value: 1}, - {timestamp: getTimeFromSec(44), value: 2}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(43): {}, - getTimeFromSec(44): {}, - getTimeFromSec(45): {}, - }, - }, - { - name: "Compacting Duplicate Values - Middle", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 2}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(44), value: 2}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(42): {}, - getTimeFromSec(44): {}, - getTimeFromSec(45): {}, - }, - }, - { - name: "Compacting Duplicate Values - End", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 3}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(45), value: 3}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(42): {}, - getTimeFromSec(43): {}, - getTimeFromSec(45): {}, - }, - }, - { - name: "Combination of Compaction and Removal of Oldest Entries", - maxSize: 3, - initialChanges: []item[int]{ - {timestamp: getTimeFromSec(42), value: 1}, - {timestamp: getTimeFromSec(43), value: 2}, - {timestamp: getTimeFromSec(44), value: 2}, - {timestamp: getTimeFromSec(45), value: 2}, - {timestamp: getTimeFromSec(46), value: 3}, - {timestamp: getTimeFromSec(47), value: 4}, - }, - expectedChanges: []item[int]{ - {timestamp: getTimeFromSec(45), value: 2}, - {timestamp: getTimeFromSec(46), value: 3}, - {timestamp: getTimeFromSec(47), value: 4}, - }, - expectedTimestamps: map[time.Time]struct{}{ - getTimeFromSec(45): {}, - getTimeFromSec(46): {}, - getTimeFromSec(47): {}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cl := NewChangelog[int](tc.maxSize) - for _, change := range tc.initialChanges { - cl.Set(change.value, change.timestamp) - } - - cl.enforceSizeBoundary() - - eq := reflect.DeepEqual(cl.timestamps, tc.expectedTimestamps) - assert.True(t, eq) - - eq = reflect.DeepEqual(cl.changes, tc.expectedChanges) - assert.True(t, eq) - }) - } - }) + // Set and assert value + changelog.Set(3001, time0) + assert.Equal(t, 3001, changelog.Get(time0), "Expected testInt0 to be 3001") + assert.Equal(t, 3001, changelog.GetCurrent(), "Expected current testInt0 to be 3001") + + // Check the count of entries + assert.Equal(t, 1, changelog.Count(), "Expected 1 entry") } -func getTimeFromSec(second int) time.Time { - return time.Unix(int64(second), 0) +func TestChangelog_ShiftAndReplace(t *testing.T) { + changelog := NewChangelog[string](2) + + // Set entries and assert initial values + changelog.Set("initial", getTimeFromSec(0)) + changelog.Set("updated", getTimeFromSec(1)) + assert.Equal(t, "initial", changelog.Get(getTimeFromSec(0)), "Expected first entry to be 'initial'") + assert.Equal(t, "updated", changelog.Get(getTimeFromSec(1)), "Expected second entry to be 'updated'") + + // Test shifting and replacement + changelog.Set("final", getTimeFromSec(2)) + assert.Equal(t, "updated", changelog.Get(getTimeFromSec(1)), "Expected oldest entry to be removed") + assert.Equal(t, "final", changelog.Get(getTimeFromSec(2)), "Expected newest entry to be 'final'") + assert.Equal(t, "final", changelog.GetCurrent(), "Expected current entry to be 'final'") + + // Check the count of entries + assert.Equal(t, 2, changelog.Count(), "Expected 2 entries") +} + +func TestChangelog_ReplaceMostRecentWithSameValue(t *testing.T) { + changelog := NewChangelog[string](2) + + // Set entries and assert initial value + changelog.Set("initial", getTimeFromSec(0)) + assert.Equal(t, "initial", changelog.Get(getTimeFromSec(0)), "Expected first entry to be 'initial'") + changelog.Set("initial", getTimeFromSec(1)) + assert.Equal(t, "initial", changelog.Get(getTimeFromSec(1)), "Expected first entry to have timestamp updated") + + // Test replacement of most recent entry with same value + changelog.Set("second", getTimeFromSec(2)) + assert.Equal(t, "initial", changelog.Get(getTimeFromSec(1)), "Expected first entry to be 'initial'") + assert.Equal(t, "second", changelog.Get(getTimeFromSec(2)), "Expected second entry to have timestamp updated") + + // Check the count of entries + assert.Equal(t, 2, changelog.Count(), "Expected 2 entries") +} + +func TestChangelog_InsertWithOlderTimestamp(t *testing.T) { + changelog := NewChangelog[string](3) + now := getTimeFromSec(0) + + // Insert entries with increasing timestamps + changelog.Set("first", now) + changelog.Set("second", now.Add(1*time.Second)) + changelog.Set("third", now.Add(2*time.Second)) + + // Insert an entry with an older timestamp + changelog.Set("older", now.Add(1*time.Millisecond)) + + // Check the count of entries + assert.Equal(t, 3, changelog.Count(), "Expected 3 entries") + + // Verify the order of entries + assert.Equal(t, "older", changelog.Get(now.Add(1*time.Millisecond)), "Expected 'older' to be the first entry") + assert.Equal(t, "second", changelog.Get(now.Add(1*time.Second)), "Expected 'second' to be the second entry") + assert.Equal(t, "third", changelog.Get(now.Add(2*time.Second)), "Expected 'third' to be the last entry") + + // Insert an entry with an intermediate timestamp + changelog.Set("second-third", now.Add(1*time.Second+1*time.Millisecond)) + + // Verify the order of entries + assert.Equal(t, "older", changelog.Get(now.Add(1*time.Millisecond)), "Expected 'older' to be the first entry") + assert.Equal(t, "second-third", changelog.Get(now.Add(1*time.Second+1*time.Millisecond)), "Expected 'second-third' to be the second entry") + assert.Equal(t, "third", changelog.Get(now.Add(2*time.Second)), "Expected 'third' to be the last entry") + + // Check the count of entries + assert.Equal(t, 3, changelog.Count(), "Expected 3 entries") +} + +func TestChangelog_InsertSameValueWithNewTimestamp(t *testing.T) { + changelog := NewChangelog[string](3) + + // Insert entries with increasing timestamps + changelog.Set("same", getTimeFromSec(0)) + + // Replace the last entry with the same value but a new timestamp + changelog.Set("same", getTimeFromSec(1)) + + // Verify the order of entries + assert.Equal(t, "same", changelog.Get(getTimeFromSec(1)), "Expected 'same' to be the second entry") + + // Insert entries with sequential timestamps + changelog.Set("new", getTimeFromSec(2)) + changelog.Set("other", getTimeFromSec(3)) + + // Replace the last entry with the same value but a new timestamp + changelog.Set("other", getTimeFromSec(4)) + + // Verify the order of entries + assert.Equal(t, "same", changelog.Get(getTimeFromSec(1)), "Expected 'same' to be the first entry") + assert.Equal(t, "new", changelog.Get(getTimeFromSec(2)), "Expected 'new' to be the second entry") + assert.Equal(t, "other", changelog.Get(getTimeFromSec(4)), "Expected 'other' to be the last entry") + + // Check the count of entries + assert.Equal(t, 3, changelog.Count(), "Expected 3 entries") +} + +func TestChangelog_StructType(t *testing.T) { + type testStruct struct { + A int + B string + } + + changelog := NewChangelog[testStruct](3) + now := getTimeFromSec(0) + + // Insert an entry + tsFirst := testStruct{A: 1, B: "first"} + changelog.Set(tsFirst, now) + + // Verify the entry + assert.Equal(t, tsFirst, changelog.Get(now), fmt.Sprintf("Expected %v", tsFirst)) + + // Check the count of entries + assert.Equal(t, 1, changelog.Count(), "Expected 1 entry") + + // Insert a new entry + tsSecond := testStruct{A: 2, B: "second"} + changelog.Set(tsSecond, now.Add(1*time.Second)) + + // Verify the entry + assert.Equal(t, tsSecond, changelog.Get(now.Add(1*time.Second)), fmt.Sprintf("Expected %v", tsSecond)) + + // Check the count of entries + assert.Equal(t, 2, changelog.Count(), "Expected 2 entries") + + // Insert a new entry + tsThird := testStruct{A: 3, B: "third"} + changelog.Set(tsThird, now.Add(2*time.Second)) + + // Verify the entry + assert.Equal(t, tsThird, changelog.Get(now.Add(2*time.Second)), fmt.Sprintf("Expected %v", tsThird)) + + // Check the count of entries + assert.Equal(t, 3, changelog.Count(), "Expected 3 entries") + + // Insert a new entry + tsFourth := testStruct{A: 4, B: "fourth"} + changelog.Set(tsFourth, now.Add(3*time.Second)) + + // Verify the entry + assert.Equal(t, tsFourth, changelog.Get(now.Add(3*time.Second)), fmt.Sprintf("Expected %v", tsFourth)) + + // Check the count of entries + assert.Equal(t, 3, changelog.Count(), "Expected 3 entries") + + // Verify the order of entries + assert.Equal(t, tsSecond, changelog.Get(now.Add(1*time.Second)), fmt.Sprintf("Expected %v", tsSecond)) + assert.Equal(t, tsThird, changelog.Get(now.Add(2*time.Second)), fmt.Sprintf("Expected %v", tsThird)) + assert.Equal(t, tsFourth, changelog.Get(now.Add(3*time.Second)), fmt.Sprintf("Expected %v", tsFourth)) + assert.Equal(t, tsFourth, changelog.GetCurrent(), fmt.Sprintf("Expected %v", tsFourth)) +} + +// TestChangelog_PrintSizes prints the sizes of the structs used in the Changelog type. +// Run it as DEBUG test to see the output. +func TestChangelog_PrintSizes(t *testing.T) { + changelog1 := NewChangelog[int](1) + utils.PrintStructSizes(os.Stdout, changelog1) + + entry1 := entry[int]{} + utils.PrintStructSizes(os.Stdout, entry1) + + // + + changelog2 := NewChangelog[string](1) + utils.PrintStructSizes(os.Stdout, changelog2) + + entry2 := entry[string]{} + utils.PrintStructSizes(os.Stdout, entry2) } diff --git a/pkg/changelog/entry.go b/pkg/changelog/entry.go new file mode 100644 index 000000000000..a3093e0ca4df --- /dev/null +++ b/pkg/changelog/entry.go @@ -0,0 +1,170 @@ +package changelog + +import ( + "time" +) + +// MaxEntries represents the maximum number of changes that can be tracked. +type MaxEntries uint8 + +// entry is an internal structure representing a single change in the entryList. +// It includes the timestamp and the value of the change. +type entry[T comparable] struct { + tsUnixNano int64 // timestamp of the change (nanoseconds since epoch) + value T // value of the change +} + +func newEntry[T comparable](value T, timestamp time.Time) entry[T] { + return entry[T]{ + tsUnixNano: timestamp.UnixNano(), + value: value, + } +} + +// entryList is an internal structure that stores a list of changes (entries). +type entryList[T comparable] struct { + maxEntries MaxEntries // maximum number of entries + entries []entry[T] // list of entries +} + +func newEntryList[T comparable](maxEntries MaxEntries) entryList[T] { + return entryList[T]{ + maxEntries: maxEntries, + entries: make([]entry[T], 0), // don't pre-allocate full capacity + } +} + +func (el *entryList[T]) set(value T, timestamp time.Time) entryList[T] { + tsUnixNano := timestamp.UnixNano() + entries := el.entries + length := len(entries) + + // if there are entries, check if the last entry has the same value + + if length > 0 { + lastIdx := length - 1 + if entries[lastIdx].value == value && tsUnixNano > entries[lastIdx].tsUnixNano { + // only update timestamp and return + entries[lastIdx].tsUnixNano = tsUnixNano + return *el + } + } + + entry := newEntry[T](value, timestamp) + + // if there is space, insert the new entry at the correct position + + if length < int(el.maxEntries) { + insertPos := findInsertIdx(entries, tsUnixNano) + if insertPos == length { + entries = append(entries, entry) + } else { + entries = insertAt(insertPos, entries, entry) + } + el.entries = entries // replace entries with the new list + return *el + } + + // as there is no space, replace an entry + + replaceIdx := length - 1 // default index to replace + if tsUnixNano > entries[replaceIdx].tsUnixNano { + // reallocate values to the left + shiftLeft(entries) + } else { + // find the correct position to store the entry + replaceIdx = findInsertIdx(entries, tsUnixNano) - 1 + if replaceIdx == -1 { + replaceIdx = 0 + } + } + entries[replaceIdx] = entry + + return *el +} + +func (el *entryList[T]) get(timestamp time.Time) T { + tsUnixNano := timestamp.UnixNano() + entries := el.entries + for i := len(entries) - 1; i >= 0; i-- { + if entries[i].tsUnixNano <= tsUnixNano { + return entries[i].value + } + } + + return getZero[T]() +} + +func (el *entryList[T]) getCurrent() T { + entries := el.entries + length := len(entries) + if length == 0 { + return getZero[T]() + } + + return entries[length-1].value +} + +func (el *entryList[T]) getAll() []T { + entries := el.entries + values := make([]T, 0, len(entries)) + for i := len(entries) - 1; i >= 0; i-- { + values = append(values, entries[i].value) + } + + return values +} + +func (el *entryList[T]) noEntries() bool { + return len(el.entries) == 0 +} + +// populateEntries fills the entries list with zeroed entries up to the maximum number of entries. +// This is useful to measure the memory usage. +func (el *entryList[T]) populateEntries() { + maxEntries := int(el.maxEntries) + newEntries := make([]entry[T], 0, maxEntries) + + for i := 0; i < maxEntries; i++ { + newEntries = append(newEntries, entry[T]{ + // futuristic timestamp to make sure it will be replaced + tsUnixNano: time.Now().AddDate(0, 0, 1).UnixNano(), + // be aware that for variable-length types like strings, + // zero value will not reflect the actual memory usage of the type. + value: getZero[T](), + }) + } + + el.entries = newEntries +} + +// utility + +// insertAt inserts a new entry at the specified index in the entries list. +func insertAt[T comparable](idx int, entries []entry[T], newEntry entry[T]) []entry[T] { + return append(entries[:idx], append([]entry[T]{newEntry}, entries[idx:]...)...) +} + +// findInsertIdx finds the correct index to insert a new entry based on its timestamp. +func findInsertIdx[T comparable](entries []entry[T], tsUnixNano int64) int { + for i := len(entries) - 1; i >= 0; i-- { + if entries[i].tsUnixNano < tsUnixNano { + return i + 1 + } + } + + return len(entries) +} + +// shiftLeft shifts entries within the given indexes to the left, discarding the oldest entry. +func shiftLeft[T comparable](entries []entry[T]) { + for i := 0; i < len(entries)-1; i++ { + entries[i] = entries[i+1] + } +} + +// getZero returns the zero value for the type `T`. +func getZero[T comparable]() T { + var zero T + return zero +} diff --git a/pkg/proctree/datasource.go b/pkg/proctree/datasource.go index b14a6d44165e..d708803dd699 100644 --- a/pkg/proctree/datasource.go +++ b/pkg/proctree/datasource.go @@ -93,8 +93,6 @@ func (ptds *DataSource) exportProcessInfo( // Pick the objects related to the process from the process tree. info := process.GetInfo() executable := process.GetExecutable() - interpreter := process.GetInterpreter() - interp := process.GetInterp() // Walk children hashes and discover the ones alive at the query time. aliveChildren := make(map[int]uint32) @@ -135,8 +133,6 @@ func (ptds *DataSource) exportProcessInfo( ContainerId: "", // TODO: Add Cmd: []string{}, // TODO: Add ExecutionBinary: exportFileInfo(executable, queryTime), - Interpreter: exportFileInfo(interpreter, queryTime), - Interp: exportFileInfo(interp, queryTime), StartTime: info.GetStartTime(), ExecTime: time.Unix(0, 0), // TODO: Add ExitTime: info.GetExitTime(), diff --git a/pkg/proctree/fileinfo.go b/pkg/proctree/fileinfo.go index 078b8a692711..a41defbedf9a 100644 --- a/pkg/proctree/fileinfo.go +++ b/pkg/proctree/fileinfo.go @@ -1,20 +1,21 @@ package proctree import ( + "strings" "sync" "time" - ch "github.com/aquasecurity/tracee/pkg/changelog" + "github.com/aquasecurity/tracee/pkg/changelog" ) // FileInfoFeed allows external packages to set/get multiple values of a task at once. type FileInfoFeed struct { // Name string - Path string - Dev int - Ctime int - Inode int - InodeMode int + Path string // mutable (file path) + Dev int // mutable (device number) + Ctime int // mutable (creation time) + Inode int // mutable (inode number) + InodeMode int // mutable (inode mode) } // @@ -23,241 +24,196 @@ type FileInfoFeed struct { // FileInfo represents a file. type FileInfo struct { - path *ch.Changelog[string] // file path - dev *ch.Changelog[int] // device number of the file - ctime *ch.Changelog[int] // creation time of the file - inode *ch.Changelog[int] // inode number of the file - inodeMode *ch.Changelog[int] // inode mode of the file - mutex *sync.RWMutex + feed *changelog.Changelog[FileInfoFeed] + mutex *sync.RWMutex } // NewFileInfo creates a new file. -func NewFileInfo(maxLogSize int) *FileInfo { +func NewFileInfo() *FileInfo { return &FileInfo{ - path: ch.NewChangelog[string](maxLogSize), - dev: ch.NewChangelog[int](maxLogSize), - ctime: ch.NewChangelog[int](maxLogSize), - inode: ch.NewChangelog[int](maxLogSize), - inodeMode: ch.NewChangelog[int](maxLogSize), - mutex: &sync.RWMutex{}, + feed: changelog.NewChangelog[FileInfoFeed](3), + mutex: &sync.RWMutex{}, } } // NewFileInfoFeed creates a new file with values from the given feed. -func NewFileInfoFeed(maxLogSize int, feed FileInfoFeed) *FileInfo { - new := NewFileInfo(maxLogSize) - new.SetFeed(feed) +func NewFileInfoFeed(feed FileInfoFeed) *FileInfo { + new := NewFileInfo() + new.setFeed(feed) + return new } +// +// Setters +// + // Multiple values at once (using a feed structure) -// SetFeed sets the values of the file from a feed. +// SetFeed sets the values of the file from a feed at the current time. func (fi *FileInfo) SetFeed(feed FileInfoFeed) { fi.mutex.Lock() defer fi.mutex.Unlock() - fi.SetFeedAt(feed, time.Now()) + + fi.setFeed(feed) } // SetFeedAt sets the values of the file from a feed at the given time. func (fi *FileInfo) SetFeedAt(feed FileInfoFeed, targetTime time.Time) { fi.mutex.Lock() defer fi.mutex.Unlock() + fi.setFeedAt(feed, targetTime) } +// private setters + +func (fi *FileInfo) setFeed(feed FileInfoFeed) { + fi.setFeedAt(feed, time.Now()) +} + // Paths theoretically has no limit, but we do need to set a limit for the sake of // managing memory more responsibly. const MaxPathLen = 1024 func (fi *FileInfo) setFeedAt(feed FileInfoFeed, targetTime time.Time) { + atFeed := fi.getFeedAt(targetTime) + if feed.Path != "" { filePath := feed.Path if len(filePath) > MaxPathLen { - // Take only the end of the path, as the specific file name and location are the most - // important parts. - filePath = filePath[len(filePath)-MaxPathLen:] + // Take only the end of the path, as the specific file name and location + // are the most important parts. Cloning prevents memory retention. + filePath = strings.Clone(filePath[len(filePath)-MaxPathLen:]) } - fi.path.Set(filePath, targetTime) + atFeed.Path = filePath } if feed.Dev >= 0 { - fi.dev.Set(feed.Dev, targetTime) + atFeed.Dev = feed.Dev } if feed.Ctime >= 0 { - fi.ctime.Set(feed.Ctime, targetTime) + atFeed.Ctime = feed.Ctime } if feed.Inode >= 0 { - fi.inode.Set(feed.Inode, targetTime) + atFeed.Inode = feed.Inode } if feed.InodeMode >= 0 { - fi.inodeMode.Set(feed.InodeMode, targetTime) + atFeed.InodeMode = feed.InodeMode } + + fi.feed.Set(atFeed, targetTime) } +// +// Getters +// + +// Multiple values at once (getting a feed structure) + // GetFeed returns the values of the file as a feed. func (fi *FileInfo) GetFeed() FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeedAt(time.Now()) + + return fi.getFeed() } // GetFeedAt returns the values of the file as a feed at the given time. func (fi *FileInfo) GetFeedAt(targetTime time.Time) FileInfoFeed { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.getFeedAt(targetTime) // return values at the given time -} - -func (fi *FileInfo) getFeedAt(targetTime time.Time) FileInfoFeed { - return FileInfoFeed{ - Path: fi.path.Get(targetTime), - Dev: fi.dev.Get(targetTime), - Ctime: fi.ctime.Get(targetTime), - Inode: fi.inode.Get(targetTime), - InodeMode: fi.inodeMode.Get(targetTime), - } -} - -// Setters - -// SetPath sets the path of the file. -func (fi *FileInfo) SetPath(path string) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.path.Set(path, time.Now()) -} - -// SetPathAt sets the path of the file at the given time. -func (fi *FileInfo) SetPathAt(path string, targetTime time.Time) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.path.Set(path, targetTime) -} - -// SetDev sets the device number of the file. -func (fi *FileInfo) SetDev(dev int) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.dev.Set(dev, time.Now()) -} - -// SetDevAt sets the device number of the file at the given time. -func (fi *FileInfo) SetDevAt(dev int, targetTime time.Time) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.dev.Set(dev, targetTime) -} - -// SetCtime sets the creation time of the file. -func (fi *FileInfo) SetCtime(ctime int) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.ctime.Set(ctime, time.Now()) -} - -// SetCtimeAt sets the creation time of the file at the given time. -func (fi *FileInfo) SetCtimeAt(ctime int, targetTime time.Time) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.ctime.Set(ctime, targetTime) -} - -// SetInode sets the inode number of the file. -func (fi *FileInfo) SetInode(inode int) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.inode.Set(inode, time.Now()) -} - -// SetInodeAt sets the inode number of the file at the given time. -func (fi *FileInfo) SetInodeAt(inode int, targetTime time.Time) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.inode.Set(inode, targetTime) -} - -// SetInodeMode sets the inode mode of the file. -func (fi *FileInfo) SetInodeMode(inodeMode int) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.inodeMode.Set(inodeMode, time.Now()) -} -// SetInodeModeAt sets the inode mode of the file at the given time. -func (fi *FileInfo) SetInodeModeAt(inodeMode int, targetTime time.Time) { - fi.mutex.Lock() - defer fi.mutex.Unlock() - fi.inodeMode.Set(inodeMode, targetTime) + return fi.getFeedAt(targetTime) } -// Getters +// Single values // GetPath returns the path of the file. func (fi *FileInfo) GetPath() string { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.path.Get(time.Now()) + + return fi.getFeed().Path } // GetPathAt returns the path of the file at the given time. func (fi *FileInfo) GetPathAt(targetTime time.Time) string { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.path.Get(targetTime) + + return fi.getFeedAt(targetTime).Path } // GetDev returns the device number of the file. func (fi *FileInfo) GetDev() int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.dev.Get(time.Now()) + + return fi.getFeed().Dev } // GetDevAt returns the device number of the file at the given time. func (fi *FileInfo) GetDevAt(targetTime time.Time) int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.dev.Get(targetTime) + + return fi.getFeedAt(targetTime).Dev } // GetCtime returns the creation time of the file. func (fi *FileInfo) GetCtime() int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.ctime.Get(time.Now()) + + return fi.getFeed().Ctime } // GetCtimeAt returns the creation time of the file at the given time. func (fi *FileInfo) GetCtimeAt(targetTime time.Time) int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.ctime.Get(targetTime) + + return fi.getFeedAt(targetTime).Ctime } // GetInode returns the inode number of the file. func (fi *FileInfo) GetInode() int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.inode.Get(time.Now()) + + return fi.getFeed().Inode } // GetInodeAt returns the inode number of the file at the given time. func (fi *FileInfo) GetInodeAt(targetTime time.Time) int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.inode.Get(targetTime) + + return fi.getFeedAt(targetTime).Inode } // GetInodeMode returns the inode mode of the file. func (fi *FileInfo) GetInodeMode() int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.inodeMode.Get(time.Now()) + + return fi.getFeed().InodeMode } // GetInodeModeAt returns the inode mode of the file at the given time. func (fi *FileInfo) GetInodeModeAt(targetTime time.Time) int { fi.mutex.RLock() defer fi.mutex.RUnlock() - return fi.inodeMode.Get(targetTime) + + return fi.getFeedAt(targetTime).InodeMode +} + +// private getters + +func (fi *FileInfo) getFeed() FileInfoFeed { + return fi.feed.GetCurrent() +} + +func (fi *FileInfo) getFeedAt(targetTime time.Time) FileInfoFeed { + return fi.feed.Get(targetTime) } diff --git a/pkg/proctree/process.go b/pkg/proctree/process.go index 172176810090..6b8b1a631e44 100644 --- a/pkg/proctree/process.go +++ b/pkg/proctree/process.go @@ -14,30 +14,19 @@ type Process struct { parentHash uint32 // hash of parent info *TaskInfo // task info executable *FileInfo // executable info - interpreter *FileInfo // interpreter info (binary format interpreter/loader) - interp *FileInfo // interpreter (scripting language interpreter) children map[uint32]struct{} // hash of childrens threads map[uint32]struct{} // hash of threads // Control fields mutex *sync.RWMutex // mutex to protect the process } -const ( - executableChangelogSize = 5 // Binary's history is much more important to save - // TODO: Decide whether remove the interpreter and interp from the tree or add them back - interpreterChangelogSize = 0 - interpChangelogSize = 0 -) - // NewProcess creates a new process. func NewProcess(hash uint32) *Process { return &Process{ processHash: hash, parentHash: 0, info: NewTaskInfo(), - executable: NewFileInfo(executableChangelogSize), - interpreter: NewFileInfo(interpreterChangelogSize), - interp: NewFileInfo(interpChangelogSize), + executable: NewFileInfo(), children: make(map[uint32]struct{}), threads: make(map[uint32]struct{}), mutex: &sync.RWMutex{}, @@ -50,9 +39,7 @@ func NewProcessWithInfo(hash uint32, info *TaskInfo) *Process { processHash: hash, parentHash: 0, info: info, - executable: NewFileInfo(executableChangelogSize), - interpreter: NewFileInfo(interpreterChangelogSize), - interp: NewFileInfo(interpChangelogSize), + executable: NewFileInfo(), children: make(map[uint32]struct{}), threads: make(map[uint32]struct{}), mutex: &sync.RWMutex{}, @@ -89,20 +76,6 @@ func (p *Process) GetExecutable() *FileInfo { return p.executable } -// GetInterpreter returns a instanced interpreter info. -func (p *Process) GetInterpreter() *FileInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.interpreter -} - -// GetInterp returns a instanced interpreter. -func (p *Process) GetInterp() *FileInfo { - p.mutex.RLock() - defer p.mutex.RUnlock() - return p.interp -} - // Setters // SetParentHash sets the hash of the parent. diff --git a/pkg/proctree/proctree.go b/pkg/proctree/proctree.go index aac3a2ba4a48..7017ffaf427d 100644 --- a/pkg/proctree/proctree.go +++ b/pkg/proctree/proctree.go @@ -35,7 +35,7 @@ import ( // const ( - DefaultProcessCacheSize = 32768 + DefaultProcessCacheSize = 16384 DefaultThreadCacheSize = 32768 DefaultProcessCacheTTL = time.Second * 120 DefaultThreadCacheTTL = time.Second * 120 diff --git a/pkg/proctree/proctree_feed.go b/pkg/proctree/proctree_feed.go index 996461f2bd7c..9714abfb4ad0 100644 --- a/pkg/proctree/proctree_feed.go +++ b/pkg/proctree/proctree_feed.go @@ -131,10 +131,6 @@ func (pt *ProcessTree) FeedFromFork(feed ForkFeed) error { parent.GetExecutable().GetFeed(), feedTimeStamp, ) - leader.GetInterpreter().SetFeedAt( - parent.GetInterpreter().GetFeed(), - feedTimeStamp, - ) } // In all cases (task is a process, or a thread) there is a thread entry. diff --git a/pkg/proctree/taskinfo.go b/pkg/proctree/taskinfo.go index 15c5864238bf..954ff1f3fa9c 100644 --- a/pkg/proctree/taskinfo.go +++ b/pkg/proctree/taskinfo.go @@ -4,23 +4,23 @@ import ( "sync" "time" - ch "github.com/aquasecurity/tracee/pkg/changelog" + "github.com/aquasecurity/tracee/pkg/changelog" traceetime "github.com/aquasecurity/tracee/pkg/time" ) // TaskInfoFeed allows external packages to set/get multiple values of a task at once. type TaskInfoFeed struct { - Name string - Tid int - Pid int - PPid int - NsTid int - NsPid int - NsPPid int - Uid int - Gid int - StartTimeNS uint64 - ExitTimeNS uint64 + Name string // mutable (process name can be changed) + Tid int // immutable + Pid int // immutable + PPid int // mutable (process can be reparented) + NsTid int // immutable + NsPid int // immutable + NsPPid int // mutable (process can be reparented) + Uid int // mutable (process uid can be changed) + Gid int // mutable (process gid can be changed) + StartTimeNS uint64 // immutable (this is a duration, in ns, since boot) + ExitTimeNS uint64 // immutable (this is a duration, in ns, since boot) } // @@ -29,342 +29,262 @@ type TaskInfoFeed struct { // TaskInfo represents a task. type TaskInfo struct { - name *ch.Changelog[string] // variable (process name can be changed) - tid int // immutable - pid int // immutable - pPid *ch.Changelog[int] // variable (process can be reparented) - nsTid int // immutable - nsPid int // immutable - nsPPid *ch.Changelog[int] // variable (process can be reparented) - uid *ch.Changelog[int] // variable (process uid can be changed) - gid *ch.Changelog[int] // variable (process gid can be changed) - startTimeNS uint64 // this is a duration, in ns, since boot (immutable) - exitTimeNS uint64 // this is a duration, in ns, since boot (immutable) - mutex *sync.RWMutex + feed *changelog.Changelog[TaskInfoFeed] + mutex *sync.RWMutex } // NewTaskInfo creates a new task. func NewTaskInfo() *TaskInfo { return &TaskInfo{ - name: ch.NewChangelog[string](5), - // All the folloowing values changes are currently not monitored by the process tree. - // Hence, for now, they will only contain one value in the changelog - pPid: ch.NewChangelog[int](1), - nsPPid: ch.NewChangelog[int](1), - uid: ch.NewChangelog[int](1), - gid: ch.NewChangelog[int](1), - mutex: &sync.RWMutex{}, + feed: changelog.NewChangelog[TaskInfoFeed](3), + mutex: &sync.RWMutex{}, } } // NewTaskInfoFromFeed creates a new task with values from the given feed. -func NewTaskInfoFromFeed(feed TaskInfoFeed) *TaskInfo { +func NewTaskInfoNewFromFeed(feed TaskInfoFeed) *TaskInfo { new := NewTaskInfo() - new.SetFeed(feed) + new.setFeed(feed) + return new } -// Feed: Multiple values at once. +// +// Setters +// -// SetFeed sets the values of the task from the given feed. +// Multiple values at once (using a feed structure) + +// SetFeed sets the values of the task from the given feed at the current time. func (ti *TaskInfo) SetFeed(feed TaskInfoFeed) { ti.mutex.Lock() defer ti.mutex.Unlock() - ti.setFeedAt(feed, time.Now()) // set current values + + ti.setFeed(feed) } // SetFeedAt sets the values of the task from the given feed at the given time. func (ti *TaskInfo) SetFeedAt(feed TaskInfoFeed, targetTime time.Time) { ti.mutex.Lock() defer ti.mutex.Unlock() - ti.setFeedAt(feed, targetTime) // set values at the given time -} -func (ti *TaskInfo) setFeedAt(feed TaskInfoFeed, targetTime time.Time) { + atFeed := ti.getFeedAt(targetTime) + if feed.Name != "" { - ti.name.Set(feed.Name, targetTime) + atFeed.Name = feed.Name } if feed.Tid >= 0 { - ti.tid = feed.Tid + atFeed.Tid = feed.Tid } if feed.Pid >= 0 { - ti.pid = feed.Pid + atFeed.Pid = feed.Pid } if feed.PPid >= 0 { - ti.pPid.Set(feed.PPid, targetTime) + atFeed.PPid = feed.PPid } if feed.NsTid >= 0 { - ti.nsTid = feed.NsTid + atFeed.NsTid = feed.NsTid } if feed.NsPid >= 0 { - ti.nsPid = feed.NsPid + atFeed.NsPid = feed.NsPid } if feed.NsPid >= 0 { - ti.nsPPid.Set(feed.NsPid, targetTime) + atFeed.NsPPid = feed.NsPPid } if feed.Uid >= 0 { - ti.uid.Set(feed.Uid, targetTime) + atFeed.Uid = feed.Uid } if feed.Gid >= 0 { - ti.gid.Set(feed.Gid, targetTime) + atFeed.Gid = feed.Gid } if feed.StartTimeNS != 0 { - ti.startTimeNS = feed.StartTimeNS + atFeed.StartTimeNS = feed.StartTimeNS } if feed.ExitTimeNS != 0 { - ti.exitTimeNS = feed.ExitTimeNS + atFeed.ExitTimeNS = feed.ExitTimeNS } -} - -// GetFeed returns the values of the task as a feed. -func (ti *TaskInfo) GetFeed() TaskInfoFeed { - ti.mutex.RLock() - defer ti.mutex.RUnlock() - return ti.getFeedAt(time.Now()) // return current values -} - -// GetFeedAt returns the values of the task as a feed at the given time. -func (ti *TaskInfo) GetFeedAt(targetTime time.Time) TaskInfoFeed { - ti.mutex.RLock() - defer ti.mutex.RUnlock() - return ti.getFeedAt(targetTime) // return values at the given time -} -func (ti *TaskInfo) getFeedAt(targetTime time.Time) TaskInfoFeed { - return TaskInfoFeed{ - Name: ti.name.Get(targetTime), - Tid: ti.tid, - Pid: ti.pid, - PPid: ti.pPid.Get(targetTime), - NsTid: ti.nsTid, - NsPid: ti.nsPid, - NsPPid: ti.nsPPid.Get(targetTime), - Uid: ti.uid.Get(targetTime), - Gid: ti.gid.Get(targetTime), - StartTimeNS: ti.startTimeNS, - ExitTimeNS: ti.exitTimeNS, - } + ti.setFeedAt(atFeed, targetTime) } -// Setters - -// SetName sets the name of the task. -func (ti *TaskInfo) SetName(name string) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.name.Set(name, time.Now()) -} +// Single values // SetNameAt sets the name of the task at the given time. func (ti *TaskInfo) SetNameAt(name string, targetTime time.Time) { ti.mutex.Lock() defer ti.mutex.Unlock() - ti.name.Set(name, targetTime) -} -// SetTid sets the tid of the task. -func (ti *TaskInfo) SetTid(tid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.tid = tid -} + feed := ti.getFeedAt(targetTime) + feed.Name = name -// SetPid sets the pid of the task. -func (ti *TaskInfo) SetPid(pid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.pid = pid + ti.setFeedAt(feed, targetTime) } -// SetNsTid sets the nsTid of the task. -func (ti *TaskInfo) SetNsTid(nsTid int) { +// SetExitTime sets the exitTime of the task. +func (ti *TaskInfo) SetExitTime(exitTime uint64) { ti.mutex.Lock() defer ti.mutex.Unlock() - ti.nsTid = nsTid -} -// SetNsPid sets the nsPid of the task. -func (ti *TaskInfo) SetNsPid(nsPid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.nsPid = nsPid -} + exitTimestamp := traceetime.NsSinceEpochToTime(exitTime) -// SetStartTimeNS sets the startTimeNS of the task. -func (ti *TaskInfo) SetStartTimeNS(startTimeNS uint64) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.startTimeNS = startTimeNS -} + feed := ti.getFeedAt(exitTimestamp) + feed.ExitTimeNS = exitTime -// SetExitTime sets the exitTime of the task. -func (ti *TaskInfo) SetExitTime(exitTime uint64) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.exitTimeNS = exitTime + ti.setFeedAt(feed, exitTimestamp) } -// SetPPid sets the ppid of the task. -func (ti *TaskInfo) SetPPid(pPid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.pPid.Set(pPid, time.Now()) -} +// private setters -// SetPPidAt sets the ppid of the task at the given time. -func (ti *TaskInfo) SetPPidAt(pPid int, targetTime time.Time) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.pPid.Set(pPid, targetTime) +func (ti *TaskInfo) setFeed(feed TaskInfoFeed) { + ti.setFeedAt(feed, time.Now()) } -// SetNsPPid sets the nsppid of the task. -func (ti *TaskInfo) SetNsPPid(nsPPid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.nsPPid.Set(nsPPid, time.Now()) +func (ti *TaskInfo) setFeedAt(feed TaskInfoFeed, targetTime time.Time) { + ti.feed.Set(feed, targetTime) } -// SetNsPPidAt sets the nsppid of the task at the given time. -func (ti *TaskInfo) SetNsPPidAt(nsPPid int, targetTime time.Time) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.nsPPid.Set(nsPPid, targetTime) -} +// +// Getters +// -// SetUid sets the uid of the task. -func (ti *TaskInfo) SetUid(uid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.uid.Set(uid, time.Now()) -} +// Multiple values at once (getting a feed structure) -// SetUidAt sets the uid of the task at the given time. -func (ti *TaskInfo) SetUidAt(uid int, targetTime time.Time) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.uid.Set(uid, targetTime) -} +// GetFeed returns the values of the task as a feed. +func (ti *TaskInfo) GetFeed() TaskInfoFeed { + ti.mutex.RLock() + defer ti.mutex.RUnlock() -// SetGid sets the gid of the task. -func (ti *TaskInfo) SetGid(gid int) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.gid.Set(gid, time.Now()) + return ti.getFeed() } -// SetGidAt sets the gid of the task at the given time. -func (ti *TaskInfo) SetGidAt(gid int, targetTime time.Time) { - ti.mutex.Lock() - defer ti.mutex.Unlock() - ti.gid.Set(gid, targetTime) +// GetFeedAt returns the values of the task as a feed at the given time. +func (ti *TaskInfo) GetFeedAt(targetTime time.Time) TaskInfoFeed { + ti.mutex.RLock() + defer ti.mutex.RUnlock() + + return ti.getFeedAt(targetTime) } -// Getters +// Single values // GetName returns the name of the task. func (ti *TaskInfo) GetName() string { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.name.GetCurrent() + + return ti.getFeed().Name } // GetNameAt returns the name of the task at the given time. func (ti *TaskInfo) GetNameAt(targetTime time.Time) string { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.name.Get(targetTime) + + return ti.getFeedAt(targetTime).Name } // GetTid returns the tid of the task. func (ti *TaskInfo) GetTid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.tid + + return ti.getFeed().Tid } // GetPid returns the pid of the task. func (ti *TaskInfo) GetPid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.pid + + return ti.getFeed().Pid } // GetNsTid returns the nsTid of the task. func (ti *TaskInfo) GetNsTid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.nsTid + + return ti.getFeed().NsTid } // GetNsPid returns the nsPid of the task. func (ti *TaskInfo) GetNsPid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.nsPid + + return ti.getFeed().NsPid } // GetPPid returns the ppid of the task. func (ti *TaskInfo) GetPPid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.pPid.GetCurrent() + + return ti.getFeed().PPid } // GetPPidAt returns the ppid of the task at the given time. func (ti *TaskInfo) GetPPidAt(targetTime time.Time) int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.pPid.Get(targetTime) + + return ti.getFeedAt(targetTime).PPid } // GetNsPPid returns the nsPPid of the task. func (ti *TaskInfo) GetNsPPid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.nsPPid.GetCurrent() + + return ti.getFeed().NsPPid } // GetNsPPidAt returns the nsPPid of the task at the given time. func (ti *TaskInfo) GetNsPPidAt(targetTime time.Time) int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.nsPPid.Get(targetTime) + + return ti.getFeedAt(targetTime).NsPPid } // GetUid returns the uid of the task. func (ti *TaskInfo) GetUid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.uid.GetCurrent() + + return ti.getFeed().Uid } // GetUidAt returns the uid of the task at the given time. func (ti *TaskInfo) GetUidAt(targetTime time.Time) int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.uid.Get(targetTime) + + return ti.getFeedAt(targetTime).Uid } // GetGid returns the gid of the task. func (ti *TaskInfo) GetGid() int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.gid.GetCurrent() + + return ti.getFeed().Gid } // GetGidAt returns the gid of the task at the given time. func (ti *TaskInfo) GetGidAt(targetTime time.Time) int { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.gid.Get(targetTime) + + return ti.getFeedAt(targetTime).Gid } // GetStartTimeNS returns the start time of the task in nanoseconds since epoch func (ti *TaskInfo) GetStartTimeNS() uint64 { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.startTimeNS + + return ti.getFeed().StartTimeNS } // GetStartTime returns the start time of the task. @@ -372,28 +292,31 @@ func (ti *TaskInfo) GetStartTime() time.Time { ti.mutex.RLock() defer ti.mutex.RUnlock() - return traceetime.NsSinceEpochToTime(ti.startTimeNS) + return traceetime.NsSinceEpochToTime(ti.getFeed().StartTimeNS) } // GetExitTimeNS returns the exitTime of the task in nanoseconds since epoch func (ti *TaskInfo) GetExitTimeNS() uint64 { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.exitTimeNS + + return ti.getFeed().ExitTimeNS } // GetExitTime returns the exit time of the task. func (ti *TaskInfo) GetExitTime() time.Time { ti.mutex.RLock() defer ti.mutex.RUnlock() - return traceetime.NsSinceEpochToTime(ti.exitTimeNS) + + return traceetime.NsSinceEpochToTime(ti.getFeed().ExitTimeNS) } // IsAlive returns true if the task has exited. func (ti *TaskInfo) IsAlive() bool { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.exitTimeNS == 0 + + return ti.getFeed().ExitTimeNS == 0 } // IsAliveAt return whether the task is alive in the given time, either because it didn't start @@ -401,15 +324,26 @@ func (ti *TaskInfo) IsAlive() bool { func (ti *TaskInfo) IsAliveAt(targetTime time.Time) bool { ti.mutex.RLock() defer ti.mutex.RUnlock() - if ti.exitTimeNS != 0 { - if targetTime.After(traceetime.NsSinceEpochToTime(ti.exitTimeNS)) { + + feed := ti.getFeedAt(targetTime) + exitTimeNS := feed.ExitTimeNS + if exitTimeNS != 0 { + if targetTime.After(traceetime.NsSinceEpochToTime(exitTimeNS)) { return false } } + // If start time is not initialized it will count as 0 ns, meaning it will be before any // query time given. - if targetTime.Before(traceetime.NsSinceEpochToTime(ti.startTimeNS)) { - return false - } - return true + return !targetTime.Before(traceetime.NsSinceEpochToTime(feed.StartTimeNS)) +} + +// private getters + +func (ti *TaskInfo) getFeed() TaskInfoFeed { + return ti.feed.GetCurrent() +} + +func (ti *TaskInfo) getFeedAt(targetTime time.Time) TaskInfoFeed { + return ti.feed.Get(targetTime) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 280633919789..3a88eb74232b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,7 +1,10 @@ package utils import ( + "fmt" + "io" "math/rand" + "reflect" "strings" "time" @@ -83,3 +86,47 @@ func ReverseString(s string) string { } return string(bytes) } + +// PrintStructSizes prints the size of a struct and the size of its fields +func PrintStructSizes(w io.Writer, structure interface{}) { + typ := reflect.TypeOf(structure) + + // if the type is a pointer to a struct, dereference it + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + + if typ.Kind() != reflect.Struct { + fmt.Fprintf(w, "Type %s is not a struct\n", typ.Kind()) + return + } + + totalSize := typ.Size() + expectedSize := uintptr(0) + fieldsInfo := "[" + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldSize := field.Type.Size() + fieldOffset := field.Offset + fieldsInfo += fmt.Sprintf( + "%s:%s %d bytes (offset=%d), ", + field.Name, field.Type.String(), fieldSize, fieldOffset, + ) + expectedSize += fieldSize + } + + padding := totalSize - expectedSize + paddingInfo := "" + if padding > 0 { + paddingInfo = "(has padding)" + } + + // remove trailing comma and space + if len(fieldsInfo) > 2 { + fieldsInfo = fieldsInfo[:len(fieldsInfo)-2] + } + fieldsInfo += "]" + + fmt.Fprintf(w, "%s: %d bytes %s %s\n", typ.Name(), totalSize, fieldsInfo, paddingInfo) +}