Skip to content

Commit 68b9420

Browse files
authored
Merge pull request #376 from simvue-io/dev
Updating RC with dev
2 parents e327b2e + 2a58868 commit 68b9420

File tree

6 files changed

+93
-42
lines changed

6 files changed

+93
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ if __name__ == "__main__":
9191
...
9292

9393
# Send metrics inside main application loop
94-
run.log({'loss': 0.5, 'density': 34.4})
94+
run.log_metrics({'loss': 0.5, 'density': 34.4})
9595

9696
...
9797

poetry.lock

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

simvue/executor.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def __init__(self, simvue_runner: "simvue.Run", keep_logs: bool = True) -> None:
9292
self._exit_codes = self._manager.dict()
9393
self._std_err = self._manager.dict()
9494
self._std_out = self._manager.dict()
95+
self._alert_ids: dict[str, str] = {}
9596
self._command_str: typing.Dict[str, str] = {}
9697
self._processes: typing.Dict[str, multiprocessing.Process] = {}
9798

@@ -221,6 +222,9 @@ def callback_function(status_code: int, std_out: str, std_err: str) -> None:
221222
env,
222223
),
223224
)
225+
self._alert_ids[identifier] = self._runner.create_alert(
226+
name=f"{identifier}_exit_status", source="user"
227+
)
224228
logger.debug(f"Executing process: {' '.join(_command)}")
225229
self._processes[identifier].start()
226230

@@ -239,6 +243,14 @@ def exit_status(self) -> int:
239243

240244
return 0
241245

