-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcontroller_test.go
More file actions
136 lines (113 loc) · 4.24 KB
/
controller_test.go
File metadata and controls
136 lines (113 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* Copyright (c) New Cloud Technologies, Ltd. 2013-2022.
* Author: Vitaly Isaev <vitaly.isaev@myoffice.team>
* License: https://github.com/newcloudtechnologies/memlimiter/blob/master/LICENSE
*/
package nextgc
import (
"sync/atomic"
"testing"
"time"
"code.cloudfoundry.org/bytefmt"
"github.com/go-logr/logr/testr"
"github.com/newcloudtechnologies/memlimiter/stats"
"github.com/stretchr/testify/require"
"github.com/newcloudtechnologies/memlimiter/backpressure"
"github.com/newcloudtechnologies/memlimiter/utils/config/bytes"
"github.com/newcloudtechnologies/memlimiter/utils/config/duration"
"github.com/stretchr/testify/mock"
)
func TestController(t *testing.T) {
logger := testr.New(t)
const servusPeriod = 100 * time.Millisecond
controllerPeriod := 2 * servusPeriod
cfg := &ControllerConfig{
// We cannot exceed 1000M RSS threshold
RSSLimit: bytes.Bytes{Value: 1000 * bytefmt.MEGABYTE},
// When memory budget utilization reaches 50%, the controller will start GOGC altering.
DangerZoneGOGC: 50,
// When memory budget utilization reaches 90%, the controller will start request throttling.
DangerZoneThrottling: 90,
Period: duration.Duration{Duration: controllerPeriod},
ComponentProportional: &ComponentProportionalConfig{
Coefficient: 1,
WindowSize: 0, // just for simplicity disable the smoothing
},
}
// First ServiceStats instance describes the situation, when the memory budget utilization
// is very close to the limits.
memoryBudgetExhausted := &stats.ServiceStatsMock{}
memoryBudgetExhausted.On("NextGC").Return(uint64(950 * bytefmt.MEGABYTE))
memoryBudgetExhausted.On("RSS").Return(uint64(900 * bytefmt.MEGABYTE))
cr1 := &stats.ConsumptionReport{
Cgo: map[string]uint64{"some_important_cache": 5 * bytefmt.MEGABYTE},
}
memoryBudgetExhausted.On("ConsumptionReport").Return(cr1)
// In the second case the memory budget utilization returns to the ordinary values.
memoryBudgetNormal := &stats.ServiceStatsMock{}
memoryBudgetNormal.On("NextGC").Return(uint64(300 * bytefmt.MEGABYTE))
memoryBudgetNormal.On("RSS").Return(uint64(500 * bytefmt.MEGABYTE))
cr2 := &stats.ConsumptionReport{
Cgo: map[string]uint64{"some_important_cache": 1 * bytefmt.MEGABYTE},
}
memoryBudgetNormal.On("ConsumptionReport").Return(cr2)
subscriptionMock := &stats.SubscriptionMock{
Chan: make(chan stats.ServiceStats),
}
// this channel is closed when backpressure.Operator receives all required actions
terminateChan := make(chan struct{})
var serviceStatsContainer atomic.Value
// The stream of tracker.ServiceStats instances
go func() {
ticker := time.NewTicker(servusPeriod)
for {
select {
case <-ticker.C:
serviceStats, ok := serviceStatsContainer.Load().(stats.ServiceStats)
if ok {
subscriptionMock.Chan <- serviceStats
}
case <-terminateChan:
return
}
}
}()
backpressureOperatorMock := &backpressure.OperatorMock{}
// first initialization within NewController constructor
backpressureOperatorMock.On(
"SetControlParameters",
&stats.ControlParameters{
GOGC: backpressure.DefaultGOGC,
ThrottlingPercentage: backpressure.NoThrottling,
},
).Return(nil).Once()
// Here we model the situation of memory exhaustion.
serviceStatsContainer.Store(memoryBudgetExhausted)
backpressureOperatorMock.On(
"SetControlParameters",
mock.MatchedBy(func(val *stats.ControlParameters) bool {
return val.GOGC == 78 && val.ThrottlingPercentage == 22
}),
).Return(nil).Once().Run(
func(args mock.Arguments) {
// As soon as the control signal is delivered to the backpressure.Operator,
// replace the ServiceStats instance to make controller think that memory
// consumption returned to normal.
serviceStatsContainer.Store(memoryBudgetNormal)
},
).On(
"SetControlParameters",
mock.MatchedBy(func(val *stats.ControlParameters) bool {
return val.GOGC == backpressure.DefaultGOGC && val.ThrottlingPercentage == backpressure.NoThrottling
}),
).Return(nil).Once().Run(
func(args mock.Arguments) {
close(terminateChan)
},
)
c, err := NewControllerFromConfig(logger, cfg, subscriptionMock, backpressureOperatorMock)
require.NoError(t, err)
<-terminateChan
c.Quit()
mock.AssertExpectationsForObjects(t, subscriptionMock, backpressureOperatorMock)
}