@@ -2,8 +2,10 @@ use crossbeam_utils::atomic::AtomicCell;
2
2
use parking_lot:: Mutex ;
3
3
use std:: {
4
4
marker:: PhantomData ,
5
- ops:: SubAssign ,
6
- sync:: { atomic:: AtomicU64 , Arc } ,
5
+ sync:: {
6
+ atomic:: { AtomicU32 , AtomicU64 , Ordering } ,
7
+ Arc ,
8
+ } ,
7
9
time:: { Duration , Instant } ,
8
10
} ;
9
11
use temporal_sdk_core_api:: worker:: {
@@ -14,40 +16,57 @@ use tokio::sync::watch;
14
16
15
17
pub struct ResourceBasedSlots < MI > {
16
18
target_mem_usage : f64 ,
17
- assumed_maximum_marginal_contribution : f64 ,
18
- mem_info_supplier : MI ,
19
- max : usize ,
19
+ target_cpu_usage : f32 ,
20
+ sys_info_supplier : MI ,
20
21
}
21
22
pub struct ResourceBasedSlotsForType < MI , SK > {
22
23
inner : Arc < ResourceBasedSlots < MI > > ,
23
24
minimum : usize ,
25
+ /// Maximum amount of slots of this type permitted
26
+ max : usize ,
24
27
/// Minimum time we will wait (after passing the minimum slots number) between handing out new
25
28
/// slots
26
29
ramp_throttle : Duration ,
27
30
31
+ pids : Arc < Mutex < PidControllers > > ,
28
32
last_slot_issued_tx : watch:: Sender < Instant > ,
29
33
last_slot_issued_rx : watch:: Receiver < Instant > ,
30
34
_slot_kind : PhantomData < SK > ,
31
35
}
36
+ struct PidControllers {
37
+ mem : pid:: Pid < f64 > ,
38
+ cpu : pid:: Pid < f32 > ,
39
+ }
32
40
33
- impl ResourceBasedSlots < SysinfoMem > {
34
- pub fn new ( target_mem_usage : f64 , marginal_contribution : f64 , max : usize ) -> Self {
41
+ impl ResourceBasedSlots < RealSysInfo > {
42
+ pub fn new ( target_mem_usage : f64 , target_cpu_usage : f32 ) -> Self {
35
43
Self {
36
44
target_mem_usage,
37
- assumed_maximum_marginal_contribution : marginal_contribution,
38
- mem_info_supplier : SysinfoMem :: new ( ) ,
39
- max,
45
+ target_cpu_usage,
46
+ sys_info_supplier : RealSysInfo :: new ( ) ,
40
47
}
41
48
}
42
49
}
43
50
51
+ impl PidControllers {
52
+ fn new ( mem_target : f64 , cpu_target : f32 ) -> Self {
53
+ let mut mem = pid:: Pid :: new ( mem_target, 100.0 ) ;
54
+ mem. p ( 5.0 , 100 ) . i ( 0.0 , 100 ) . d ( 1.0 , 100 ) ;
55
+ let mut cpu = pid:: Pid :: new ( cpu_target, 100.0 ) ;
56
+ cpu. p ( 5.0 , 100. ) . i ( 0.0 , 100. ) . d ( 1.0 , 100. ) ;
57
+ Self { mem, cpu }
58
+ }
59
+ }
60
+
44
61
trait MemoryInfo {
45
62
/// Return total available system memory in bytes
46
63
fn total_mem ( & self ) -> u64 ;
47
64
/// Return memory used by this process in bytes
48
65
// TODO: probably needs to be just overall used... won't work w/ subprocesses for example
49
66
fn process_used_mem ( & self ) -> u64 ;
50
67
68
+ fn used_cpu_percent ( & self ) -> f32 ;
69
+
51
70
fn process_used_percent ( & self ) -> f64 {
52
71
self . process_used_mem ( ) as f64 / self . total_mem ( ) as f64
53
72
}
79
98
80
99
fn try_reserve_slot ( & self , ctx : & dyn SlotReservationContext ) -> Option < SlotSupplierPermit > {
81
100
if self . time_since_last_issued ( ) > self . ramp_throttle
82
- && self . inner . can_reserve ( ctx. num_issued_slots ( ) )
101
+ && ctx. num_issued_slots ( ) < self . max
102
+ && self . pid_decision ( )
103
+ && self . inner . can_reserve ( )
83
104
{
84
105
let _ = self . last_slot_issued_tx . send ( Instant :: now ( ) ) ;
85
106
Some ( SlotSupplierPermit :: NoData )
@@ -102,84 +123,105 @@ where
102
123
MI : MemoryInfo + Send + Sync ,
103
124
SK : SlotKind + Send + Sync ,
104
125
{
126
+ fn new (
127
+ inner : Arc < ResourceBasedSlots < MI > > ,
128
+ minimum : usize ,
129
+ max : usize ,
130
+ ramp_throttle : Duration ,
131
+ ) -> Self {
132
+ let ( tx, rx) = watch:: channel ( Instant :: now ( ) ) ;
133
+ Self {
134
+ minimum,
135
+ max,
136
+ ramp_throttle,
137
+ pids : Arc :: new ( Mutex :: new ( PidControllers :: new (
138
+ inner. target_mem_usage ,
139
+ inner. target_cpu_usage ,
140
+ ) ) ) ,
141
+ inner,
142
+ last_slot_issued_tx : tx,
143
+ last_slot_issued_rx : rx,
144
+ _slot_kind : PhantomData ,
145
+ }
146
+ }
147
+
105
148
fn time_since_last_issued ( & self ) -> Duration {
106
149
Instant :: now ( )
107
150
. checked_duration_since ( * self . last_slot_issued_rx . borrow ( ) )
108
151
. unwrap_or_default ( )
109
152
}
153
+
154
+ /// Returns true if the pid controllers think a new slot should be given out
155
+ fn pid_decision ( & self ) -> bool {
156
+ let mut pids = self . pids . lock ( ) ;
157
+ let mem_output = pids
158
+ . mem
159
+ . next_control_output ( self . inner . sys_info_supplier . process_used_percent ( ) )
160
+ . output ;
161
+ let cpu_output = pids
162
+ . cpu
163
+ . next_control_output ( self . inner . sys_info_supplier . used_cpu_percent ( ) )
164
+ . output ;
165
+ mem_output > 0.25 && cpu_output > 0.25
166
+ }
110
167
}
111
168
112
169
impl < MI > WorkflowCacheSizer for ResourceBasedSlots < MI >
113
170
where
114
- MI : MemoryInfo + Sync ,
171
+ MI : MemoryInfo + Sync + Send ,
115
172
{
116
- fn can_allow_workflow (
117
- & self ,
118
- slots_info : & WorkflowSlotsInfo ,
119
- _new_task : & WorkflowSlotInfo ,
120
- ) -> bool {
121
- self . can_reserve ( slots_info. used_slots . len ( ) )
173
+ fn can_allow_workflow ( & self , _: & WorkflowSlotsInfo , _: & WorkflowSlotInfo ) -> bool {
174
+ self . can_reserve ( )
122
175
}
123
176
}
124
177
125
- impl < MI : MemoryInfo + Sync > ResourceBasedSlots < MI > {
178
+ impl < MI : MemoryInfo + Sync + Send > ResourceBasedSlots < MI > {
126
179
// TODO: Can just be an into impl probably?
127
180
pub fn as_kind < SK : SlotKind + Send + Sync > (
128
181
self : & Arc < Self > ,
129
182
minimum : usize ,
183
+ max : usize ,
130
184
ramp_throttle : Duration ,
131
185
) -> Arc < ResourceBasedSlotsForType < MI , SK > > {
132
- let ( tx, rx) = watch:: channel ( Instant :: now ( ) ) ;
133
- Arc :: new ( ResourceBasedSlotsForType {
134
- inner : self . clone ( ) ,
186
+ Arc :: new ( ResourceBasedSlotsForType :: new (
187
+ self . clone ( ) ,
135
188
minimum,
189
+ max,
136
190
ramp_throttle,
137
- last_slot_issued_tx : tx,
138
- last_slot_issued_rx : rx,
139
- _slot_kind : PhantomData ,
140
- } )
191
+ ) )
141
192
}
142
193
143
194
pub fn into_kind < SK : SlotKind + Send + Sync > ( self ) -> ResourceBasedSlotsForType < MI , SK > {
144
- let ( tx, rx) = watch:: channel ( Instant :: now ( ) ) ;
145
- ResourceBasedSlotsForType {
146
- inner : Arc :: new ( self ) ,
147
- // TODO: Configure
148
- minimum : 1 ,
149
- ramp_throttle : Duration :: from_millis ( 0 ) ,
150
- last_slot_issued_tx : tx,
151
- last_slot_issued_rx : rx,
152
- _slot_kind : PhantomData ,
153
- }
195
+ // TODO: remove or parameterize
196
+ ResourceBasedSlotsForType :: new ( Arc :: new ( self ) , 1 , 1000 , Duration :: from_millis ( 0 ) )
154
197
}
155
198
156
- fn can_reserve ( & self , num_used : usize ) -> bool {
157
- if num_used > self . max {
158
- return false ;
159
- }
160
- self . mem_info_supplier . process_used_percent ( ) + self . assumed_maximum_marginal_contribution
161
- <= self . target_mem_usage
199
+ fn can_reserve ( & self ) -> bool {
200
+ self . sys_info_supplier . process_used_percent ( ) <= self . target_mem_usage
162
201
}
163
202
}
164
203
165
204
#[ derive( Debug ) ]
166
- pub struct SysinfoMem {
205
+ pub struct RealSysInfo {
167
206
sys : Mutex < sysinfo:: System > ,
168
207
pid : sysinfo:: Pid ,
169
208
cur_mem_usage : AtomicU64 ,
209
+ cur_cpu_usage : AtomicU32 ,
170
210
last_refresh : AtomicCell < Instant > ,
171
211
}
172
- impl SysinfoMem {
212
+ impl RealSysInfo {
173
213
fn new ( ) -> Self {
174
214
let mut sys = sysinfo:: System :: new ( ) ;
175
215
let pid = sysinfo:: get_current_pid ( ) . expect ( "get pid works" ) ;
176
216
sys. refresh_processes ( ) ;
177
217
sys. refresh_memory ( ) ;
218
+ sys. refresh_cpu ( ) ;
178
219
Self {
179
220
sys : Default :: default ( ) ,
180
221
last_refresh : AtomicCell :: new ( Instant :: now ( ) ) ,
181
222
pid,
182
223
cur_mem_usage : AtomicU64 :: new ( 0 ) ,
224
+ cur_cpu_usage : AtomicU32 :: new ( 0 ) ,
183
225
}
184
226
}
185
227
fn refresh_if_needed ( & self ) {
@@ -189,23 +231,32 @@ impl SysinfoMem {
189
231
let mut lock = self . sys . lock ( ) ;
190
232
lock. refresh_memory ( ) ;
191
233
lock. refresh_processes ( ) ;
234
+ lock. refresh_cpu_usage ( ) ;
192
235
let proc = lock. process ( self . pid ) . expect ( "exists" ) ;
193
236
self . cur_mem_usage
194
- . store ( dbg ! ( proc. memory( ) ) , std:: sync:: atomic:: Ordering :: Release ) ;
237
+ . store ( dbg ! ( proc. memory( ) ) , Ordering :: Release ) ;
238
+ self . cur_cpu_usage . store (
239
+ dbg ! ( lock. global_cpu_info( ) . cpu_usage( ) ) . to_bits ( ) ,
240
+ Ordering :: Release ,
241
+ ) ;
195
242
self . last_refresh . store ( Instant :: now ( ) )
196
243
}
197
244
}
198
245
}
199
- impl MemoryInfo for SysinfoMem {
246
+ impl MemoryInfo for RealSysInfo {
200
247
fn total_mem ( & self ) -> u64 {
201
248
self . refresh_if_needed ( ) ;
202
249
self . sys . lock ( ) . total_memory ( )
203
250
}
204
251
205
252
fn process_used_mem ( & self ) -> u64 {
206
253
self . refresh_if_needed ( ) ;
207
- self . cur_mem_usage
208
- . load ( std:: sync:: atomic:: Ordering :: Acquire )
254
+ self . cur_mem_usage . load ( Ordering :: Acquire )
255
+ }
256
+
257
+ fn used_cpu_percent ( & self ) -> f32 {
258
+ self . refresh_if_needed ( ) ;
259
+ f32:: from_bits ( self . cur_cpu_usage . load ( Ordering :: Acquire ) )
209
260
}
210
261
}
211
262
@@ -235,6 +286,10 @@ mod tests {
235
286
fn process_used_mem ( & self ) -> u64 {
236
287
self . used . load ( Ordering :: Acquire )
237
288
}
289
+
290
+ fn used_cpu_percent ( & self ) -> f32 {
291
+ todo ! ( )
292
+ }
238
293
}
239
294
struct FakeResCtx { }
240
295
impl SlotReservationContext for FakeResCtx {
@@ -248,9 +303,8 @@ mod tests {
248
303
let ( fmis, used) = FakeMIS :: new ( ) ;
249
304
let rbs = ResourceBasedSlots {
250
305
target_mem_usage : 0.8 ,
251
- assumed_maximum_marginal_contribution : 0.1 ,
252
- mem_info_supplier : fmis,
253
- max : 1000 ,
306
+ target_cpu_usage : 1.0 ,
307
+ sys_info_supplier : fmis,
254
308
}
255
309
. into_kind :: < WorkflowSlotKind > ( ) ;
256
310
assert ! ( rbs. try_reserve_slot( & FakeResCtx { } ) . is_some( ) ) ;
@@ -264,9 +318,8 @@ mod tests {
264
318
used. store ( 90_000 , Ordering :: Release ) ;
265
319
let rbs = ResourceBasedSlots {
266
320
target_mem_usage : 0.8 ,
267
- assumed_maximum_marginal_contribution : 0.1 ,
268
- mem_info_supplier : fmis,
269
- max : 1000 ,
321
+ target_cpu_usage : 1.0 ,
322
+ sys_info_supplier : fmis,
270
323
}
271
324
. into_kind :: < WorkflowSlotKind > ( ) ;
272
325
let order = crossbeam_queue:: ArrayQueue :: new ( 2 ) ;
0 commit comments