labnotes 0.0.126__tar.gz → 0.0.127__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.127}/PKG-INFO +1 -1
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/context.md +4 -1
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/log_path.py +1 -1
- labnotes-0.0.127/src/labnotes/runner/cgroup_memory.py +17 -0
- labnotes-0.0.127/src/labnotes/runner/container_cgroup.py +15 -0
- labnotes-0.0.127/src/labnotes/runner/container_memory.py +100 -0
- labnotes-0.0.127/src/labnotes/runner/docker_output.py +11 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/execute_run.py +2 -1
- labnotes-0.0.127/src/labnotes/runner/record_memory.py +28 -0
- labnotes-0.0.126/src/labnotes/runner/record_memory.py +0 -18
- {labnotes-0.0.126 → labnotes-0.0.127}/.gitignore +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/CHANGELOG.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/MIGRATIONS.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/README.md +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/pyproject.toml +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/__main__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/compose_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/count_groups.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/control_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/daemon_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/daemon_client.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/daemon_running.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/daemon_server.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/describe_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/ensure_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/exit_when_idle.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/finalize_lost_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/last_stderr.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/remote_start.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/run_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/run_logs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/serve_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/socket_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/stop_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/watch_daemon.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon/watch_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/daemon_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/format_started.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/grid_cell.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/init.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/is_non_negative_int.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/is_non_negative_number.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/load_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/log.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/memory_interval.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/models.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/note_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/parse_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/pivot_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/retries.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/run_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/run_control.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/run_output.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/run_status.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/check_args.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/check_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/exit_signal.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/flag_name.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/in_subset.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/invocation_hook.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/invocation_meta.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/prepare_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/signal_tree.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/wait_for_tree.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/runs_view_command.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/save_conflict_copy.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/server.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/shared_params.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/elapsed.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/local-time.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/note-editor.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/run-output.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/experiment_run_ids.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/experiment_toml.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/experiments_ddl.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/file_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/note_text.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_log.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_memory.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/read_run_file.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/runs_columns.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/note.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/templates/run.html +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/browse_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/browse_tui.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/check_view_response.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/config.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/experiment_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/experiments_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/fill_table.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/format_cell.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/group_cells.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/invocation_columns.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/invocation_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/leave_prompt.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/log_block.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/log_blocks.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/native_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/reload_on_change.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/run_cells.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/run_output.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/run_screen.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/runs_view.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/stopped_title.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/view_column.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/tui/view_process.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/format_bytes.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/process_alive.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/run_usage.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/tree_rss.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/utils/tree_usage.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/view/__init__.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/view/answer.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/view/build.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/view/respond.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/view/serve.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/src/labnotes/watch_rows.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/testing-conventions.toml +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.126 → labnotes-0.0.127}/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.127
|
|
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>
|
|
@@ -130,7 +130,10 @@ Runs can take a long time. Start `run` in the background, then check on it with
|
|
|
130
130
|
|
|
131
131
|
While a run goes, labnotes appends a memory sample to `memory.jsonl` in its run
|
|
132
132
|
folder every 5 seconds: one JSON line, `{"time": <unix seconds>, "rss": <bytes>,
|
|
133
|
-
"processes": <count>}`, for the command
|
|
133
|
+
"processes": <count>}`, for the command's process tree and its labeled Docker
|
|
134
|
+
containers. `LABNOTES_RUN` is the absolute run folder path; scripts label containers
|
|
135
|
+
with `docker run --label "labnotes.run=$LABNOTES_RUN" ...` to count their memory.
|
|
136
|
+
`processes` counts host processes in the run's tree, not containers. It keeps
|
|
134
137
|
every sample, so a run killed for memory still shows the climb.
|
|
135
138
|
`memory_interval` in `experiment.toml` (or `[experiment]` in `.labnotes.toml`)
|
|
136
139
|
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,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()
|
|
@@ -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
|
|
File without changes
|