From c924b9ea46dcd2a701fdf6ba93a3733b72c09a99 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 13 Sep 2024 11:54:17 +0100 Subject: [PATCH 1/8] #2871 - Initial commit of a show_history() function in AbstractAgent --- src/primaite/game/agent/interface.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index d5165a71..404c2bfe 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -4,6 +4,7 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING from gymnasium.core import ActType, ObsType +from prettytable import PrettyTable from pydantic import BaseModel, model_validator from primaite.game.agent.actions import ActionManager @@ -126,6 +127,27 @@ class AbstractAgent(ABC): self.history: List[AgentHistoryItem] = [] self.logger = AgentLog(agent_name) + def show_history(self): + """ + Print an agent action provided it's not the DONOTHING action. + + :param agent_name: Name of agent (str). + """ + table = PrettyTable() + table.field_names = ["Step", "Action", "Node", "Application", "Response"] + print(f"Actions for '{self.agent_name}':") + for item in self.history: + if item.action != "DONOTHING": + node, application = "unknown", "unknown" + if (node_id := item.parameters.get("node_id")) is not None: + node = self.action_manager.node_names[node_id] + if (application_id := item.parameters.get("application_id")) is not None: + application = self.action_manager.application_names[node_id][application_id] + if (application_name := item.parameters.get("application_name")) is not None: + application = application_name + table.add_row([item.timestep, item.action, node, application, item.response.status]) + print(table) + def update_observation(self, state: Dict) -> ObsType: """ Convert a state from the simulator into an observation for the agent using the observation space. From f2a0eeaca23159da9caa0cd9e55e81f5aaac6875 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 13 Sep 2024 14:11:13 +0100 Subject: [PATCH 2/8] #2871 - Updated show_history() method to use boolean 'include_nothing' for whether to include DONOTHING actions --- src/primaite/game/agent/interface.py | 31 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index 404c2bfe..0ec44d22 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -127,25 +127,34 @@ class AbstractAgent(ABC): self.history: List[AgentHistoryItem] = [] self.logger = AgentLog(agent_name) - def show_history(self): + def add_agent_action(self, item: AgentHistoryItem, table: PrettyTable) -> PrettyTable: + """Update the given table with information from given AgentHistoryItem.""" + node, application = "unknown", "unknown" + if (node_id := item.parameters.get("node_id")) is not None: + node = self.action_manager.node_names[node_id] + if (application_id := item.parameters.get("application_id")) is not None: + application = self.action_manager.application_names[node_id][application_id] + if (application_name := item.parameters.get("application_name")) is not None: + application = application_name + table.add_row([item.timestep, item.action, node, application, item.response.status]) + return table + + def show_history(self, include_nothing: bool = False): """ Print an agent action provided it's not the DONOTHING action. - :param agent_name: Name of agent (str). + :param include_nothing: boolean for including DONOTHING actions. Default False. """ table = PrettyTable() table.field_names = ["Step", "Action", "Node", "Application", "Response"] print(f"Actions for '{self.agent_name}':") for item in self.history: - if item.action != "DONOTHING": - node, application = "unknown", "unknown" - if (node_id := item.parameters.get("node_id")) is not None: - node = self.action_manager.node_names[node_id] - if (application_id := item.parameters.get("application_id")) is not None: - application = self.action_manager.application_names[node_id][application_id] - if (application_name := item.parameters.get("application_name")) is not None: - application = application_name - table.add_row([item.timestep, item.action, node, application, item.response.status]) + if item.action == "DONOTHING": + if include_nothing: + table = self.add_agent_action(item=item, table=table) + else: + pass + self.add_agent_action(item=item, table=table) print(table) def update_observation(self, state: Dict) -> ObsType: From 5006e41546d37cabb0a505fe0c9e3346dcaebf89 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Fri, 13 Sep 2024 15:47:59 +0100 Subject: [PATCH 3/8] #2871 - Updated the show_history() function to receive a list of actions to ignore when printing the history. Defaults to ignoring DONOTHING actions --- src/primaite/game/agent/interface.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/primaite/game/agent/interface.py b/src/primaite/game/agent/interface.py index 0ec44d22..6609dd03 100644 --- a/src/primaite/game/agent/interface.py +++ b/src/primaite/game/agent/interface.py @@ -139,22 +139,23 @@ class AbstractAgent(ABC): table.add_row([item.timestep, item.action, node, application, item.response.status]) return table - def show_history(self, include_nothing: bool = False): + def show_history(self, ignored_actions: Optional[list] = None): """ Print an agent action provided it's not the DONOTHING action. - :param include_nothing: boolean for including DONOTHING actions. Default False. + :param ignored_actions: OPTIONAL: List of actions to be ignored when displaying the history. + If not provided, defaults to ignore DONOTHING actions. """ + if not ignored_actions: + ignored_actions = ["DONOTHING"] table = PrettyTable() table.field_names = ["Step", "Action", "Node", "Application", "Response"] print(f"Actions for '{self.agent_name}':") for item in self.history: - if item.action == "DONOTHING": - if include_nothing: - table = self.add_agent_action(item=item, table=table) - else: - pass - self.add_agent_action(item=item, table=table) + if item.action in ignored_actions: + pass + else: + table = self.add_agent_action(item=item, table=table) print(table) def update_observation(self, state: Dict) -> ObsType: From 5d7935cde083d662389198b8345fc9194e8351be Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 17 Sep 2024 09:39:32 +0100 Subject: [PATCH 4/8] #2871 - Changes to notebooks following updates to action history --- .../Command-&-Control-E2E-Demonstration.ipynb | 12 +++++- .../Data-Manipulation-E2E-Demonstration.ipynb | 11 ++++- .../Getting-Information-Out-Of-PrimAITE.ipynb | 40 ++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb index b6b13f28..a0599ee4 100644 --- a/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Command-&-Control-E2E-Demonstration.ipynb @@ -1800,6 +1800,16 @@ "\n", "display_obs_diffs(tcp_c2_obs, udp_c2_obs, blue_config_env.game.step_counter)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "env.game.agents[\"CustomC2Agent\"].show_history()" + ] } ], "metadata": { @@ -1818,7 +1828,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb index 0460f771..c1b959f5 100644 --- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb @@ -675,6 +675,15 @@ " print(f\"step: {env.game.step_counter}, Red action: {info['agent_actions']['data_manipulation_attacker'].action}, Blue reward:{reward:.2f}\" )" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "env.game.agents[\"data_manipulation_attacker\"].show_history(ignored_actions=[\"\"])" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -708,7 +717,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb index a832f3cc..e4009822 100644 --- a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb +++ b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb @@ -144,6 +144,44 @@ "PRIMAITE_CONFIG[\"developer_mode\"][\"enabled\"] = was_enabled\n", "PRIMAITE_CONFIG[\"developer_mode\"][\"output_sys_logs\"] = was_syslogs_enabled" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Viewing Agent history\n", + "\n", + "It's possible to view the actions carried out by an agent for a given training session using the `show_history()` method. By default, this will be all actions apart from DONOTHING actions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run the training session to generate some resultant data.\n", + "for i in range(100):\n", + " env.step(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling `.show_history()` should show us when the Data Manipulation used the `NODE_APPLICATION_EXECUTE` action." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "attacker = env.game.agents[\"data_manipulation_attacker\"]\n", + "\n", + "attacker.show_history()" + ] } ], "metadata": { @@ -162,7 +200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.10.11" } }, "nbformat": 4, From c8f6459af6022f2536580f968c04e9d32b15e596 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 17 Sep 2024 10:09:10 +0100 Subject: [PATCH 5/8] #2871 - Changelog and documentation updates, corrected changes in Data manipulation demo notebook --- CHANGELOG.md | 1 + docs/source/configuration/agents.rst | 1 + .../notebooks/Data-Manipulation-E2E-Demonstration.ipynb | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44f1ec29..b7f8a26e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Log observation space data by episode and step. +- Added `show_history` method to Agents, allowing you to view actions taken by an agent per step. By default, `DONOTHING` actions are omitted. ### Changed - ACL's are no longer applied to layer-2 traffic. diff --git a/docs/source/configuration/agents.rst b/docs/source/configuration/agents.rst index dece94c5..0bc586e8 100644 --- a/docs/source/configuration/agents.rst +++ b/docs/source/configuration/agents.rst @@ -177,3 +177,4 @@ If ``True``, gymnasium flattening will be performed on the observation space bef ----------------- Agents will record their action log for each step. This is a summary of what the agent did, along with response information from requests within the simulation. +A log of the actions taken by the agent can be viewed using the `show_history()` function. By default, this will display all actions taken apart from ``DONOTHING``. diff --git a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb index c1b959f5..13533097 100644 --- a/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb +++ b/src/primaite/notebooks/Data-Manipulation-E2E-Demonstration.ipynb @@ -681,7 +681,7 @@ "metadata": {}, "outputs": [], "source": [ - "env.game.agents[\"data_manipulation_attacker\"].show_history(ignored_actions=[\"\"])" + "env.game.agents[\"data_manipulation_attacker\"].show_history()" ] }, { From ccb91869c4e7b62e5772c09de496c5cc96b7d35a Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 17 Sep 2024 10:17:18 +0100 Subject: [PATCH 6/8] #2871 - Minor wording change to description in agents.rst --- docs/source/configuration/agents.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/configuration/agents.rst b/docs/source/configuration/agents.rst index 0bc586e8..74571cf2 100644 --- a/docs/source/configuration/agents.rst +++ b/docs/source/configuration/agents.rst @@ -177,4 +177,4 @@ If ``True``, gymnasium flattening will be performed on the observation space bef ----------------- Agents will record their action log for each step. This is a summary of what the agent did, along with response information from requests within the simulation. -A log of the actions taken by the agent can be viewed using the `show_history()` function. By default, this will display all actions taken apart from ``DONOTHING``. +A summary of the actions taken by the agent can be viewed using the `show_history()` function. By default, this will display all actions taken apart from ``DONOTHING``. From 3a5b75239d64c6febe35ac4bae227e8c804a8f01 Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 17 Sep 2024 12:05:40 +0100 Subject: [PATCH 7/8] #2871 - Typo in Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f8a26e..b81e256b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [3.3.0] - 2024-09-04 +## [3.4.0] ### Added - Log observation space data by episode and step. From 8d3760b5a7e8bf53f8a7e20cabc3a5597ecd897f Mon Sep 17 00:00:00 2001 From: Charlie Crane Date: Tue, 17 Sep 2024 16:19:43 +0100 Subject: [PATCH 8/8] #2871 - Fix notebook failure --- .../notebooks/Getting-Information-Out-Of-PrimAITE.ipynb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb index e4009822..6a60c1bc 100644 --- a/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb +++ b/src/primaite/notebooks/Getting-Information-Out-Of-PrimAITE.ipynb @@ -160,6 +160,11 @@ "metadata": {}, "outputs": [], "source": [ + "with open(data_manipulation_config_path(), 'r') as f:\n", + " cfg = yaml.safe_load(f)\n", + "\n", + "env = PrimaiteGymEnv(env_config=cfg)\n", + "\n", "# Run the training session to generate some resultant data.\n", "for i in range(100):\n", " env.step(0)"