* Copyright (c) 2024 China Unicom Digital Technology Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: YuXiang Guo
* Date: 2025-09-08
*/
package aggregate
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockContainerStateAggregator struct {
mock.Mock
}
func (m *MockContainerStateAggregator) AddSample(sample *ContainerUsageSample) {
m.Called(sample)
}
func (m *MockContainerStateAggregator) SubtractSample(sample *ContainerUsageSample) {
m.Called(sample)
}
func TestSampleIsValid(t *testing.T) {
tests := []struct {
name string
sample ContainerUsageSample
expectedResource ResourceName
expectedValid bool
}{
{
name: "valid CPU sample",
sample: ContainerUsageSample{
Usage: 1000, Resource: ResourceCPU,
},
expectedResource: ResourceCPU, expectedValid: true,
},
{
name: "valid Memory sample",
sample: ContainerUsageSample{
Usage: 1024 * 1024, Resource: ResourceMemory,
},
expectedResource: ResourceMemory, expectedValid: true,
},
{
name: "negative usage",
sample: ContainerUsageSample{
Usage: -100, Resource: ResourceCPU,
},
expectedResource: ResourceCPU, expectedValid: false,
},
{
name: "wrong resource type",
sample: ContainerUsageSample{
Usage: 1000, Resource: ResourceCPU,
},
expectedResource: ResourceMemory, expectedValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.sample.isValid(tt.expectedResource)
assert.Equal(t, tt.expectedValid, result)
})
}
}
func TestNewContainerState(t *testing.T) {
request := Resources{ResourceCPU: 1000, ResourceMemory: 1024 * 1024}
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(request, mockAggregator)
assert.NotNil(t, containerState)
assert.Equal(t, request, containerState.Request)
assert.Equal(t, mockAggregator, containerState.aggregator)
assert.True(t, containerState.LastCPUSampleStart.IsZero())
assert.True(t, containerState.WindowEnd.IsZero())
assert.Equal(t, ResourceAmount(0), containerState.memoryPeak)
assert.Equal(t, ResourceAmount(0), containerState.oomPeak)
}
func TestAddSampleCPU(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
request := Resources{ResourceCPU: 2000}
containerState := NewContainerState(request, mockAggregator)
fixedTime := time.Date(2023, 1, 1, 10, 0, 0, 0, time.UTC)
sample1 := &ContainerUsageSample{
MeasureStart: fixedTime, Usage: 1000, Request: 2000, Resource: ResourceCPU,
}
mockAggregator.On("AddSample", sample1).Once()
result := containerState.AddSample(sample1)
assert.True(t, result)
assert.Equal(t, fixedTime, containerState.LastCPUSampleStart)
mockAggregator.AssertExpectations(t)
earlierTime := fixedTime.Add(-time.Minute)
sample2 := &ContainerUsageSample{
MeasureStart: earlierTime, Usage: 1500, Request: 2000, Resource: ResourceCPU,
}
result = containerState.AddSample(sample2)
assert.False(t, result)
assert.Equal(t, fixedTime, containerState.LastCPUSampleStart)
}
func TestAddSampleMemory(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
request := Resources{ResourceMemory: 1024 * 1024}
containerState := NewContainerState(request, mockAggregator)
originalConfig := aggregationsConfig
aggregationsConfig = &AggregationsConfig{MemoryAggregationInterval: time.Minute}
defer func() { aggregationsConfig = originalConfig }()
now := time.Now()
sample1 := &ContainerUsageSample{
MeasureStart: now, Usage: 512 * 1024, Request: 1024 * 1024, Resource: ResourceMemory,
}
var capturedPeak *ContainerUsageSample
mockAggregator.On("AddSample", mock.Anything).Run(func(args mock.Arguments) {
if sample, ok := args.Get(0).(*ContainerUsageSample); ok {
capturedPeak = sample
} else {
t.Fatalf("capturedPeak is not a ContainerUsageSample")
}
}).Once()
result := containerState.AddSample(sample1)
assert.True(t, result)
assert.Equal(t, now, containerState.lastMemorySampleStart)
expectedWindowEnd := now.Add(time.Minute)
assert.Equal(t, expectedWindowEnd, containerState.WindowEnd)
assert.Equal(t, ResourceAmount(512*1024), containerState.memoryPeak)
assert.NotNil(t, capturedPeak)
assert.Equal(t, expectedWindowEnd, capturedPeak.MeasureStart)
assert.Equal(t, sample1.Usage, capturedPeak.Usage)
assert.Equal(t, sample1.Request, capturedPeak.Request)
assert.Equal(t, sample1.Resource, capturedPeak.Resource)
mockAggregator.AssertExpectations(t)
}
func TestAddSampleInvalidResource(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(Resources{}, mockAggregator)
sample := &ContainerUsageSample{
MeasureStart: time.Now(), Usage: 1000, Resource: "invalid-resource",
}
result := containerState.AddSample(sample)
assert.False(t, result)
}
func TestGetMaxMemoryPeak(t *testing.T) {
containerState := &ContainerState{}
containerState.memoryPeak = 500
containerState.oomPeak = 0
assert.Equal(t, ResourceAmount(500), containerState.GetMaxMemoryPeak())
containerState.memoryPeak = 0
containerState.oomPeak = 800
assert.Equal(t, ResourceAmount(800), containerState.GetMaxMemoryPeak())
containerState.memoryPeak = 500
containerState.oomPeak = 800
assert.Equal(t, ResourceAmount(800), containerState.GetMaxMemoryPeak())
containerState.memoryPeak = 900
containerState.oomPeak = 800
assert.Equal(t, ResourceAmount(900), containerState.GetMaxMemoryPeak())
}
func TestMemoryWindowManagement(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
request := Resources{ResourceMemory: 1024 * 1024}
containerState := NewContainerState(request, mockAggregator)
originalConfig := aggregationsConfig
aggregationsConfig = &AggregationsConfig{MemoryAggregationInterval: time.Hour}
defer func() { aggregationsConfig = originalConfig }()
var addedSamples, subtractedSamples []*ContainerUsageSample
mockAggregator.On("AddSample", mock.Anything).Run(func(args mock.Arguments) {
addedSamples = append(addedSamples, args.Get(0).(*ContainerUsageSample))
})
mockAggregator.On("SubtractSample", mock.Anything).Run(func(args mock.Arguments) {
subtractedSamples = append(subtractedSamples, args.Get(0).(*ContainerUsageSample))
})
startTime := time.Date(2023, 1, 1, 10, 0, 0, 0, time.UTC)
sample1 := &ContainerUsageSample{
MeasureStart: startTime, Usage: 500 * 1024, Request: 1024 * 1024, Resource: ResourceMemory,
}
containerState.addMemorySample(sample1, false)
expectedFirstWindowEnd := time.Date(2023, 1, 1, 11, 0, 0, 0, time.UTC)
assert.Equal(t, expectedFirstWindowEnd, containerState.WindowEnd)
assert.Equal(t, 1, len(addedSamples))
assert.Equal(t, 0, len(subtractedSamples))
sample2 := &ContainerUsageSample{
MeasureStart: startTime.Add(30 * time.Minute), Usage: 800 * 1024, Request: 1024 * 1024, Resource: ResourceMemory,
}
containerState.addMemorySample(sample2, false)
assert.Equal(t, ResourceAmount(800*1024), containerState.memoryPeak)
assert.Equal(t, 2, len(addedSamples))
assert.Equal(t, 1, len(subtractedSamples))
assert.Equal(t, expectedFirstWindowEnd, containerState.WindowEnd)
sample3 := &ContainerUsageSample{
MeasureStart: startTime.Add(90 * time.Minute),
Usage: 600 * 1024, Request: 1024 * 1024, Resource: ResourceMemory,
}
containerState.addMemorySample(sample3, false)
expectedNewWindowEnd := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
assert.Equal(t, expectedNewWindowEnd, containerState.WindowEnd)
assert.Equal(t, ResourceAmount(600*1024), containerState.memoryPeak)
assert.Equal(t, 3, len(addedSamples))
assert.Equal(t, 1, len(subtractedSamples))
}
func TestOutOfOrderSample(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(Resources{}, mockAggregator)
now := time.Now()
sample1 := &ContainerUsageSample{
MeasureStart: now, Usage: 1000, Request: 2000, Resource: ResourceMemory,
}
mockAggregator.On("AddSample", mock.Anything).Once()
containerState.addMemorySample(sample1, false)
earlierSample := &ContainerUsageSample{
MeasureStart: now.Add(-time.Minute), Usage: 1500, Request: 2000, Resource: ResourceMemory,
}
result := containerState.addMemorySample(earlierSample, false)
assert.False(t, result)
mockAggregator.AssertNumberOfCalls(t, "AddSample", 1)
}
func TestLowerPeakSample(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(Resources{}, mockAggregator)
now := time.Now()
sample1 := &ContainerUsageSample{
MeasureStart: now, Usage: 1000, Request: 2000, Resource: ResourceMemory,
}
mockAggregator.On("AddSample", mock.Anything).Once()
containerState.addMemorySample(sample1, false)
sample2 := &ContainerUsageSample{
MeasureStart: now.Add(time.Minute), Usage: 500, Request: 2000, Resource: ResourceMemory,
}
result := containerState.addMemorySample(sample2, false)
assert.True(t, result)
mockAggregator.AssertNumberOfCalls(t, "AddSample", 1)
}
func TestAddMemorySampleInvalid(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(Resources{}, mockAggregator)
invalidSample := &ContainerUsageSample{
MeasureStart: time.Now(), Usage: -100, Request: 2000, Resource: ResourceMemory,
}
result := containerState.addMemorySample(invalidSample, false)
assert.False(t, result)
wrongResourceSample := &ContainerUsageSample{
MeasureStart: time.Now(), Usage: 1000, Request: 2000, Resource: ResourceCPU,
}
result = containerState.addMemorySample(wrongResourceSample, false)
assert.False(t, result)
}
func TestAddMemorySampleOOM(t *testing.T) {
mockAggregator := &MockContainerStateAggregator{}
containerState := NewContainerState(Resources{ResourceMemory: 1024 * 1024}, mockAggregator)
originalConfig := aggregationsConfig
aggregationsConfig = &AggregationsConfig{MemoryAggregationInterval: time.Minute}
defer func() { aggregationsConfig = originalConfig }()
var addedSamples []*ContainerUsageSample
var subtractedSamples []*ContainerUsageSample
mockAggregator.On("AddSample", mock.Anything).Run(func(args mock.Arguments) {
addedSamples = append(addedSamples, args.Get(0).(*ContainerUsageSample))
})
mockAggregator.On("SubtractSample", mock.Anything).Run(func(args mock.Arguments) {
subtractedSamples = append(subtractedSamples, args.Get(0).(*ContainerUsageSample))
})
now := time.Now()
oomSample := &ContainerUsageSample{
MeasureStart: now,
Usage: 900 * 1024,
Request: 1024 * 1024,
Resource: ResourceMemory,
}
result := containerState.addMemorySample(oomSample, true)
assert.True(t, result)
assert.Equal(t, ResourceAmount(900*1024), containerState.oomPeak)
assert.Equal(t, ResourceAmount(0), containerState.memoryPeak)
assert.Equal(t, 1, len(addedSamples))
assert.Equal(t, now, containerState.lastMemorySampleStart)
assert.Equal(t, ResourceAmount(900*1024), containerState.GetMaxMemoryPeak())
largerOOM := &ContainerUsageSample{
MeasureStart: now.Add(30 * time.Second),
Usage: 1200 * 1024,
Request: 1024 * 1024,
Resource: ResourceMemory,
}
result = containerState.addMemorySample(largerOOM, true)
assert.True(t, result)
assert.Equal(t, ResourceAmount(1200*1024), containerState.oomPeak)
assert.Equal(t, 2, len(addedSamples))
assert.Equal(t, 1, len(subtractedSamples))
}
func BenchmarkAddSampleCPU(b *testing.B) {
mockAggregator := &MockContainerStateAggregator{}
mockAggregator.On("AddSample", mock.Anything).Return()
containerState := NewContainerState(Resources{ResourceCPU: 2000}, mockAggregator)
sample := &ContainerUsageSample{
MeasureStart: time.Now(), Usage: 1000, Request: 2000, Resource: ResourceCPU,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
containerState.AddSample(sample)
}
}
func BenchmarkAddSampleMemory(b *testing.B) {
mockAggregator := &MockContainerStateAggregator{}
mockAggregator.On("AddSample", mock.Anything).Return()
containerState := NewContainerState(Resources{ResourceMemory: 1024 * 1024}, mockAggregator)
sample := &ContainerUsageSample{
MeasureStart: time.Now(), Usage: 512 * 1024, Request: 1024 * 1024, Resource: ResourceMemory,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
containerState.AddSample(sample)
}
}
func BenchmarkGetMaxMemoryPeak(b *testing.B) {
containerState := &ContainerState{memoryPeak: 500, oomPeak: 800}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = containerState.GetMaxMemoryPeak()
}
}