diff --git a/com.unity.ml-agents/CHANGELOG.md b/com.unity.ml-agents/CHANGELOG.md index 9588417c1f..1112a3cc3c 100755 --- a/com.unity.ml-agents/CHANGELOG.md +++ b/com.unity.ml-agents/CHANGELOG.md @@ -76,6 +76,7 @@ and this project adheres to added in `CollectObservations()`. (#3825) - Model updates can now happen asynchronously with environment steps for better performance. (#3690) - `num_updates` and `train_interval` for SAC were replaced with `steps_per_update`. (#3690) +- Unity Player logs are now written out to the results directory. (#3877) ### Bug Fixes diff --git a/ml-agents-envs/mlagents_envs/environment.py b/ml-agents-envs/mlagents_envs/environment.py index 178ebe1eb6..d5ba9a3bd6 100644 --- a/ml-agents-envs/mlagents_envs/environment.py +++ b/ml-agents-envs/mlagents_envs/environment.py @@ -120,8 +120,9 @@ def __init__( seed: int = 0, no_graphics: bool = False, timeout_wait: int = 60, - args: Optional[List[str]] = None, + additional_args: Optional[List[str]] = None, side_channels: Optional[List[SideChannel]] = None, + log_folder: Optional[str] = None, ): """ Starts a new unity environment and establishes a connection with the environment. @@ -136,9 +137,11 @@ def __init__( :int timeout_wait: Time (in seconds) to wait for connection from environment. :list args: Addition Unity command line arguments :list side_channels: Additional side channel for no-rl communication with Unity + :str log_folder: Optional folder to write the Unity Player log file into. Requires absolute path. """ - args = args or [] atexit.register(self._close) + self.additional_args = additional_args or [] + self.no_graphics = no_graphics # If base port is not specified, use BASE_ENVIRONMENT_PORT if we have # an environment, otherwise DEFAULT_EDITOR_PORT if base_port is None: @@ -164,6 +167,7 @@ def __init__( ) ) self.side_channels[_sc.channel_id] = _sc + self.log_folder = log_folder # If the environment name is None, a new environment will not be launched # and the communicator will directly try to connect to an existing unity environment. @@ -174,7 +178,7 @@ def __init__( "the worker-id must be 0 in order to connect with the Editor." ) if file_name is not None: - self.executable_launcher(file_name, no_graphics, args) + self.executable_launcher(file_name, no_graphics, additional_args) else: logger.info( f"Listening on port {self.port}. " @@ -268,6 +272,20 @@ def validate_environment_path(env_path: str) -> Optional[str]: launch_string = candidates[0] return launch_string + def executable_args(self) -> List[str]: + args: List[str] = [] + if self.no_graphics: + args += ["-nographics", "-batchmode"] + args += [UnityEnvironment.PORT_COMMAND_LINE_ARG, str(self.port)] + if self.log_folder: + log_file_path = os.path.join( + self.log_folder, f"Player-{self.worker_id}.log" + ) + args += ["-logFile", log_file_path] + # Add in arguments passed explicitly by the user. + args += self.additional_args + return args + def executable_launcher(self, file_name, no_graphics, args): launch_string = self.validate_environment_path(file_name) if launch_string is None: @@ -278,11 +296,7 @@ def executable_launcher(self, file_name, no_graphics, args): else: logger.debug("This is the launch string {}".format(launch_string)) # Launch Unity environment - subprocess_args = [launch_string] - if no_graphics: - subprocess_args += ["-nographics", "-batchmode"] - subprocess_args += [UnityEnvironment.PORT_COMMAND_LINE_ARG, str(self.port)] - subprocess_args += args + subprocess_args = [launch_string] + self.executable_args() try: self.proc1 = subprocess.Popen( subprocess_args, diff --git a/ml-agents-envs/mlagents_envs/tests/test_envs.py b/ml-agents-envs/mlagents_envs/tests/test_envs.py index 1f1fc5dd99..2d0faef9a1 100755 --- a/ml-agents-envs/mlagents_envs/tests/test_envs.py +++ b/ml-agents-envs/mlagents_envs/tests/test_envs.py @@ -49,6 +49,18 @@ def test_port_defaults( assert expected == env.port +@mock.patch("mlagents_envs.environment.UnityEnvironment.executable_launcher") +@mock.patch("mlagents_envs.environment.UnityEnvironment.get_communicator") +def test_log_file_path_is_set(mock_communicator, mock_launcher): + mock_communicator.return_value = MockCommunicator() + env = UnityEnvironment( + file_name="myfile", worker_id=0, log_folder="./some-log-folder-path" + ) + args = env.executable_args() + log_file_index = args.index("-logFile") + assert args[log_file_index + 1] == "./some-log-folder-path/Player-0.log" + + @mock.patch("mlagents_envs.environment.UnityEnvironment.executable_launcher") @mock.patch("mlagents_envs.environment.UnityEnvironment.get_communicator") def test_reset(mock_communicator, mock_launcher): diff --git a/ml-agents/mlagents/trainers/learn.py b/ml-agents/mlagents/trainers/learn.py index f8519d585b..460efa2519 100644 --- a/ml-agents/mlagents/trainers/learn.py +++ b/ml-agents/mlagents/trainers/learn.py @@ -354,7 +354,12 @@ def run_training(run_seed: int, options: RunOptions) -> None: if options.env_path is None: port = UnityEnvironment.DEFAULT_EDITOR_PORT env_factory = create_environment_factory( - options.env_path, options.no_graphics, run_seed, port, options.env_args + options.env_path, + options.no_graphics, + run_seed, + port, + options.env_args, + os.path.abspath(run_logs_dir), # Unity environment requires absolute path ) engine_config = EngineConfig( options.width, @@ -470,6 +475,7 @@ def create_environment_factory( seed: int, start_port: int, env_args: Optional[List[str]], + log_folder: str, ) -> Callable[[int, List[SideChannel]], BaseEnv]: if env_path is not None: launch_string = UnityEnvironment.validate_environment_path(env_path) @@ -489,8 +495,9 @@ def create_unity_environment( seed=env_seed, no_graphics=no_graphics, base_port=start_port, - args=env_args, + additional_args=env_args, side_channels=side_channels, + log_folder=log_folder, ) return create_unity_environment diff --git a/ml-agents/mlagents/trainers/tests/test_learn.py b/ml-agents/mlagents/trainers/tests/test_learn.py index 0eda10936a..1b37f84aee 100644 --- a/ml-agents/mlagents/trainers/tests/test_learn.py +++ b/ml-agents/mlagents/trainers/tests/test_learn.py @@ -70,6 +70,7 @@ def test_bad_env_path(): seed=None, start_port=8000, env_args=None, + log_folder="results/log_folder", ) diff --git a/ml-agents/tests/yamato/scripts/run_llapi.py b/ml-agents/tests/yamato/scripts/run_llapi.py index d298bc9be2..591407f163 100644 --- a/ml-agents/tests/yamato/scripts/run_llapi.py +++ b/ml-agents/tests/yamato/scripts/run_llapi.py @@ -17,7 +17,7 @@ def test_run_environment(env_name): file_name=env_name, side_channels=[engine_configuration_channel], no_graphics=True, - args=["-logFile", "-"], + additional_args=["-logFile", "-"], ) try: @@ -94,14 +94,23 @@ def test_closing(env_name): """ try: env1 = UnityEnvironment( - file_name=env_name, base_port=5006, no_graphics=True, args=["-logFile", "-"] + file_name=env_name, + base_port=5006, + no_graphics=True, + additional_args=["-logFile", "-"], ) env1.close() env1 = UnityEnvironment( - file_name=env_name, base_port=5006, no_graphics=True, args=["-logFile", "-"] + file_name=env_name, + base_port=5006, + no_graphics=True, + additional_args=["-logFile", "-"], ) env2 = UnityEnvironment( - file_name=env_name, base_port=5007, no_graphics=True, args=["-logFile", "-"] + file_name=env_name, + base_port=5007, + no_graphics=True, + additional_args=["-logFile", "-"], ) env2.reset() finally: