labnotes 0.0.110__tar.gz → 0.0.112__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.110 → labnotes-0.0.112}/PKG-INFO +1 -1
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/context.md +14 -5
- labnotes-0.0.112/src/labnotes/is_non_negative_number.py +10 -0
- labnotes-0.0.112/src/labnotes/memory_interval.py +2 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/models.py +2 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/execute_run.py +18 -2
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/read_experiment_config.py +16 -1
- labnotes-0.0.112/src/labnotes/runner/record_memory.py +18 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/run_experiment.py +1 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/open_tables.py +16 -1
- labnotes-0.0.112/src/labnotes/tables/read_memory.py +34 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/read_run.py +1 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/utils/process_alive.py +1 -1
- labnotes-0.0.112/src/labnotes/utils/tree_rss.py +6 -0
- labnotes-0.0.110/src/labnotes/utils/tree_rss.py → labnotes-0.0.112/src/labnotes/utils/tree_usage.py +2 -2
- {labnotes-0.0.110 → labnotes-0.0.112}/.gitignore +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/CHANGELOG.md +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/MIGRATIONS.md +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/README.md +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/pyproject.toml +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/init.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/log.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/log_path.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/server.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/testing-conventions.toml +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.110 → labnotes-0.0.112}/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.112
|
|
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>
|
|
@@ -25,8 +25,8 @@ When `experiment.toml` sits next to `.labnotes.toml`, the root is the one experi
|
|
|
25
25
|
- `files`: `experiment`, `filename`, `path`, `ctime`, `mtime`.
|
|
26
26
|
- `runs`: `experiment`, `id`, `folder`, `created`, `ended`, `exit_code`,
|
|
27
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
|
|
29
|
-
folder's `run.json`.
|
|
28
|
+
a `meta` command), `command`, and `run`, the whole `run.json` as JSON, read
|
|
29
|
+
from each run folder's `run.json`.
|
|
30
30
|
|
|
31
31
|
Keys come from folder names, so renaming a folder renames its rows on the next
|
|
32
32
|
read. Paths are relative to the root, and times are UTC ISO 8601.
|
|
@@ -75,9 +75,11 @@ inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that pat
|
|
|
75
75
|
script so it writes its outputs there.
|
|
76
76
|
Each param is appended as `--name value`, with the name as written (use
|
|
77
77
|
`source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
|
|
78
|
-
and tables as JSON. labnotes writes `run.json` (params, created,
|
|
79
|
-
code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
|
|
80
|
-
run folder. `
|
|
78
|
+
and tables as JSON. labnotes writes `run.json` (params, `command`, created,
|
|
79
|
+
ended, exit code, `ended_by`, `signal`), `stdout.log` and `stderr.log` to the
|
|
80
|
+
run folder. `command` is the shell command the run executed, with `{run_dir}`
|
|
81
|
+
and the params filled in, written before the run starts, so a killed run has it
|
|
82
|
+
and a later edit to `experiment.toml` doesn't hide what a run did. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
|
|
81
83
|
`"signal"` (a signal labnotes didn't send, like the out-of-memory killer),
|
|
82
84
|
`"lost"` (its process died unseen; the daemon marks these when it starts, with
|
|
83
85
|
`ended` set and `exit_code` null) or null (the script exited itself). `signal` is the signal number read from the
|
|
@@ -121,6 +123,13 @@ shows a waiting retry as `retry in 30s` and the end as `gave up (exit 75)`;
|
|
|
121
123
|
|
|
122
124
|
Runs can take a long time. Start `run` in the background, then check on it with `labnotes experiments foo runs --format json`; a run with no `ended` is still going. Each run lists `size`, the bytes in its run folder, and a running run lists `memory`, the resident bytes of its command and everything it started.
|
|
123
125
|
|
|
126
|
+
While a run goes, labnotes appends a memory sample to `memory.jsonl` in its run
|
|
127
|
+
folder every 5 seconds: one JSON line, `{"time": <unix seconds>, "rss": <bytes>,
|
|
128
|
+
"processes": <count>}`, for the command and everything it started. It keeps
|
|
129
|
+
every sample, so a run killed for memory still shows the climb.
|
|
130
|
+
`memory_interval` in `experiment.toml` (or `[experiment]` in `.labnotes.toml`)
|
|
131
|
+
sets the seconds between samples; 0 turns it off.
|
|
132
|
+
|
|
124
133
|
In a terminal, `run` shows a live table of runs (status, time, memory and
|
|
125
134
|
folder size) and stays
|
|
126
135
|
open when they finish. Failed runs show their status in red; `f` hides or shows them, and `r` on one
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any, Protocol
|
|
5
5
|
|
|
6
6
|
from labnotes.default_counts import DEFAULT_COUNTS
|
|
7
|
+
from labnotes.memory_interval import DEFAULT_MEMORY_INTERVAL
|
|
7
8
|
from labnotes.modes import DEFAULT_MODE
|
|
8
9
|
from labnotes.retries import DEFAULT_RETRIES
|
|
9
10
|
|
|
@@ -66,3 +67,4 @@ class ExperimentConfig:
|
|
|
66
67
|
concurrency: int = 1
|
|
67
68
|
retries: int = DEFAULT_RETRIES
|
|
68
69
|
meta: str | None = None
|
|
70
|
+
memory_interval: float = DEFAULT_MEMORY_INTERVAL
|
|
@@ -9,6 +9,7 @@ import psutil
|
|
|
9
9
|
|
|
10
10
|
from labnotes.log_path import LOG_ENV
|
|
11
11
|
from labnotes.runner.exit_signal import exit_signal
|
|
12
|
+
from labnotes.runner.record_memory import record_memory
|
|
12
13
|
from labnotes.runner.shell_command import shell_command
|
|
13
14
|
from labnotes.runner.stop_then_kill import GRACE_SECONDS
|
|
14
15
|
from labnotes.runner.wait_for_tree import wait_for_tree
|
|
@@ -30,9 +31,13 @@ async def execute_run(
|
|
|
30
31
|
stop: Callable[[], None] = lambda: None,
|
|
31
32
|
stopped: Callable[[], bool] = lambda: False,
|
|
32
33
|
meta: dict[str, object] | None = None,
|
|
34
|
+
*,
|
|
35
|
+
memory_interval: float,
|
|
33
36
|
) -> dict[str, object]:
|
|
37
|
+
resolved = shell_command(command, folder, params)
|
|
34
38
|
run: dict[str, object] = {
|
|
35
39
|
"params": params,
|
|
40
|
+
"command": resolved,
|
|
36
41
|
"created": iso_time(time.time()),
|
|
37
42
|
"ended": None,
|
|
38
43
|
"exit_code": None,
|
|
@@ -51,7 +56,7 @@ async def execute_run(
|
|
|
51
56
|
):
|
|
52
57
|
try:
|
|
53
58
|
process = await asyncio.create_subprocess_shell(
|
|
54
|
-
|
|
59
|
+
resolved,
|
|
55
60
|
cwd=root,
|
|
56
61
|
env={
|
|
57
62
|
"FORCE_COLOR": "1",
|
|
@@ -67,9 +72,17 @@ async def execute_run(
|
|
|
67
72
|
else:
|
|
68
73
|
running.add(process)
|
|
69
74
|
run["pid"] = process.pid
|
|
75
|
+
created = None
|
|
70
76
|
with contextlib.suppress(psutil.Error):
|
|
71
|
-
run["pid_created"] = psutil.Process(process.pid).create_time()
|
|
77
|
+
run["pid_created"] = created = psutil.Process(process.pid).create_time()
|
|
72
78
|
write_run_json(folder, run)
|
|
79
|
+
sampler = None
|
|
80
|
+
if memory_interval > 0 and created is not None:
|
|
81
|
+
sampler = asyncio.create_task(
|
|
82
|
+
record_memory(
|
|
83
|
+
folder / "memory.jsonl", process.pid, created, memory_interval
|
|
84
|
+
)
|
|
85
|
+
)
|
|
73
86
|
exited = asyncio.ensure_future(process.wait())
|
|
74
87
|
kill_later = None
|
|
75
88
|
try:
|
|
@@ -98,6 +111,9 @@ async def execute_run(
|
|
|
98
111
|
finally:
|
|
99
112
|
if kill_later is not None:
|
|
100
113
|
kill_later.cancel()
|
|
114
|
+
if sampler is not None:
|
|
115
|
+
sampler.cancel()
|
|
116
|
+
await asyncio.wait({sampler})
|
|
101
117
|
running.discard(process)
|
|
102
118
|
run["ended"] = iso_time(time.time())
|
|
103
119
|
write_run_json(folder, run)
|
|
@@ -7,6 +7,8 @@ from labnotes.compose_experiment import compose_experiment
|
|
|
7
7
|
from labnotes.default_counts import DEFAULT_COUNTS
|
|
8
8
|
from labnotes.models import ExperimentConfig
|
|
9
9
|
from labnotes.is_non_negative_int import is_non_negative_int
|
|
10
|
+
from labnotes.is_non_negative_number import is_non_negative_number
|
|
11
|
+
from labnotes.memory_interval import DEFAULT_MEMORY_INTERVAL
|
|
10
12
|
from labnotes.is_positive_int import is_positive_int
|
|
11
13
|
from labnotes.modes import MODES
|
|
12
14
|
|
|
@@ -20,6 +22,7 @@ SETTINGS = {
|
|
|
20
22
|
"concurrency",
|
|
21
23
|
"retries",
|
|
22
24
|
"meta",
|
|
25
|
+
"memory_interval",
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
|
|
@@ -73,6 +76,18 @@ def read_experiment_config(
|
|
|
73
76
|
meta = values.get("meta")
|
|
74
77
|
if meta is not None and (not isinstance(meta, str) or not meta.strip()):
|
|
75
78
|
raise ValueError(f"{path}: meta must be a string")
|
|
79
|
+
memory_interval = values.get("memory_interval", DEFAULT_MEMORY_INTERVAL)
|
|
80
|
+
if not is_non_negative_number(memory_interval):
|
|
81
|
+
raise ValueError(f"{path}: memory_interval must be 0 or more seconds")
|
|
76
82
|
return ExperimentConfig(
|
|
77
|
-
command,
|
|
83
|
+
command,
|
|
84
|
+
repeats,
|
|
85
|
+
params,
|
|
86
|
+
name,
|
|
87
|
+
counts,
|
|
88
|
+
mode,
|
|
89
|
+
concurrency,
|
|
90
|
+
retries,
|
|
91
|
+
meta,
|
|
92
|
+
memory_interval,
|
|
78
93
|
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from labnotes.utils.tree_usage import tree_usage
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def record_memory(path: Path, pid: int, created: float, interval: float) -> None:
|
|
10
|
+
with path.open("a", encoding="utf-8") as samples:
|
|
11
|
+
while True:
|
|
12
|
+
usage = tree_usage(pid, created)
|
|
13
|
+
if usage is not None:
|
|
14
|
+
rss, processes = usage
|
|
15
|
+
sample = {"time": time.time(), "rss": rss, "processes": processes}
|
|
16
|
+
samples.write(json.dumps(sample) + "\n")
|
|
17
|
+
samples.flush()
|
|
18
|
+
await asyncio.sleep(interval)
|
|
@@ -10,6 +10,7 @@ from labnotes.tables.read_blocks import read_blocks
|
|
|
10
10
|
from labnotes.tables.read_experiment import read_experiment
|
|
11
11
|
from labnotes.tables.read_file import read_file
|
|
12
12
|
from labnotes.tables.read_log import read_log
|
|
13
|
+
from labnotes.tables.read_memory import read_memory
|
|
13
14
|
from labnotes.tables.read_run import read_run
|
|
14
15
|
|
|
15
16
|
FILES = """
|
|
@@ -22,7 +23,7 @@ RUNS = """
|
|
|
22
23
|
CREATE TABLE runs (
|
|
23
24
|
experiment TEXT, id TEXT, folder TEXT, created TEXT, ended TEXT,
|
|
24
25
|
exit_code INTEGER, error TEXT,
|
|
25
|
-
stdout TEXT, stderr TEXT, params TEXT, meta TEXT, run TEXT,
|
|
26
|
+
stdout TEXT, stderr TEXT, params TEXT, meta TEXT, command TEXT, run TEXT,
|
|
26
27
|
PRIMARY KEY (experiment, id)
|
|
27
28
|
)
|
|
28
29
|
"""
|
|
@@ -39,6 +40,12 @@ CREATE TABLE blocks (
|
|
|
39
40
|
PRIMARY KEY (experiment, run)
|
|
40
41
|
)
|
|
41
42
|
"""
|
|
43
|
+
MEMORY = """
|
|
44
|
+
CREATE TABLE memory (
|
|
45
|
+
experiment TEXT, run TEXT, line INTEGER, time REAL, rss INTEGER, processes INTEGER,
|
|
46
|
+
PRIMARY KEY (experiment, run, line)
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
def open_tables(config: LabnotesConfig) -> DirSQL:
|
|
@@ -78,6 +85,14 @@ def open_tables(config: LabnotesConfig) -> DirSQL:
|
|
|
78
85
|
read_blocks, root, config.runs, experiment=config.experiment
|
|
79
86
|
),
|
|
80
87
|
),
|
|
88
|
+
Table(
|
|
89
|
+
name="memory",
|
|
90
|
+
ddl=MEMORY,
|
|
91
|
+
glob=path_glob(f"{config.runs}/memory.jsonl"),
|
|
92
|
+
on_file=partial(
|
|
93
|
+
read_memory, root, config.runs, experiment=config.experiment
|
|
94
|
+
),
|
|
95
|
+
),
|
|
81
96
|
Table(
|
|
82
97
|
name="experiment_configs",
|
|
83
98
|
ddl=experiments_ddl(config),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from labnotes.tables.path_pattern import path_pattern
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_memory(
|
|
8
|
+
root: Path, template: str, path: str, experiment: str | None = None
|
|
9
|
+
) -> list[dict[str, object]]:
|
|
10
|
+
relative = Path(path).relative_to(root).as_posix()
|
|
11
|
+
found = path_pattern(f"{template}/memory.jsonl").fullmatch(relative)
|
|
12
|
+
if found is None:
|
|
13
|
+
return []
|
|
14
|
+
rows: list[dict[str, object]] = []
|
|
15
|
+
lines = Path(path).read_text(encoding="utf-8").splitlines()
|
|
16
|
+
for line, text in enumerate(lines):
|
|
17
|
+
# A run killed mid-write leaves a cut-off last line.
|
|
18
|
+
try:
|
|
19
|
+
sample = json.loads(text)
|
|
20
|
+
except json.JSONDecodeError:
|
|
21
|
+
continue
|
|
22
|
+
if not isinstance(sample, dict):
|
|
23
|
+
continue
|
|
24
|
+
rows.append(
|
|
25
|
+
{
|
|
26
|
+
"experiment": experiment or found["experiment"],
|
|
27
|
+
"run": found["run"],
|
|
28
|
+
"line": line,
|
|
29
|
+
"time": sample.get("time"),
|
|
30
|
+
"rss": sample.get("rss"),
|
|
31
|
+
"processes": sample.get("processes"),
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
return rows
|
labnotes-0.0.110/src/labnotes/utils/tree_rss.py → labnotes-0.0.112/src/labnotes/utils/tree_usage.py
RENAMED
|
@@ -7,7 +7,7 @@ import psutil
|
|
|
7
7
|
SAME_START_SECONDS = 0.01
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def tree_usage(pid: int, created: float) -> tuple[int, int] | None:
|
|
11
11
|
try:
|
|
12
12
|
root = psutil.Process(pid)
|
|
13
13
|
if abs(root.create_time() - created) > SAME_START_SECONDS:
|
|
@@ -24,4 +24,4 @@ def tree_rss(pid: int, created: float) -> int | None:
|
|
|
24
24
|
for process in tree:
|
|
25
25
|
with contextlib.suppress(psutil.NoSuchProcess, psutil.AccessDenied):
|
|
26
26
|
sizes.append(process.memory_info().rss)
|
|
27
|
-
return sum(sizes) if sizes else None
|
|
27
|
+
return (sum(sizes), len(sizes)) if sizes else None
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|