1
- % % @copyright 2023 Erlang Solutions Ltd.
1
+ % % @copyright 2024 Erlang Solutions Ltd.
2
2
% % @doc Main controller of a node, responsible for the scenario and the users
3
3
% %
4
4
% % Note that this module should be rarely used, APIs are fully exposed by `amoc' and `amoc_dist'
8
8
-behaviour (gen_server ).
9
9
10
10
-define (SERVER , ? MODULE ).
11
- -define (USERS_TABLE , amoc_users ).
12
11
13
12
-required_variable (#{name => interarrival , default_value => 50 ,
14
- verification => {? MODULE , positive_integer , 1 },
13
+ verification => {? MODULE , non_neg_integer , 1 },
15
14
description => " a delay between creating the processes for two "
16
15
" consecutive users (ms, def: 50ms)" ,
17
16
update => {? MODULE , maybe_update_interarrival_timer , 2 }}).
18
17
19
18
-record (state , {scenario :: amoc :scenario () | undefined ,
20
- no_of_users = 0 :: user_count (),
21
19
last_user_id = 0 :: last_user_id (),
22
20
status = idle :: idle | running | terminating | finished |
23
21
{error , any ()} | disabled ,
67
65
% % ------------------------------------------------------------------
68
66
% % Parameters verification functions
69
67
% % ------------------------------------------------------------------
70
- -export ([maybe_update_interarrival_timer /2 , positive_integer /1 ]).
68
+ -export ([maybe_update_interarrival_timer /2 , non_neg_integer /1 ]).
69
+
70
+ -export ([zero_users_running /0 ]).
71
71
72
72
% % ------------------------------------------------------------------
73
73
% % gen_server Function Exports
77
77
% % ------------------------------------------------------------------
78
78
% % API Function Definitions
79
79
% % ------------------------------------------------------------------
80
+
80
81
% % @private
81
82
-spec start_link () -> {ok , pid ()}.
82
83
start_link () ->
@@ -121,17 +122,24 @@ disable() ->
121
122
gen_server :call (? SERVER , disable ).
122
123
123
124
% % @private
124
- -spec positive_integer (any ()) -> boolean ().
125
- positive_integer (Interarrival ) ->
126
- is_integer (Interarrival ) andalso Interarrival > 0 .
125
+ -spec non_neg_integer (any ()) -> boolean ().
126
+ non_neg_integer (Interarrival ) ->
127
+ is_integer (Interarrival ) andalso Interarrival >= 0 .
127
128
128
129
% % @private
129
130
-spec maybe_update_interarrival_timer (interarrival , term ()) -> ok .
130
131
maybe_update_interarrival_timer (interarrival , _ ) ->
131
132
gen_server :cast (? SERVER , maybe_update_interarrival_timer ).
133
+
134
+ % % @private
135
+ -spec zero_users_running () -> ok .
136
+ zero_users_running () ->
137
+ gen_server :cast (? SERVER , zero_users_running ).
138
+
132
139
% % ------------------------------------------------------------------
133
140
% % gen_server Function Definitions
134
141
% % ------------------------------------------------------------------
142
+
135
143
% % @private
136
144
-spec init ([]) -> {ok , state ()}.
137
145
init ([]) ->
@@ -174,6 +182,9 @@ handle_call(_Request, _From, State) ->
174
182
-spec handle_cast (any (), state ()) -> {noreply , state ()}.
175
183
handle_cast (maybe_update_interarrival_timer , State ) ->
176
184
{noreply , maybe_update_interarrival_timer (State )};
185
+ handle_cast (zero_users_running , State ) ->
186
+ NewSate = handle_zero_users_running (State ),
187
+ {noreply , NewSate };
177
188
handle_cast (_Msg , State ) ->
178
189
{noreply , State }.
179
190
@@ -182,8 +193,8 @@ handle_cast(_Msg, State) ->
182
193
handle_info (start_user , State ) ->
183
194
NewSate = handle_start_user (State ),
184
195
{noreply , NewSate };
185
- handle_info ({ 'DOWN' , _ , process , Pid , _ } , State ) ->
186
- NewSate = handle_stop_user ( Pid , State ),
196
+ handle_info (start_all_users , State ) ->
197
+ NewSate = handle_start_all_users ( State ),
187
198
{noreply , NewSate };
188
199
handle_info (_Msg , State ) ->
189
200
{noreply , State }.
@@ -209,12 +220,15 @@ handle_start_scenario(_Scenario, _Settings, #state{status = Status} = State) ->
209
220
{{error , {invalid_status , Status }}, State }.
210
221
211
222
-spec handle_stop_scenario (state ()) -> {handle_call_res (), state ()}.
212
- handle_stop_scenario (# state {no_of_users = 0 , status = running } = State ) ->
213
- terminate_scenario (State ),
214
- {ok , State # state {status = finished }};
215
223
handle_stop_scenario (# state {status = running } = State ) ->
216
- terminate_all_users (),
217
- {ok , State # state {status = terminating }};
224
+ case amoc_users_sup :count_no_of_users () of
225
+ 0 ->
226
+ terminate_scenario (State ),
227
+ {ok , State # state {status = finished }};
228
+ _ ->
229
+ amoc_users_sup :terminate_all_children (),
230
+ {ok , State # state {status = terminating }}
231
+ end ;
218
232
handle_stop_scenario (# state {status = Status } = State ) ->
219
233
{{error , {invalid_status , Status }}, State }.
220
234
@@ -240,29 +254,25 @@ handle_add(StartId, EndId, #state{last_user_id = LastId,
240
254
NewUsers = lists :seq (StartId , EndId ),
241
255
NewScheduledUsers = lists :append (ScheduledUsers , NewUsers ),
242
256
NewTRef = maybe_start_timer (TRef ),
243
- {ok , State # state {create_users = NewScheduledUsers , tref = NewTRef ,
244
- last_user_id = EndId }};
257
+ {ok , State # state {create_users = NewScheduledUsers , tref = NewTRef , last_user_id = EndId }};
245
258
handle_add (_StartId , _EndId , # state {status = running } = State ) ->
246
259
{{error , invalid_range }, State };
247
260
handle_add (_StartId , _EndId , # state {status = Status } = State ) ->
248
261
{{error , {invalid_status , Status }}, State }.
249
262
250
263
-spec handle_remove (user_count (), boolean (), state ()) -> handle_call_res ().
251
264
handle_remove (Count , ForceRemove , # state {status = running , scenario = Scenario }) ->
252
- amoc_telemetry :execute ([controller , users ], #{count => Count },
265
+ CountRemove = amoc_users_sup :stop_children (Count , ForceRemove ),
266
+ amoc_telemetry :execute ([controller , users ], #{count => CountRemove },
253
267
#{scenario => Scenario , type => remove }),
254
- Pids = case ets :match_object (? USERS_TABLE , '$1' , Count ) of
255
- {Objects , _ } -> [Pid || {_Id , Pid } <- Objects ];
256
- '$end_of_table' -> []
257
- end ,
258
- amoc_users_sup :stop_children (Pids , ForceRemove ),
259
- {ok , length (Pids )};
268
+ {ok , CountRemove };
260
269
handle_remove (_Count , _ForceRemove , # state {status = Status }) ->
261
270
{error , {invalid_status , Status }}.
262
271
263
272
-spec handle_status (state ()) -> amoc_status ().
264
273
handle_status (# state {status = running , scenario = Scenario ,
265
- no_of_users = N , last_user_id = LastId }) ->
274
+ last_user_id = LastId }) ->
275
+ N = amoc_users_sup :count_no_of_users (),
266
276
{running , #{scenario => Scenario , currently_running_users => N , highest_user_id => LastId }};
267
277
handle_status (# state {status = terminating , scenario = Scenario }) ->
268
278
{terminating , Scenario };
@@ -279,33 +289,26 @@ handle_disable(#state{status = Status} = State) ->
279
289
280
290
-spec handle_start_user (state ()) -> state ().
281
291
handle_start_user (# state {create_users = [UserId | T ],
282
- no_of_users = N ,
283
292
scenario = Scenario ,
284
293
scenario_state = ScenarioState } = State ) ->
285
- start_user (Scenario , UserId , ScenarioState ),
286
- State # state {create_users = T , no_of_users = N + 1 };
294
+ amoc_users_sup : start_child (Scenario , UserId , ScenarioState ),
295
+ State # state {create_users = T };
287
296
handle_start_user (# state {create_users = [], tref = TRef } = State ) ->
288
297
State # state {tref = maybe_stop_timer (TRef )}.
289
298
290
- -spec handle_stop_user (pid (), state ()) -> state ().
291
- handle_stop_user (Pid , State ) ->
292
- case ets :match (? USERS_TABLE , {'$1' , Pid }, 1 ) of
293
- {[[UserId ]], _ } ->
294
- ets :delete (? USERS_TABLE , UserId ),
295
- dec_no_of_users (State );
296
- _ ->
297
- State
298
- end .
299
+ -spec handle_start_all_users (state ()) -> state ().
300
+ handle_start_all_users (# state {create_users = AllUsers ,
301
+ scenario = Scenario ,
302
+ scenario_state = ScenarioState ,
303
+ tref = TRef } = State ) ->
304
+ amoc_users_sup :start_children (Scenario , AllUsers , ScenarioState ),
305
+ State # state {create_users = [], tref = maybe_stop_timer (TRef )}.
299
306
300
307
% % ------------------------------------------------------------------
301
308
% % helpers
302
309
% % ------------------------------------------------------------------
303
310
-spec start_tables () -> ok .
304
311
start_tables () -> % % ETS creation
305
- ? USERS_TABLE = ets :new (? USERS_TABLE , [named_table ,
306
- ordered_set ,
307
- protected ,
308
- {read_concurrency , true }]),
309
312
amoc_config_utils :create_amoc_config_ets (),
310
313
ok .
311
314
@@ -321,11 +324,12 @@ init_scenario(Scenario, Settings) ->
321
324
terminate_scenario (# state {scenario = Scenario , scenario_state = ScenarioState }) ->
322
325
amoc_scenario :terminate (Scenario , ScenarioState ).
323
326
324
- -spec maybe_start_timer (timer :tref () | undefined ) -> timer :tref ().
325
- maybe_start_timer (undefined ) ->
326
- {ok , TRef } = timer :send_interval (interarrival (), start_user ),
327
- TRef ;
328
- maybe_start_timer (TRef ) -> TRef .
327
+ -spec handle_zero_users_running (state ()) -> state ().
328
+ handle_zero_users_running (# state {status = terminating } = State ) ->
329
+ terminate_scenario (State ),
330
+ State # state {status = finished };
331
+ handle_zero_users_running (State ) ->
332
+ State .
329
333
330
334
-spec maybe_stop_timer (timer :tref () | undefined ) -> undefined .
331
335
maybe_stop_timer (undefined ) ->
@@ -334,43 +338,28 @@ maybe_stop_timer(TRef) ->
334
338
{ok , cancel } = timer :cancel (TRef ),
335
339
undefined .
336
340
337
- -spec start_user (amoc :scenario (), amoc_scenario :user_id (), any ()) -> ok .
338
- start_user (Scenario , Id , ScenarioState ) ->
339
- {ok , Pid } = supervisor :start_child (amoc_users_sup , [Scenario , Id , ScenarioState ]),
340
- ets :insert (? USERS_TABLE , {Id , Pid }),
341
- erlang :monitor (process , Pid ),
342
- ok .
343
-
344
- -spec terminate_all_users () -> any ().
345
- terminate_all_users () ->
346
- % stop all the users
347
- Match = ets :match_object (? USERS_TABLE , '$1' , 200 ),
348
- terminate_all_users (Match ).
349
-
350
- % % ets:continuation/0 type is unfortunately not exported from the ets module.
351
- -spec terminate_all_users ({tuple (), term ()} | '$end_of_table' ) -> ok .
352
- terminate_all_users ({Objects , Continuation }) ->
353
- Pids = [Pid || {_Id , Pid } <- Objects ],
354
- amoc_users_sup :stop_children (Pids , true ),
355
- Match = ets :match_object (Continuation ),
356
- terminate_all_users (Match );
357
- terminate_all_users ('$end_of_table' ) -> ok .
358
-
359
- -spec dec_no_of_users (state ()) -> state ().
360
- dec_no_of_users (# state {no_of_users = 1 , status = terminating } = State ) ->
361
- terminate_scenario (State ),
362
- State # state {no_of_users = 0 , status = finished };
363
- dec_no_of_users (# state {no_of_users = N } = State ) ->
364
- State # state {no_of_users = N - 1 }.
365
-
366
- -spec interarrival () -> interarrival ().
367
- interarrival () ->
341
+ -spec get_interarrival () -> interarrival ().
342
+ get_interarrival () ->
368
343
amoc_config :get (interarrival ).
369
344
370
345
-spec maybe_update_interarrival_timer (state ()) -> state ().
371
346
maybe_update_interarrival_timer (# state {tref = undefined } = State ) ->
372
347
State ;
373
348
maybe_update_interarrival_timer (# state {tref = TRef } = State ) ->
374
349
{ok , cancel } = timer :cancel (TRef ),
375
- {ok , NewTRef } = timer :send_interval (interarrival (), start_user ),
350
+ Value = get_interarrival (),
351
+ NewTRef = do_interarrival (Value ),
376
352
State # state {tref = NewTRef }.
353
+
354
+ -spec maybe_start_timer (timer :tref () | undefined ) -> timer :tref ().
355
+ maybe_start_timer (undefined ) ->
356
+ Value = get_interarrival (),
357
+ do_interarrival (Value );
358
+ maybe_start_timer (TRef ) -> TRef .
359
+
360
+ do_interarrival (0 ) ->
361
+ self () ! start_all_users ,
362
+ undefined ;
363
+ do_interarrival (Value ) ->
364
+ {ok , NewTRef } = timer :send_interval (Value , start_user ),
365
+ NewTRef .
0 commit comments