Commit 3a46366b004e92b7caef7f9ae7c0bc3a3fab0611
1 parent
ee5f79f8
Exists in
dev
Ajout infos précises sur statut commande + Bugfix simulateur sur "abort"
& Nouveaux diagrammes UML pour Command (state diag) et Agent (activity diag)
Showing
6 changed files
with
223 additions
and
67 deletions
Show diff stats
README.md
... | ... | @@ -67,13 +67,13 @@ This software has been tested and validated with the following configurations : |
67 | 67 | -------------------------------------------------------------------------------------------- |
68 | 68 | ## LAST VERSION |
69 | 69 | |
70 | -Date: 29/03/2019 | |
70 | +Date: 01/04/2019 | |
71 | 71 | |
72 | 72 | Author: E. Pallier |
73 | 73 | |
74 | -VERSION: 0.20.33 | |
74 | +VERSION: 0.20.34 | |
75 | 75 | |
76 | -Comment: Déplacé AgentM dans src/monitoring/ (pyros start agentM) | |
76 | +Comment: Ajout informations plus précises sur statut commande + Bugfix simulateur sur "abort" | |
77 | 77 | |
78 | 78 | - Scenario de test : |
79 | 79 | - lancer agents A et B en mode simu (option -t): ./pyros.py -t start agentA,agentB |
... | ... | @@ -88,6 +88,8 @@ Comment: Déplacé AgentM dans src/monitoring/ (pyros start agentM) |
88 | 88 | - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True |
89 | 89 | |
90 | 90 | - Historique des nouveautés implémentées : |
91 | + - Nouveaux diagrammes UML pour Command (state diag) et Agent (activity diag) | |
92 | + - Déplacé AgentM dans src/monitoring/ (pyros start agentM) | |
91 | 93 | - Implémenté la commande "restart_init" (voir doc "play with a pyros agent") et refactorisation de run() |
92 | 94 | - Implémenté le "abort" dans le simulateur |
93 | 95 | - Optimisation log_agent_status() (ex update_survey()) | ... | ... |
src/agent/Agent.py
... | ... | @@ -194,61 +194,133 @@ class Agent: |
194 | 194 | """ |
195 | 195 | |
196 | 196 | """ |
197 | - PLANTUML ACTIVITY DIAGRAM | |
198 | - # (http://plantuml.com/fr/activity-diagram-legacy) | |
199 | - """ | |
197 | + # --- Agent ACTIVIY DIAGRAM (plantUML) --- | |
198 | + # NEW syntax => http://plantuml.com/fr/activity-diagram-beta | |
199 | + # OLD syntax => http://plantuml.com/fr/activity-diagram-legacy | |
200 | 200 | |
201 | - """ | |
202 | 201 | @startuml |
203 | 202 | version |
204 | 203 | @enduml |
205 | - """ | |
206 | 204 | |
207 | - """ | |
205 | + # Exemple de skin utilisable (celui de ChemCam) : | |
206 | + skinparam activity { | |
207 | + StartColor red | |
208 | + EndColor Silver | |
209 | + BackgroundColor Peru | |
210 | + BackgroundColor<< Begin >> Olive | |
211 | + BorderColor Peru | |
212 | + FontName Impact | |
213 | + } | |
214 | + | |
215 | + (NEW SYNTAX) | |
208 | 216 | @startuml |
209 | 217 | |
210 | 218 | title |
211 | - __** Agent.run() function activity diagram **__ | |
219 | + __**Agent.run() function : Activity Diagram **__ | |
220 | + | |
212 | 221 | end title |
213 | 222 | |
214 | - skinparam activity { | |
215 | - StartColor red | |
216 | - EndColor Silver | |
217 | - BackgroundColor Peru | |
218 | - BackgroundColor<< Begin >> Olive | |
219 | - BorderColor Peru | |
220 | - FontName Impact | |
221 | - } | |
223 | + start | |
222 | 224 | |
223 | - (*) --> load_config() | |
225 | + :DO_EXIT = False | |
226 | + DO_RESTART = True; | |
224 | 227 | |
225 | - --> init() | |
228 | + while (DO_RESTART ?) is (yes) | |
229 | + :load_config(); | |
230 | + :init(); | |
231 | + :DO_MAIN_LOOP = True; | |
232 | + while (DO_MAIN_LOOP ?) is (yes) | |
233 | + partition main_loop() { | |
234 | + :reload_config(); | |
235 | + note right | |
236 | + only if changed | |
237 | + end note | |
226 | 238 | |
227 | - partition infinite_loop { | |
228 | - ---> "reload_config()" | |
229 | - note right | |
230 | - only if changed | |
231 | - end note | |
239 | + :log_agent_status(); | |
240 | + note right | |
241 | + Log this agent status in DB | |
242 | + end note | |
232 | 243 | |
233 | - --> log_agent_status() | |
234 | - note right | |
235 | - Log this agent status in DB | |
236 | - end note | |
244 | + :routine_process(); | |
245 | + | |
246 | + :cmd = get_next_valid_command(); | |
247 | + partition command_process(cmd) { | |
248 | + :cmd = self.general_process(cmd); | |
249 | + note right | |
250 | + only if there is a command | |
251 | + end note | |
252 | + :specific_process(cmd); | |
253 | + note right | |
254 | + only if there is a command and I am "active" | |
255 | + end note | |
256 | + } | |
257 | + if (DO_RESTART or DO_EXIT ?) then (yes) | |
258 | + :DO_MAIN_LOOP = False; | |
259 | + endif | |
237 | 260 | |
238 | - --> routine_process() | |
239 | 261 | |
240 | - --> cmd = get_next_valid_command() | |
241 | - --> cmd = command_process(cmd) | |
242 | - note right | |
243 | - only if there is a command | |
244 | - end note | |
245 | - } | |
246 | 262 | |
247 | - If "NEW ITERATION ?" then | |
248 | - --> [Yes] "reload_config()" | |
249 | - else | |
250 | - --> (*) | |
251 | - Endif | |
263 | + | |
264 | + } | |
265 | + endwhile (no) | |
266 | + endwhile (no) | |
267 | + | |
268 | + stop | |
269 | + | |
270 | + @enduml | |
271 | + | |
272 | + | |
273 | + (OLD SYNTAX) | |
274 | + @startuml | |
275 | + title | |
276 | + __** Agent.run() function activity diagram **__ | |
277 | + end title | |
278 | + | |
279 | + (*) --> DO_EXIT=False & DO_RESTART=True | |
280 | + | |
281 | + partition while-DO_RESTART { | |
282 | + --> load_config() | |
283 | + --> init() | |
284 | + --> DO_MAIN_LOOP = True | |
285 | + --> main_loop() | |
286 | + | |
287 | + partition while-DO_MAIN_LOOP { | |
288 | + partition main_loop() { | |
289 | + | |
290 | + ---> "reload_config()" | |
291 | + note right | |
292 | + only if changed | |
293 | + end note | |
294 | + | |
295 | + --> log_agent_status() | |
296 | + note right | |
297 | + Log this agent status in DB | |
298 | + end note | |
299 | + | |
300 | + --> routine_process() | |
301 | + | |
302 | + --> cmd = get_next_valid_command() | |
303 | + --> cmd = command_process(cmd) | |
304 | + note right | |
305 | + only if there is a command | |
306 | + end note | |
307 | + partition command_process(cmd) { | |
308 | + --> cmd = self.general_process(cmd) | |
309 | + --> specific_process(cmd) | |
310 | + note right | |
311 | + only if there is a command and I am "active" | |
312 | + end note | |
313 | + } | |
314 | + } | |
315 | + } | |
316 | + | |
317 | + If "NEW ITERATION ?" then | |
318 | + --> "reload_config()" | |
319 | + else | |
320 | + --> [No] (*) | |
321 | + Endif | |
322 | + | |
323 | + } | |
252 | 324 | |
253 | 325 | @enduml |
254 | 326 | """ |
... | ... | @@ -399,7 +471,7 @@ class Agent: |
399 | 471 | _path_data = '../../config' |
400 | 472 | |
401 | 473 | _iter_num = None |
402 | - | |
474 | + | |
403 | 475 | # Log object |
404 | 476 | _log = None |
405 | 477 | |
... | ... | @@ -595,10 +667,10 @@ class Agent: |
595 | 667 | cmd = self.get_next_valid_command() |
596 | 668 | #if cmd: cmd = self.general_process(cmd) |
597 | 669 | # Process this (next) command (if exists) |
598 | - if cmd: cmd = self.command_process(cmd) | |
670 | + if cmd: self.command_process(cmd) | |
599 | 671 | # if restart, exit this loop to restart from beginning |
600 | 672 | if self.DO_RESTART or self.DO_EXIT: |
601 | - self.DO_MAIN_LOOP = False | |
673 | + self.DO_MAIN_LOOP = False | |
602 | 674 | return |
603 | 675 | |
604 | 676 | self.printd("------END COMMMAND PROCESSING------") |
... | ... | @@ -611,7 +683,7 @@ class Agent: |
611 | 683 | # (simulator only) Exit if max duration is reached |
612 | 684 | if self.SIMULATOR_MAX_DURATION_SEC and (time.time()-self.start_time > self.SIMULATOR_MAX_DURATION_SEC): |
613 | 685 | self.print("Exit because of max duration set to ", self.SIMULATOR_MAX_DURATION_SEC, "s") |
614 | - self.kill_running_specific_cmd_if_exists() | |
686 | + self.kill_running_specific_cmd_if_exists(self.name) | |
615 | 687 | if self.SIMULATOR_MODE and self.SIMULATOR_WITH_TEST: self.simulator_test_results() |
616 | 688 | self.DO_MAIN_LOOP = False |
617 | 689 | return |
... | ... | @@ -911,9 +983,6 @@ class Agent: |
911 | 983 | # 6) Current cmd must now be a valid (not expired) and PENDING one, |
912 | 984 | # so pass it to general_process() for execution |
913 | 985 | #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}") |
914 | - self.print("***") | |
915 | - self.print("*** Got", cmd) | |
916 | - self.print("***") | |
917 | 986 | return cmd |
918 | 987 | |
919 | 988 | |
... | ... | @@ -929,7 +998,10 @@ class Agent: |
929 | 998 | assert cmd is not None |
930 | 999 | |
931 | 1000 | self.set_status(self.STATUS_GENERAL_PROCESS) |
932 | - self.print(f"Starting processing of {cmd}") | |
1001 | + self.print("***") | |
1002 | + self.print("*** RECEIVED", cmd) | |
1003 | + self.print("***") | |
1004 | + self.printd(f"Starting general processing of this command") | |
933 | 1005 | |
934 | 1006 | # Update read time to say that the command has been READ |
935 | 1007 | cmd.set_read_time() |
... | ... | @@ -999,7 +1071,7 @@ class Agent: |
999 | 1071 | #self.printd("Current pending commands are:") |
1000 | 1072 | #Command.show_commands(self._pending_commands) |
1001 | 1073 | self.print("Aborting current executing command if exists:") |
1002 | - self.kill_running_specific_cmd_if_exists() | |
1074 | + self.kill_running_specific_cmd_if_exists(cmd.sender) | |
1003 | 1075 | if cmd.name == "restart_init": |
1004 | 1076 | self.print("restart_init received: Restarting from init()") |
1005 | 1077 | self.DO_RESTART=True |
... | ... | @@ -1072,7 +1144,7 @@ class Agent: |
1072 | 1144 | self.printd("Ending specific process (thread has been launched)") |
1073 | 1145 | |
1074 | 1146 | |
1075 | - def kill_running_specific_cmd_if_exists(self): | |
1147 | + def kill_running_specific_cmd_if_exists(self, abort_sender): | |
1076 | 1148 | if (self._current_specific_thread is None) or not self._current_specific_thread.is_alive(): |
1077 | 1149 | self.printd("...No current specific command thread to abort...") |
1078 | 1150 | else: |
... | ... | @@ -1083,7 +1155,7 @@ class Agent: |
1083 | 1155 | #self._current_specific_thread.shutdown() |
1084 | 1156 | #threading._shutdown() |
1085 | 1157 | #self._current_specific_thread.raise_exception() |
1086 | - self._current_specific_cmd.set_as_killed() | |
1158 | + self._current_specific_cmd.set_as_killed_by(abort_sender) | |
1087 | 1159 | self._current_specific_thread.terminate() |
1088 | 1160 | # Now, wait for the end of the thread |
1089 | 1161 | if self.RUN_IN_THREAD: |
... | ... | @@ -1129,7 +1201,9 @@ class Agent: |
1129 | 1201 | SIMULATOR MODE ONLY |
1130 | 1202 | """ |
1131 | 1203 | |
1132 | - # There is a current command being processed => check its status to see if finished | |
1204 | + # There is a current command being processed | |
1205 | + # => check if next command is "abort" | |
1206 | + # => if so, instantly send an "abort" to abort previous command | |
1133 | 1207 | if self.cmdts is not None: |
1134 | 1208 | self.print(f"Waiting for end of cmd '{self.cmdts.name}' execution...") |
1135 | 1209 | # Update cmdts fields from DB |
... | ... | @@ -1139,8 +1213,14 @@ class Agent: |
1139 | 1213 | # If next command is "abort" then abort becomes the new current command (to be sent) |
1140 | 1214 | self.next_cmdts = self.simulator_get_next_command_to_send() |
1141 | 1215 | if self.next_cmdts and self.next_cmdts.name == "abort": |
1216 | + # Wait a little to give a chance to agentB to start execution of current command, | |
1217 | + # so that we can abort it then (otherwise it won't be aborted!!) | |
1218 | + time.sleep(2) | |
1142 | 1219 | self.cmdts = self.next_cmdts |
1143 | 1220 | self.next_cmdts = None |
1221 | + self.print("***") | |
1222 | + self.print(f"*** SEND command", self.cmdts) | |
1223 | + self.print("***") | |
1144 | 1224 | self.cmdts.send() |
1145 | 1225 | |
1146 | 1226 | # Current cmd is no more running |
... | ... | @@ -1176,7 +1256,7 @@ class Agent: |
1176 | 1256 | # Set cmdts to None so that a new command will be sent at next iteration |
1177 | 1257 | self.cmdts = None |
1178 | 1258 | |
1179 | - # No currently running command => get a new command to send | |
1259 | + # No currently running command => get a new command and SEND it | |
1180 | 1260 | if self.cmdts is None: |
1181 | 1261 | if self.next_cmdts is not None: |
1182 | 1262 | self.cmdts = self.next_cmdts |
... | ... | @@ -1186,8 +1266,11 @@ class Agent: |
1186 | 1266 | # No more command to send (from simulator) => return |
1187 | 1267 | if self.cmdts is None: return |
1188 | 1268 | # Send cmd (= set as pending and save) |
1189 | - self.print(f"Send command", self.cmdts) | |
1269 | + self.print("***") | |
1270 | + self.print(f"*** SEND command", self.cmdts) | |
1271 | + self.print("***") | |
1190 | 1272 | #self.cmdts.set_as_pending() |
1273 | + # SEND | |
1191 | 1274 | self.cmdts.send() |
1192 | 1275 | #cmdts_is_processed = False |
1193 | 1276 | #cmdts_res = None | ... | ... |
src/agent/AgentB.py
... | ... | @@ -29,8 +29,8 @@ class AgentB(Agent): |
29 | 29 | "flush_commands", |
30 | 30 | "go_active", |
31 | 31 | |
32 | - # Because of this command, the receiver agent | |
33 | - # will no more send any new command | |
32 | + # Because of this command, the recipient agent | |
33 | + # should no more send any new specific command | |
34 | 34 | "go_idle", |
35 | 35 | "go_idle", |
36 | 36 | "go_idle", | ... | ... |
src/agent/Agent_run_activitydiag.png
src/common/models.py
... | ... | @@ -323,6 +323,45 @@ class Command(models.Model): |
323 | 323 | | id | sender | recipient | name | validity_duration (default=60) | s_deposit_time | r_read_time |
324 | 324 | """ |
325 | 325 | |
326 | + """ | |
327 | + # --- Command STATE DIAGRAM --- | |
328 | + # http://plantuml.com/fr/state-diagram | |
329 | + @startuml | |
330 | + | |
331 | + title | |
332 | + __**models.Command class : State Diagram**__ | |
333 | + | |
334 | + end title | |
335 | + | |
336 | + [*] --> PENDING : **created by sender** | |
337 | + | |
338 | + ' --- (1) PENDING --- | |
339 | + PENDING: + s_deposit_time \n+ r_read_time | |
340 | + PENDING --> RUNNING : **launched** | |
341 | + PENDING --> SKIPPED: **recipient is IDLE** | |
342 | + PENDING --> EXPIRED: **command is too old** | |
343 | + | |
344 | + ' --- (2) RUNNING, SKIPPED, or EXPIRED --- | |
345 | + RUNNING: + r_launched_time | |
346 | + RUNNING --> PROCESSED : **finished** | |
347 | + RUNNING --> KILLED: \l**aborted** \n(by sender or other allowed agent, \nwith command "abort" or "exit") | |
348 | + | |
349 | + SKIPPED: + r_skipped_time | |
350 | + SKIPPED --> [*] | |
351 | + | |
352 | + EXPIRED --> [*] | |
353 | + EXPIRED: + expired_time \n+ author_agent_name | |
354 | + | |
355 | + ' --- (3) PROCESSED or KILLED --- | |
356 | + PROCESSED: + finished_time | |
357 | + PROCESSED --> [*] | |
358 | + | |
359 | + KILLED --> [*] | |
360 | + KILLED: + finished_time \n+ author_agent_name | |
361 | + | |
362 | + @enduml | |
363 | + """ | |
364 | + | |
326 | 365 | # -------------- Command CONSTANTS -------------- |
327 | 366 | |
328 | 367 | # Receiver status codes |
... | ... | @@ -361,17 +400,29 @@ class Command(models.Model): |
361 | 400 | # -------------- Command FIELDS -------------- |
362 | 401 | |
363 | 402 | #sender = models.CharField(max_length=50, blank=True, null=True, unique=True) |
364 | - sender = models.CharField(max_length=50, help_text='sender agent name') | |
365 | - recipient = models.CharField(max_length=50, help_text='recipient agent name') | |
366 | - name = models.CharField(max_length=400, help_text='command name') | |
403 | + # Sender and Recipient agents | |
404 | + sender = models.CharField(max_length=50, help_text='sender agent name', null=False) | |
405 | + recipient = models.CharField(max_length=50, help_text='recipient agent name', null=False) | |
406 | + # Other agent (than sender or recipient) that set this command to KILLED or EXPIRED | |
407 | + author_agent_name = models.CharField(max_length=50, help_text='sender agent name', null=True) | |
408 | + name = models.CharField(max_length=400, help_text='command name', null=False) | |
367 | 409 | validity_duration = models.PositiveIntegerField('(in sec)', default=COMMANDS_VALIDITY_DURATION_SEC_DEFAULT) |
368 | - # Automatically set at table line creation (line created by the sender) | |
410 | + | |
411 | + # (AUTO on create) Automatically set at table line creation (line created by the sender) | |
369 | 412 | s_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True) |
370 | 413 | # Set by the recipient : |
371 | - # - at reading time | |
414 | + # - on reading: | |
372 | 415 | r_read_time = models.DateTimeField(null=True) |
373 | - # - after execution | |
374 | - r_processed_time = models.DateTimeField(null=True) | |
416 | + # - on skipping (because idle): | |
417 | + r_skipped_time = models.DateTimeField(null=True) | |
418 | + # - on expiration (because too old): | |
419 | + r_skipped_time = models.DateTimeField(null=True) | |
420 | + # - on launching: | |
421 | + r_launched_time = models.DateTimeField(null=True) | |
422 | + # - after kill or execution: | |
423 | + #r_processed_time = models.DateTimeField(null=True) | |
424 | + finished_time = models.DateTimeField(null=True) | |
425 | + | |
375 | 426 | r_status_code = models.CharField(choices = CMD_STATUS_CODES, default=CMD_STATUS_CODES.CMD_PENDING, max_length=20) |
376 | 427 | #r_status_code = models.IntegerField(choices=CMD_STATUS_CODES, default=RSCODE_PENDING) |
377 | 428 | # TODO: maybe à mettre au format json (key:value) |
... | ... | @@ -595,10 +646,13 @@ class Command(models.Model): |
595 | 646 | |
596 | 647 | def set_as_processed(self): |
597 | 648 | print(f"- Set command {self.name} as processed") |
649 | + self.set_status_to(self.CMD_STATUS_CODES.CMD_EXECUTED) | |
598 | 650 | #print(self) |
651 | + """ | |
599 | 652 | self.r_status_code = self.CMD_STATUS_CODES.CMD_EXECUTED |
600 | 653 | self.r_processed_time = datetime.utcnow().astimezone() |
601 | 654 | self.save() |
655 | + """ | |
602 | 656 | # Optimization: update the related fields, but does not work, why ? |
603 | 657 | ##self.save(update_fields=["r_status_code", "r_processed_time"]) |
604 | 658 | |
... | ... | @@ -612,10 +666,10 @@ class Command(models.Model): |
612 | 666 | def set_as_skipped(self): |
613 | 667 | self.set_status_to(self.CMD_STATUS_CODES.CMD_SKIPPED) |
614 | 668 | |
615 | - def set_as_killed(self): | |
669 | + def set_as_killed_by(self, author_agent:str): | |
616 | 670 | print(f"- Set command {self.name} as killed") |
617 | 671 | #print(f"- Set this command as killed: {self}") |
618 | - self.set_status_to(self.CMD_STATUS_CODES.CMD_KILLED) | |
672 | + self.set_status_to(self.CMD_STATUS_CODES.CMD_KILLED, author_agent) | |
619 | 673 | |
620 | 674 | def set_as_running(self): |
621 | 675 | print(f"- Set command {self.name} as running") |
... | ... | @@ -625,7 +679,24 @@ class Command(models.Model): |
625 | 679 | self.set_status_to(self.CMD_STATUS_CODES.CMD_EXECUTED) |
626 | 680 | ''' |
627 | 681 | |
628 | - def set_status_to(self, status:str): | |
682 | + def set_status_to(self, status:str, author_agent_name:str=None): | |
683 | + now_time = datetime.utcnow().astimezone() | |
684 | + if status in (self.CMD_STATUS_CODES.CMD_RUNNING, self.CMD_STATUS_CODES.CMD_SKIPPED, self.CMD_STATUS_CODES.CMD_OUTOFDATE): | |
685 | + assert self.is_pending() | |
686 | + if status == self.CMD_STATUS_CODES.CMD_RUNNING: | |
687 | + self.r_launched_time = now_time | |
688 | + elif status == self.CMD_STATUS_CODES.CMD_SKIPPED: | |
689 | + self.r_skipped_time = now_time | |
690 | + # EXPIRED | |
691 | + elif status == self.CMD_STATUS_CODES.CMD_OUTOFDATE: | |
692 | + self.expired_time = now_time | |
693 | + self.author_agent_name = author_agent_name | |
694 | + elif status in (self.CMD_STATUS_CODES.CMD_EXECUTED, self.CMD_STATUS_CODES.CMD_KILLED): | |
695 | + assert self.is_running() | |
696 | + self.finished_time = now_time | |
697 | + if status == self.CMD_STATUS_CODES.CMD_KILLED: | |
698 | + self.author_agent_name = author_agent_name | |
699 | + # Update command status | |
629 | 700 | self.r_status_code = status |
630 | 701 | self.save() |
631 | 702 | # Optimization, but does not work, why ?... | ... | ... |
43.5 KB