labnotes 0.0.108__tar.gz → 0.0.110__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labnotes-0.0.108 → labnotes-0.0.110}/PKG-INFO +1 -1
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/context.md +11 -4
- labnotes-0.0.110/src/labnotes/daemon/finalize_lost_runs.py +24 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/run_daemon.py +2 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/models.py +1 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/execute_run.py +3 -0
- labnotes-0.0.110/src/labnotes/runner/invocation_meta.py +28 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/read_experiment_config.py +5 -1
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/run_experiment.py +5 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/open_tables.py +1 -1
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/read_run.py +1 -0
- labnotes-0.0.110/src/labnotes/utils/process_alive.py +13 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/testing-conventions.toml +1 -1
- {labnotes-0.0.108 → labnotes-0.0.110}/.gitignore +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/CHANGELOG.md +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/MIGRATIONS.md +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/README.md +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/pyproject.toml +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/init.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/log.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/server.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.108 → labnotes-0.0.110}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.110
|
|
4
4
|
Summary: Browse experiment designs, notes, and run data from a folder.
|
|
5
5
|
Project-URL: Repository, https://github.com/thekevinscott/labnotes
|
|
6
6
|
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
@@ -24,8 +24,9 @@ When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experi
|
|
|
24
24
|
`experiment.toml`, files or runs.
|
|
25
25
|
- `files`: `experiment`, `filename`, `path`, `ctime`, `mtime`.
|
|
26
26
|
- `runs`: `experiment`, `id`, `folder`, `created`, `ended`, `exit_code`,
|
|
27
|
-
`error`, `stdout`, `stderr`, `params` as JSON,
|
|
28
|
-
`run.json` as JSON, read from each run
|
|
27
|
+
`error`, `stdout`, `stderr`, `params` as JSON, `meta` as JSON (null without
|
|
28
|
+
a `meta` command), and `run`, the whole `run.json` as JSON, read from each run
|
|
29
|
+
folder's `run.json`.
|
|
29
30
|
|
|
30
31
|
Keys come from folder names, so renaming a folder renames its rows on the next
|
|
31
32
|
read. Paths are relative to the root, and times are UTC ISO 8601.
|
|
@@ -77,9 +78,15 @@ Each param is appended as `--name value`, with the name as written (use
|
|
|
77
78
|
and tables as JSON. labnotes writes `run.json` (params, created, ended, exit
|
|
78
79
|
code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
|
|
79
80
|
run folder. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
|
|
80
|
-
`"signal"` (a signal labnotes didn't send, like the out-of-memory killer)
|
|
81
|
-
|
|
81
|
+
`"signal"` (a signal labnotes didn't send, like the out-of-memory killer),
|
|
82
|
+
`"lost"` (its process died unseen; the daemon marks these when it starts, with
|
|
83
|
+
`ended` set and `exit_code` null) or null (the script exited itself). `signal` is the signal number read from the
|
|
82
84
|
exit code (negative, or 128 + N from the shell), else null.
|
|
85
|
+
`meta` in `experiment.toml` (or `[experiment]` in `.labnotes.toml`) is a shell
|
|
86
|
+
command that prints one JSON object, such as the git commit. labnotes runs it
|
|
87
|
+
once from the root when `run` starts and copies it into every run's `run.json`
|
|
88
|
+
as `meta`. If it fails or prints anything else, no run starts. Queued runs keep
|
|
89
|
+
the value from the start, even if the code changed while they waited.
|
|
83
90
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
84
91
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
85
92
|
`run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from labnotes.list_runs import UNFINISHED
|
|
5
|
+
from labnotes.models import LabnotesConfig
|
|
6
|
+
from labnotes.runner.write_run_json import write_run_json
|
|
7
|
+
from labnotes.tables.iso_time import iso_time
|
|
8
|
+
from labnotes.tables.open_tables import open_tables
|
|
9
|
+
from labnotes.utils.process_alive import process_alive
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def finalize_lost_runs(config: LabnotesConfig) -> None:
|
|
13
|
+
rows = await open_tables(config).query(
|
|
14
|
+
f"SELECT folder, run FROM runs WHERE {UNFINISHED}"
|
|
15
|
+
)
|
|
16
|
+
for row in rows:
|
|
17
|
+
run = json.loads(str(row["run"]))
|
|
18
|
+
pid, created = run.get("pid"), run.get("pid_created")
|
|
19
|
+
if isinstance(pid, int) and process_alive(
|
|
20
|
+
pid, created if isinstance(created, int | float) else None
|
|
21
|
+
):
|
|
22
|
+
continue
|
|
23
|
+
run |= {"ended": iso_time(time.time()), "ended_by": "lost"}
|
|
24
|
+
write_run_json(config.root / str(row["folder"]), run)
|
|
@@ -6,11 +6,13 @@ import uvicorn
|
|
|
6
6
|
from labnotes.daemon.daemon_app import daemon_app
|
|
7
7
|
from labnotes.daemon.daemon_server import DaemonServer
|
|
8
8
|
from labnotes.daemon.exit_when_idle import exit_when_idle
|
|
9
|
+
from labnotes.daemon.finalize_lost_runs import finalize_lost_runs
|
|
9
10
|
from labnotes.daemon.socket_path import socket_path
|
|
10
11
|
from labnotes.models import LabnotesConfig
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
async def run_daemon(config: LabnotesConfig) -> None:
|
|
15
|
+
await finalize_lost_runs(config)
|
|
14
16
|
path = socket_path(config.root)
|
|
15
17
|
app = daemon_app(config)
|
|
16
18
|
server = DaemonServer(
|
|
@@ -29,6 +29,7 @@ async def execute_run(
|
|
|
29
29
|
timeout: float | None = None,
|
|
30
30
|
stop: Callable[[], None] = lambda: None,
|
|
31
31
|
stopped: Callable[[], bool] = lambda: False,
|
|
32
|
+
meta: dict[str, object] | None = None,
|
|
32
33
|
) -> dict[str, object]:
|
|
33
34
|
run: dict[str, object] = {
|
|
34
35
|
"params": params,
|
|
@@ -41,6 +42,8 @@ async def execute_run(
|
|
|
41
42
|
}
|
|
42
43
|
if retry_of is not None:
|
|
43
44
|
run["retry_of"] = retry_of
|
|
45
|
+
if meta is not None:
|
|
46
|
+
run["meta"] = meta
|
|
44
47
|
write_run_json(folder, run)
|
|
45
48
|
with (
|
|
46
49
|
(folder / "stdout.log").open("wb") as stdout,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def invocation_meta(root: Path, command: str) -> dict[str, object]:
|
|
7
|
+
process = await asyncio.create_subprocess_shell(
|
|
8
|
+
command,
|
|
9
|
+
cwd=root,
|
|
10
|
+
stdout=asyncio.subprocess.PIPE,
|
|
11
|
+
stderr=asyncio.subprocess.PIPE,
|
|
12
|
+
)
|
|
13
|
+
stdout, stderr = await process.communicate()
|
|
14
|
+
detail = stderr.decode(errors="replace").strip()
|
|
15
|
+
|
|
16
|
+
def fail(reason: str) -> ValueError:
|
|
17
|
+
message = f"meta command {command} {reason}"
|
|
18
|
+
return ValueError(f"{message}: {detail}" if detail else message)
|
|
19
|
+
|
|
20
|
+
if process.returncode != 0:
|
|
21
|
+
raise fail(f"exited {process.returncode}")
|
|
22
|
+
try:
|
|
23
|
+
meta = json.loads(stdout)
|
|
24
|
+
except ValueError:
|
|
25
|
+
meta = None
|
|
26
|
+
if not isinstance(meta, dict):
|
|
27
|
+
raise fail("didn't print a JSON object")
|
|
28
|
+
return meta
|
|
@@ -19,6 +19,7 @@ SETTINGS = {
|
|
|
19
19
|
"mode",
|
|
20
20
|
"concurrency",
|
|
21
21
|
"retries",
|
|
22
|
+
"meta",
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
|
|
@@ -69,6 +70,9 @@ def read_experiment_config(
|
|
|
69
70
|
retries = values.get("retries", retries)
|
|
70
71
|
if not is_non_negative_int(retries):
|
|
71
72
|
raise ValueError(f"{path}: retries must be 0 or more")
|
|
73
|
+
meta = values.get("meta")
|
|
74
|
+
if meta is not None and (not isinstance(meta, str) or not meta.strip()):
|
|
75
|
+
raise ValueError(f"{path}: meta must be a string")
|
|
72
76
|
return ExperimentConfig(
|
|
73
|
-
command, repeats, params, name, counts, mode, concurrency, retries
|
|
77
|
+
command, repeats, params, name, counts, mode, concurrency, retries, meta
|
|
74
78
|
)
|
|
@@ -13,6 +13,7 @@ from labnotes.models import (
|
|
|
13
13
|
)
|
|
14
14
|
from labnotes.retries import RETRY_EXIT_CODE, RETRY_SECONDS
|
|
15
15
|
from labnotes.runner.execute_run import execute_run
|
|
16
|
+
from labnotes.runner.invocation_meta import invocation_meta
|
|
16
17
|
from labnotes.runner.new_param_run_id import new_param_run_id
|
|
17
18
|
from labnotes.runner.new_stamped_run_id import new_stamped_run_id
|
|
18
19
|
from labnotes.runner.param_grid import param_grid
|
|
@@ -36,6 +37,9 @@ async def run_experiment(
|
|
|
36
37
|
timeout: float | None = None,
|
|
37
38
|
retry_seconds: float = RETRY_SECONDS,
|
|
38
39
|
) -> list[dict[str, object]]:
|
|
40
|
+
meta = None
|
|
41
|
+
if experiment.meta is not None:
|
|
42
|
+
meta = await invocation_meta(config.root, experiment.meta)
|
|
39
43
|
tables = open_tables(config)
|
|
40
44
|
existing = await experiment_run_ids(tables, name)
|
|
41
45
|
finished: list[dict[str, object]] = []
|
|
@@ -256,6 +260,7 @@ async def run_experiment(
|
|
|
256
260
|
timeout=timeout,
|
|
257
261
|
stop=lambda: kill(run),
|
|
258
262
|
stopped=lambda: run in stopped,
|
|
263
|
+
meta=meta,
|
|
259
264
|
)
|
|
260
265
|
del running[run], trees[run]
|
|
261
266
|
report({"event": "ended", "run": run, **result})
|
|
@@ -22,7 +22,7 @@ RUNS = """
|
|
|
22
22
|
CREATE TABLE runs (
|
|
23
23
|
experiment TEXT, id TEXT, folder TEXT, created TEXT, ended TEXT,
|
|
24
24
|
exit_code INTEGER, error TEXT,
|
|
25
|
-
stdout TEXT, stderr TEXT, params TEXT, run TEXT,
|
|
25
|
+
stdout TEXT, stderr TEXT, params TEXT, meta TEXT, run TEXT,
|
|
26
26
|
PRIMARY KEY (experiment, id)
|
|
27
27
|
)
|
|
28
28
|
"""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
|
|
3
|
+
from labnotes.utils.tree_rss import SAME_START_SECONDS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def process_alive(pid: int, created: float | None) -> bool:
|
|
7
|
+
try:
|
|
8
|
+
started = psutil.Process(pid).create_time()
|
|
9
|
+
except psutil.NoSuchProcess:
|
|
10
|
+
return False
|
|
11
|
+
except psutil.AccessDenied:
|
|
12
|
+
return True
|
|
13
|
+
return created is None or abs(started - created) <= SAME_START_SECONDS
|
|
@@ -67,7 +67,7 @@ reason = "WorkerState members are singletons, so is equals =="
|
|
|
67
67
|
[[python.exempt]]
|
|
68
68
|
path = "src/labnotes/runner/run_experiment.py"
|
|
69
69
|
rules = ["mutation"]
|
|
70
|
-
lines = [
|
|
70
|
+
lines = [188, 269]
|
|
71
71
|
reason = "a retry is only scheduled below the budget and each one adds one, so tried never passes retries: >= equals ==; both sides are small ints, which CPython caches, so is equals =="
|
|
72
72
|
|
|
73
73
|
[[python.exempt]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|