246+
def get_error_summary(self) -> dict[str, typing.Optional[str]]:
247+
"""Returns the summary messages of all errors"""
248+
return {
249+
identifier: self._get_error_status(identifier)
250+
for identifier, value in self._exit_codes.items()
251+
if value
252+
}
253+
242254
def get_command(self, process_id: str) -> str:
243255
"""Returns the command executed within the given process.
244256
@@ -256,7 +268,19 @@ def get_command(self, process_id: str) -> str:
256268
raise KeyError(f"Failed to retrieve '{process_id}', no such process")
257269
return self._command_str[process_id]
258270

259-
def _log_events(self) -> None:
271+
def _get_error_status(self, process_id: str) -> typing.Optional[str]:
272+
err_msg: typing.Optional[str] = None
273+
274+
# Return last 10 lines of stdout if stderr empty
275+
if not (err_msg := self._std_err[process_id]) and (
276+
std_out := self._std_out[process_id]
277+
):
278+
err_msg = " Tail STDOUT:\n\n"
279+
start_index = -10 if len(lines := std_out.split("\n")) > 10 else 0
280+
err_msg += "\n".join(lines[start_index:])
281+
return err_msg
282+
283+
def _update_alerts(self) -> None:
260284
"""Send log events for the result of each process"""
261285
for proc_id, code in self._exit_codes.items():
262286
if code != 0:
@@ -265,11 +289,9 @@ def _log_events(self) -> None:
265289
if self._runner._dispatcher:
266290
self._runner._dispatcher.purge()
267291

268-
_err = self._std_err[proc_id]
269-
_msg = f"Process {proc_id} returned non-zero exit status {code} with:\n{_err}"
292+
self._runner.log_alert(self._alert_ids[proc_id], "critical")
270293
else:
271-
_msg = f"Process {proc_id} completed successfully."
272-
self._runner.log_event(_msg)
294+
self._runner.log_alert(self._alert_ids[proc_id], "ok")
273295

274296
# Wait for the dispatcher to send the latest information before
275297
# allowing the executor to finish (and as such the run instance to exit)
@@ -321,7 +343,7 @@ def wait_for_completion(self) -> None:
321343
for process in self._processes.values():
322344
if process.is_alive():
323345
process.join()
324-
self._log_events()
346+
self._update_alerts()
325347
self._save_output()
326348

327349
if not self.success:

simvue/run.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,19 @@ def __exit__(
178178
self._dispatcher.join()
179179

180180
if _non_zero := self.executor.exit_status:
181-
logger.error(
182-
f"Simvue process executor terminated with non-zero exit status {_non_zero}"
181+
_error_msgs: dict[str, typing.Optional[str]] = (
182+
self.executor.get_error_summary()
183+
)
184+
_error_msg = "\n".join(
185+
f"{identifier}:\n{msg}" for identifier, msg in _error_msgs.items()
186+
)
187+
if _error_msg:
188+
_error_msg = f":\n{_error_msg}"
189+
click.secho(
190+
"Simvue process executor terminated with non-zero exit status "
191+
f"{_non_zero}{_error_msg}",
192+
fg="red",
193+
bold=True,
183194
)
184195
sys.exit(_non_zero)
185196

@@ -1375,8 +1386,19 @@ def close(self) -> bool:
13751386
self._dispatcher.join()
13761387

13771388
if _non_zero := self.executor.exit_status:
1378-
logger.error(
1379-
f"Simvue process executor terminated with non-zero exit status {_non_zero}"
1389+
_error_msgs: dict[str, typing.Optional[str]] = (
1390+
self.executor.get_error_summary()
1391+
)
1392+
_error_msg = "\n".join(
1393+
f"{identifier}:\n{msg}" for identifier, msg in _error_msgs.items()
1394+
)
1395+
if _error_msg:
1396+
_error_msg = f":\n{_error_msg}"
1397+
click.secho(
1398+
"Simvue process executor terminated with non-zero exit status "
1399+
f"{_non_zero}{_error_msg}",
1400+
fg="red",
1401+
bold=True,
13801402
)
13811403
sys.exit(_non_zero)
13821404

simvue/serialization.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ def _serialize_plotly_figure(data: typing.Any) -> typing.Optional[tuple[str, str
9999
return None
100100
mimetype = "application/vnd.plotly.v1+json"
101101
data = plotly.io.to_json(data, engine="json")
102+
mfile = BytesIO()
103+
mfile.write(data.encode())
104+
mfile.seek(0)
105+
data = mfile.read()
102106
return data, mimetype
103107

104108

@@ -110,6 +114,10 @@ def _serialize_matplotlib(data: typing.Any) -> typing.Optional[tuple[str, str]]:
110114
return None
111115
mimetype = "application/vnd.plotly.v1+json"
112116
data = plotly.io.to_json(plotly.tools.mpl_to_plotly(data.gcf()), engine="json")
117+
mfile = BytesIO()
118+
mfile.write(data.encode())
119+
mfile.seek(0)
120+
data = mfile.read()
113121
return data, mimetype
114122

115123

@@ -121,6 +129,10 @@ def _serialize_matplotlib_figure(data: typing.Any) -> typing.Optional[tuple[str,
121129
return None
122130
mimetype = "application/vnd.plotly.v1+json"
123131
data = plotly.io.to_json(plotly.tools.mpl_to_plotly(data), engine="json")
132+
mfile = BytesIO()
133+
mfile.write(data.encode())
134+
mfile.seek(0)
135+
data = mfile.read()
124136
return data, mimetype
125137

126138

@@ -161,8 +173,11 @@ def _serialize_torch_tensor(data: typing.Any) -> typing.Optional[tuple[str, str]
161173
def _serialize_json(data: typing.Any) -> typing.Optional[tuple[str, str]]:
162174
mimetype = "application/json"
163175
try:
164-
data = json.dumps(data)
165-
except TypeError:
176+
mfile = BytesIO()
177+
mfile.write(json.dumps(data).encode())
178+
mfile.seek(0)
179+
data = mfile.read()
180+
except (TypeError, json.JSONDecodeError):
166181
return None
167182
return data, mimetype
168183

tests/refactor/test_executor.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ def test_executor_add_process(
3737
with pytest.raises(SystemExit):
3838
run.close()
3939

40-
time.sleep(1)
41-
client = simvue.Client()
42-
_events = client.get_events(
43-
run._id,
44-
message_contains="successfully" if successful else "non-zero exit",
45-
)
46-
assert len(_events) == 1
47-
4840

4941
@pytest.mark.executor
5042
def test_add_process_command_assembly(request: pytest.FixtureRequest) -> None:

0 commit comments

Comments
 (0)