labnotes 0.0.126__tar.gz → 0.0.128__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.126 → labnotes-0.0.128}/PKG-INFO +1 -1
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/context.md +12 -4
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/log_path.py +1 -1
- labnotes-0.0.128/src/labnotes/runner/cgroup_memory.py +17 -0
- labnotes-0.0.128/src/labnotes/runner/container_cgroup.py +15 -0
- labnotes-0.0.128/src/labnotes/runner/container_memory.py +100 -0
- labnotes-0.0.128/src/labnotes/runner/docker_output.py +11 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/execute_run.py +2 -1
- labnotes-0.0.128/src/labnotes/runner/fill_ids.py +8 -0
- labnotes-0.0.128/src/labnotes/runner/hook_ids.py +14 -0
- labnotes-0.0.128/src/labnotes/runner/record_memory.py +28 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/run_experiment.py +15 -3
- labnotes-0.0.126/src/labnotes/runner/record_memory.py +0 -18
- {labnotes-0.0.126 → labnotes-0.0.128}/.gitignore +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/CHANGELOG.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/MIGRATIONS.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/README.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/pyproject.toml +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/last_stderr.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/init.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/log.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/models.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/server.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/view/build.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/testing-conventions.toml +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.128}/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.128
|
|
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>
|
|
@@ -72,12 +72,15 @@ Each run gets a new run folder. In `matrix` mode it is named for the UTC time it
|
|
|
72
72
|
its folder the same way. One that plans more gets one folder named that way, and its runs nest
|
|
73
73
|
inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
|
|
74
74
|
`{run_dir}` replaced by the run folder's absolute path. Pass it to the
|
|
75
|
-
script so it writes its outputs there.
|
|
75
|
+
script so it writes its outputs there. `{run_id}` becomes the run id, and
|
|
76
|
+
`{invocation_id}` an id every run of one `run` command shares: the run id when
|
|
77
|
+
it plans one run, else the adhoc invocation folder's name, or in `matrix` mode a
|
|
78
|
+
stamped id that names no folder. All three are shell-quoted.
|
|
76
79
|
Each param is appended as `--name value`, with the name as written (use
|
|
77
80
|
`source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
|
|
78
81
|
and tables as JSON. labnotes writes `run.json` (params, `command`, created,
|
|
79
82
|
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
|
|
83
|
+
run folder. `command` is the shell command the run executed, with the placeholders
|
|
81
84
|
and the params filled in, written before the run starts, so a killed run has it
|
|
82
85
|
and a later edit to `experiment.toml` doesn't hide what a run did. `ended_by` is `"timeout"`, `"stop"` (labnotes stopped it),
|
|
83
86
|
`"signal"` (a signal labnotes didn't send, like the out-of-memory killer),
|
|
@@ -93,7 +96,9 @@ the value from the start, even if the code changed while they waited.
|
|
|
93
96
|
commands run once per `run` from the root: setup before the first run starts,
|
|
94
97
|
teardown after the last one ends, even if runs failed, were stopped, or setup
|
|
95
98
|
failed (so teardown must cope with a partial setup). A failing setup starts no
|
|
96
|
-
run; a failing teardown fails the invocation.
|
|
99
|
+
run; a failing teardown fails the invocation. Both get `{invocation_id}`, and
|
|
100
|
+
`{run_id}` only when one run is planned; otherwise `{run_id}` there fails the
|
|
101
|
+
invocation before anything starts.
|
|
97
102
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
98
103
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
99
104
|
`run` replaces it. Other keys you add to `run.json` are kept and show in `run`.
|
|
@@ -130,7 +135,10 @@ Runs can take a long time. Start `run` in the background, then check on it with
|
|
|
130
135
|
|
|
131
136
|
While a run goes, labnotes appends a memory sample to `memory.jsonl` in its run
|
|
132
137
|
folder every 5 seconds: one JSON line, `{"time": <unix seconds>, "rss": <bytes>,
|
|
133
|
-
"processes": <count>}`, for the command
|
|
138
|
+
"processes": <count>}`, for the command's process tree and its labeled Docker
|
|
139
|
+
containers. `LABNOTES_RUN` is the absolute run folder path; scripts label containers
|
|
140
|
+
with `docker run --label "labnotes.run=$LABNOTES_RUN" ...` to count their memory.
|
|
141
|
+
`processes` counts host processes in the run's tree, not containers. It keeps
|
|
134
142
|
every sample, so a run killed for memory still shows the climb.
|
|
135
143
|
`memory_interval` in `experiment.toml` (or `[experiment]` in `.labnotes.toml`)
|
|
136
144
|
sets the seconds between samples; 0 turns it off.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def cgroup_memory(path: Path) -> int:
|
|
5
|
+
current = int((path / "memory.current").read_text())
|
|
6
|
+
try:
|
|
7
|
+
stats = (path / "memory.stat").read_text().splitlines()
|
|
8
|
+
except OSError:
|
|
9
|
+
return current
|
|
10
|
+
for line in stats:
|
|
11
|
+
name, _, value = line.partition(" ")
|
|
12
|
+
if name == "inactive_file":
|
|
13
|
+
try:
|
|
14
|
+
return max(0, current - int(value))
|
|
15
|
+
except ValueError:
|
|
16
|
+
return current
|
|
17
|
+
return current
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
CGROUP_ROOT = Path("/sys/fs/cgroup")
|
|
4
|
+
PROC_ROOT = Path("/proc")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def container_cgroup(pid: int) -> Path | None:
|
|
8
|
+
try:
|
|
9
|
+
lines = (PROC_ROOT / str(pid) / "cgroup").read_text().splitlines()
|
|
10
|
+
except OSError:
|
|
11
|
+
return None
|
|
12
|
+
for line in lines:
|
|
13
|
+
if line.startswith("0::"):
|
|
14
|
+
return CGROUP_ROOT / line.removeprefix("0::").lstrip("/")
|
|
15
|
+
return None
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import shutil
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from labnotes.runner.cgroup_memory import cgroup_memory
|
|
7
|
+
from labnotes.runner.container_cgroup import container_cgroup
|
|
8
|
+
from labnotes.runner.docker_output import docker_output
|
|
9
|
+
|
|
10
|
+
LABEL = "labnotes.run"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ContainerMemory:
|
|
14
|
+
"""Track this run's Docker cgroups without starting a CLI process per sample."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, run_id: str) -> None:
|
|
17
|
+
self.run_id = run_id
|
|
18
|
+
self.cgroups: dict[str, Path] = {}
|
|
19
|
+
self.events: asyncio.subprocess.Process | None = None
|
|
20
|
+
self.watcher: asyncio.Task[None] | None = None
|
|
21
|
+
|
|
22
|
+
async def start(self) -> None:
|
|
23
|
+
if shutil.which("docker") is None:
|
|
24
|
+
return
|
|
25
|
+
since = str(int(time.time()))
|
|
26
|
+
ids = await asyncio.to_thread(
|
|
27
|
+
docker_output,
|
|
28
|
+
"ps",
|
|
29
|
+
"--quiet",
|
|
30
|
+
"--no-trunc",
|
|
31
|
+
"--filter",
|
|
32
|
+
f"label={LABEL}={self.run_id}",
|
|
33
|
+
)
|
|
34
|
+
if ids is None:
|
|
35
|
+
return
|
|
36
|
+
for container_id in ids.splitlines():
|
|
37
|
+
await self._add(container_id)
|
|
38
|
+
try:
|
|
39
|
+
self.events = await asyncio.create_subprocess_exec(
|
|
40
|
+
"docker",
|
|
41
|
+
"events",
|
|
42
|
+
"--since",
|
|
43
|
+
since,
|
|
44
|
+
"--filter",
|
|
45
|
+
"type=container",
|
|
46
|
+
"--filter",
|
|
47
|
+
f"label={LABEL}={self.run_id}",
|
|
48
|
+
"--format",
|
|
49
|
+
"{{.Actor.ID}} {{.Action}}",
|
|
50
|
+
stdout=asyncio.subprocess.PIPE,
|
|
51
|
+
stderr=asyncio.subprocess.DEVNULL,
|
|
52
|
+
)
|
|
53
|
+
except OSError:
|
|
54
|
+
return
|
|
55
|
+
self.watcher = asyncio.create_task(self._watch())
|
|
56
|
+
|
|
57
|
+
async def _add(self, container_id: str) -> None:
|
|
58
|
+
raw_pid = await asyncio.to_thread(
|
|
59
|
+
docker_output, "inspect", "--format", "{{.State.Pid}}", container_id
|
|
60
|
+
)
|
|
61
|
+
if raw_pid is None:
|
|
62
|
+
return
|
|
63
|
+
try:
|
|
64
|
+
pid = int(raw_pid.strip())
|
|
65
|
+
except ValueError:
|
|
66
|
+
return
|
|
67
|
+
if pid > 0 and (path := container_cgroup(pid)) is not None:
|
|
68
|
+
self.cgroups[container_id] = path
|
|
69
|
+
|
|
70
|
+
async def _watch(self) -> None:
|
|
71
|
+
assert self.events is not None
|
|
72
|
+
assert self.events.stdout is not None
|
|
73
|
+
while line := await self.events.stdout.readline():
|
|
74
|
+
container_id, _, action = line.decode().strip().partition(" ")
|
|
75
|
+
if action == "start":
|
|
76
|
+
await self._add(container_id)
|
|
77
|
+
elif action in {"die", "destroy", "stop"}:
|
|
78
|
+
self.cgroups.pop(container_id, None)
|
|
79
|
+
|
|
80
|
+
def usage(self) -> int:
|
|
81
|
+
total = 0
|
|
82
|
+
for path in self.cgroups.values():
|
|
83
|
+
try:
|
|
84
|
+
total += cgroup_memory(path)
|
|
85
|
+
except (OSError, ValueError):
|
|
86
|
+
pass
|
|
87
|
+
return total
|
|
88
|
+
|
|
89
|
+
async def close(self) -> None:
|
|
90
|
+
if self.watcher is not None:
|
|
91
|
+
self.watcher.cancel()
|
|
92
|
+
await asyncio.gather(self.watcher, return_exceptions=True)
|
|
93
|
+
if self.events is not None:
|
|
94
|
+
if self.events.returncode is None:
|
|
95
|
+
self.events.terminate()
|
|
96
|
+
try:
|
|
97
|
+
await asyncio.wait_for(self.events.wait(), timeout=1)
|
|
98
|
+
except TimeoutError:
|
|
99
|
+
self.events.kill()
|
|
100
|
+
await self.events.wait()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def docker_output(*args: str) -> str | None:
|
|
5
|
+
try:
|
|
6
|
+
result = subprocess.run(
|
|
7
|
+
["docker", *args], capture_output=True, text=True, timeout=3, check=False
|
|
8
|
+
)
|
|
9
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
10
|
+
return None
|
|
11
|
+
return result.stdout if result.returncode == 0 else None
|
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import psutil
|
|
9
9
|
|
|
10
|
-
from labnotes.log_path import LOG_ENV
|
|
10
|
+
from labnotes.log_path import LOG_ENV, RUN_ENV
|
|
11
11
|
from labnotes.runner.exit_signal import exit_signal
|
|
12
12
|
from labnotes.runner.record_memory import record_memory
|
|
13
13
|
from labnotes.runner.shell_command import shell_command
|
|
@@ -66,6 +66,7 @@ async def execute_run(
|
|
|
66
66
|
"CLICOLOR_FORCE": "1",
|
|
67
67
|
**os.environ,
|
|
68
68
|
LOG_ENV: str(folder / "log.jsonl"),
|
|
69
|
+
RUN_ENV: str(folder.resolve()),
|
|
69
70
|
},
|
|
70
71
|
stdout=stdout,
|
|
71
72
|
stderr=stderr,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
def hook_ids(
|
|
2
|
+
hooks: dict[str, str | None], invocation: str, runs: list[str]
|
|
3
|
+
) -> dict[str, str]:
|
|
4
|
+
match runs:
|
|
5
|
+
case [run]:
|
|
6
|
+
return {"run_id": run, "invocation_id": invocation}
|
|
7
|
+
for hook, command in hooks.items():
|
|
8
|
+
if command is not None and "{run_id}" in command:
|
|
9
|
+
raise ValueError(
|
|
10
|
+
f"{hook} command {command} uses {{run_id}}, but this invocation plans "
|
|
11
|
+
f"{len(runs)} runs. {{run_id}} needs exactly one; "
|
|
12
|
+
"use {invocation_id} instead."
|
|
13
|
+
)
|
|
14
|
+
return {"invocation_id": invocation}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from labnotes.runner.container_memory import ContainerMemory
|
|
7
|
+
from labnotes.utils.tree_usage import tree_usage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def record_memory(path: Path, pid: int, created: float, interval: float) -> None:
|
|
11
|
+
containers = ContainerMemory(str(path.parent.resolve()))
|
|
12
|
+
try:
|
|
13
|
+
await containers.start()
|
|
14
|
+
with path.open("a", encoding="utf-8") as samples:
|
|
15
|
+
while True:
|
|
16
|
+
usage = tree_usage(pid, created)
|
|
17
|
+
if usage is not None:
|
|
18
|
+
rss, processes = usage
|
|
19
|
+
sample = {
|
|
20
|
+
"time": time.time(),
|
|
21
|
+
"rss": rss + containers.usage(),
|
|
22
|
+
"processes": processes,
|
|
23
|
+
}
|
|
24
|
+
samples.write(json.dumps(sample) + "\n")
|
|
25
|
+
samples.flush()
|
|
26
|
+
await asyncio.sleep(interval)
|
|
27
|
+
finally:
|
|
28
|
+
await containers.close()
|
|
@@ -13,6 +13,8 @@ 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.fill_ids import fill_ids
|
|
17
|
+
from labnotes.runner.hook_ids import hook_ids
|
|
16
18
|
from labnotes.runner.in_subset import in_subset
|
|
17
19
|
from labnotes.runner.invocation_hook import invocation_hook
|
|
18
20
|
from labnotes.runner.invocation_meta import invocation_meta
|
|
@@ -77,6 +79,14 @@ async def run_experiment(
|
|
|
77
79
|
run = new_id(params)
|
|
78
80
|
planned.add(run)
|
|
79
81
|
plan.append((run, params))
|
|
82
|
+
match plan:
|
|
83
|
+
case [(invocation, _)]:
|
|
84
|
+
pass
|
|
85
|
+
case _:
|
|
86
|
+
invocation = group or new_stamped_run_id(taken)
|
|
87
|
+
planned.add(invocation)
|
|
88
|
+
hooks = {"setup": experiment.setup, "teardown": experiment.teardown}
|
|
89
|
+
ids = hook_ids(hooks, invocation, [run for run, _ in plan])
|
|
80
90
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
81
91
|
report(
|
|
82
92
|
{
|
|
@@ -212,7 +222,7 @@ async def run_experiment(
|
|
|
212
222
|
result = await execute_run(
|
|
213
223
|
config.root,
|
|
214
224
|
folder,
|
|
215
|
-
experiment.command,
|
|
225
|
+
fill_ids(experiment.command, {"run_id": run, "invocation_id": invocation}),
|
|
216
226
|
params,
|
|
217
227
|
running[run],
|
|
218
228
|
stopping,
|
|
@@ -237,14 +247,16 @@ async def run_experiment(
|
|
|
237
247
|
async def teardown() -> None:
|
|
238
248
|
if experiment.teardown is not None:
|
|
239
249
|
report({"event": "tearing down"})
|
|
240
|
-
await invocation_hook(
|
|
250
|
+
await invocation_hook(
|
|
251
|
+
config.root, "teardown", fill_ids(experiment.teardown, ids)
|
|
252
|
+
)
|
|
241
253
|
|
|
242
254
|
listen(Controls(interrupt, set_concurrency, kill, finish))
|
|
243
255
|
# Like vitest's globalSetup: a failed setup still gets its teardown.
|
|
244
256
|
try:
|
|
245
257
|
if experiment.setup is not None:
|
|
246
258
|
report({"event": "setting up"})
|
|
247
|
-
await invocation_hook(config.root, "setup", experiment.setup)
|
|
259
|
+
await invocation_hook(config.root, "setup", fill_ids(experiment.setup, ids))
|
|
248
260
|
fill()
|
|
249
261
|
await done.wait()
|
|
250
262
|
await asyncio.gather(*started)
|
|
@@ -1,18 +0,0 @@
|
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|