runmonitor 0.2.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 k
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+ recursive-include templates *.html
5
+ recursive-include static *.css
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: runmonitor
3
+ Version: 0.2.0
4
+ Summary: Lean, local experiment tracker with a live, terminal-styled web dashboard.
5
+ Author-email: bub4tz <ezgroupnl@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 k
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/empero-org/runmonitor
29
+ Project-URL: Repository, https://github.com/empero-org/runmonitor
30
+ Project-URL: Issues, https://github.com/empero-org/runmonitor/issues
31
+ Keywords: machine-learning,experiment-tracking,metrics,dashboard,training,monitoring,mlops,tensorboard,wandb
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
43
+ Classifier: Topic :: System :: Monitoring
44
+ Requires-Python: >=3.10
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Requires-Dist: flask>=2.0
48
+ Provides-Extra: system
49
+ Requires-Dist: psutil>=5.0; extra == "system"
50
+ Dynamic: license-file
51
+
52
+ # runmonitor
53
+
54
+ Lean, local experiment tracker with a live, **terminal-styled** web dashboard. Import and go.
55
+
56
+ ```python
57
+ import runmonitor as rm
58
+
59
+ run = rm.init("my-experiment", config={"lr": 0.001, "batch_size": 32}, total_steps=1000)
60
+
61
+ for step in range(1000):
62
+ loss, acc = train_step()
63
+ run.log({"loss": loss, "accuracy": acc}, step)
64
+ if step % 100 == 0:
65
+ run.save("checkpoint.pt")
66
+
67
+ run.finish()
68
+ ```
69
+
70
+ Open `http://localhost:8080` — your loss curve is already live and drawing itself.
71
+
72
+ ## Install
73
+
74
+ ```bash
75
+ pip install runmonitor # core
76
+ pip install "runmonitor[system]" # + CPU/RAM tracking (psutil)
77
+ ```
78
+
79
+ Then in any training script:
80
+
81
+ ```python
82
+ import runmonitor as rm
83
+ ```
84
+
85
+ Or run the dashboard on its own (no training script needed):
86
+
87
+ ```bash
88
+ runmonitor # opens the dashboard in your browser
89
+ python -m runmonitor # equivalent, for a checkout/vendored copy
90
+ RUNMONITOR_PORT=9000 runmonitor # pick a port (set before launch)
91
+ ```
92
+
93
+ ## The dashboard
94
+
95
+ A single, evolving view in the empero black/purple palette:
96
+
97
+ | Element | What it shows |
98
+ |---|---|
99
+ | **Metric ticker** | Every logged metric as `key=value`. Click one to **select** it. |
100
+ | **Live hero curve** | The selected metric, drawn point-by-point and growing every step. |
101
+ | **Anomaly detection** | Rolling z-score flags spikes — vertical markers on the curve + a status line ("⚠ anomaly at step N" / "● all calm"). |
102
+ | **Run header** | Run id, current step, status pill, elapsed, steps/sec, ETA, progress bar. |
103
+ | **Streak / best badges** | 🔥 improving-streak and 🏆 personal-best on the selected metric (direction-aware). |
104
+ | **Compare** | Pick a second run to overlay on the selected metric. |
105
+ | **System pane** | CPU % and RAM % over time (needs `psutil`). |
106
+ | **Hyperparameters / Artifacts** | Config passed to `rm.init()` and any saved files. |
107
+ | **Export** | Download the full run as CSV or JSON. |
108
+ | **Theme** | Midnight (black/purple) by default; toggle to light bone-paper. |
109
+
110
+ ## API
111
+
112
+ ```python
113
+ # Start a run (creates the project if new)
114
+ run = rm.init(project: str, name: str | None = None,
115
+ config: dict | None = None,
116
+ total_steps: int | None = None) -> Run
117
+
118
+ run.log(metrics: dict[str, float], step: int) # log metrics at a step
119
+ run.save(filepath: str) -> dict # save an artifact file
120
+ run.finish() # mark finished
121
+ run.fail() # mark crashed
122
+ ```
123
+
124
+ ## Storage
125
+
126
+ Everything lives in `~/.runmonitor/`:
127
+ - `runs.db` — SQLite database (WAL mode, thread-safe)
128
+ - `artifacts/<run_id>/` — saved files per run
129
+
130
+ No database setup, no API keys, no cloud.
131
+
132
+ ## Requirements
133
+
134
+ - Python 3.10+
135
+ - Flask
136
+ - Chart.js (loaded from CDN in the dashboard)
137
+ - `psutil` (optional, for system metrics)
138
+
139
+ ## Publishing to PyPI (maintainers)
140
+
141
+ ```bash
142
+ python -m pip install --upgrade build twine
143
+ python -m build # → dist/runmonitor-<version>.tar.gz + .whl
144
+ python -m twine check dist/*
145
+ python -m twine upload dist/* # needs a PyPI account + API token
146
+ ```
147
+
148
+ Bump `version` in `pyproject.toml` before each release.
@@ -0,0 +1,97 @@
1
+ # runmonitor
2
+
3
+ Lean, local experiment tracker with a live, **terminal-styled** web dashboard. Import and go.
4
+
5
+ ```python
6
+ import runmonitor as rm
7
+
8
+ run = rm.init("my-experiment", config={"lr": 0.001, "batch_size": 32}, total_steps=1000)
9
+
10
+ for step in range(1000):
11
+ loss, acc = train_step()
12
+ run.log({"loss": loss, "accuracy": acc}, step)
13
+ if step % 100 == 0:
14
+ run.save("checkpoint.pt")
15
+
16
+ run.finish()
17
+ ```
18
+
19
+ Open `http://localhost:8080` — your loss curve is already live and drawing itself.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install runmonitor # core
25
+ pip install "runmonitor[system]" # + CPU/RAM tracking (psutil)
26
+ ```
27
+
28
+ Then in any training script:
29
+
30
+ ```python
31
+ import runmonitor as rm
32
+ ```
33
+
34
+ Or run the dashboard on its own (no training script needed):
35
+
36
+ ```bash
37
+ runmonitor # opens the dashboard in your browser
38
+ python -m runmonitor # equivalent, for a checkout/vendored copy
39
+ RUNMONITOR_PORT=9000 runmonitor # pick a port (set before launch)
40
+ ```
41
+
42
+ ## The dashboard
43
+
44
+ A single, evolving view in the empero black/purple palette:
45
+
46
+ | Element | What it shows |
47
+ |---|---|
48
+ | **Metric ticker** | Every logged metric as `key=value`. Click one to **select** it. |
49
+ | **Live hero curve** | The selected metric, drawn point-by-point and growing every step. |
50
+ | **Anomaly detection** | Rolling z-score flags spikes — vertical markers on the curve + a status line ("⚠ anomaly at step N" / "● all calm"). |
51
+ | **Run header** | Run id, current step, status pill, elapsed, steps/sec, ETA, progress bar. |
52
+ | **Streak / best badges** | 🔥 improving-streak and 🏆 personal-best on the selected metric (direction-aware). |
53
+ | **Compare** | Pick a second run to overlay on the selected metric. |
54
+ | **System pane** | CPU % and RAM % over time (needs `psutil`). |
55
+ | **Hyperparameters / Artifacts** | Config passed to `rm.init()` and any saved files. |
56
+ | **Export** | Download the full run as CSV or JSON. |
57
+ | **Theme** | Midnight (black/purple) by default; toggle to light bone-paper. |
58
+
59
+ ## API
60
+
61
+ ```python
62
+ # Start a run (creates the project if new)
63
+ run = rm.init(project: str, name: str | None = None,
64
+ config: dict | None = None,
65
+ total_steps: int | None = None) -> Run
66
+
67
+ run.log(metrics: dict[str, float], step: int) # log metrics at a step
68
+ run.save(filepath: str) -> dict # save an artifact file
69
+ run.finish() # mark finished
70
+ run.fail() # mark crashed
71
+ ```
72
+
73
+ ## Storage
74
+
75
+ Everything lives in `~/.runmonitor/`:
76
+ - `runs.db` — SQLite database (WAL mode, thread-safe)
77
+ - `artifacts/<run_id>/` — saved files per run
78
+
79
+ No database setup, no API keys, no cloud.
80
+
81
+ ## Requirements
82
+
83
+ - Python 3.10+
84
+ - Flask
85
+ - Chart.js (loaded from CDN in the dashboard)
86
+ - `psutil` (optional, for system metrics)
87
+
88
+ ## Publishing to PyPI (maintainers)
89
+
90
+ ```bash
91
+ python -m pip install --upgrade build twine
92
+ python -m build # → dist/runmonitor-<version>.tar.gz + .whl
93
+ python -m twine check dist/*
94
+ python -m twine upload dist/* # needs a PyPI account + API token
95
+ ```
96
+
97
+ Bump `version` in `pyproject.toml` before each release.
@@ -0,0 +1,125 @@
1
+ """
2
+ runmonitor — a lean, local experiment tracker with a live web dashboard.
3
+ Import and go. Dashboard auto-starts on port 8080.
4
+ """
5
+
6
+ import json
7
+ import uuid
8
+ import atexit
9
+ import threading
10
+ import time
11
+ from . import server # noqa: F401 — starts the daemon
12
+ from . import storage
13
+
14
+
15
+ class Run:
16
+ """A single experiment run. Created via rm.init()."""
17
+
18
+ def __init__(self, run_id: str, project_id: int, name: str | None,
19
+ config: dict, total_steps: int | None):
20
+ self._id = run_id
21
+ self._project_id = project_id
22
+ self._name = name
23
+ self._config = config
24
+ self._total_steps = total_steps
25
+ self._finished = False
26
+ self._sysmon_stop = threading.Event()
27
+ self._sysmon_thread = None
28
+ self._start_sysmon()
29
+ atexit.register(self._on_exit)
30
+
31
+ @property
32
+ def id(self) -> str:
33
+ return self._id
34
+
35
+ @property
36
+ def name(self) -> str | None:
37
+ return self._name
38
+
39
+ def log(self, metrics: dict, step: int) -> None:
40
+ """Log a dictionary of metric-name → float at a given step."""
41
+ if self._finished:
42
+ raise RuntimeError("Cannot log to a finished run.")
43
+ if not isinstance(metrics, dict):
44
+ raise TypeError("metrics must be a dict")
45
+ storage.log_metrics(self._id, metrics, step)
46
+
47
+ def save(self, filepath: str) -> dict:
48
+ """Save an artifact file alongside this run. Returns artifact info dict."""
49
+ return storage.save_artifact(self._id, filepath)
50
+
51
+ def finish(self) -> None:
52
+ """Mark the run as successfully finished."""
53
+ if self._finished:
54
+ return
55
+ self._stop_sysmon()
56
+ storage.finish_run(self._id, "finished")
57
+ self._finished = True
58
+
59
+ def fail(self) -> None:
60
+ """Mark the run as crashed."""
61
+ if self._finished:
62
+ return
63
+ self._stop_sysmon()
64
+ storage.finish_run(self._id, "crashed")
65
+ self._finished = True
66
+
67
+ # ── system metrics background thread ───────────────────
68
+
69
+ def _start_sysmon(self):
70
+ """Start a daemon thread that logs CPU/RAM every 10 seconds."""
71
+ def _collect():
72
+ try:
73
+ import psutil
74
+ has_psutil = True
75
+ except ImportError:
76
+ has_psutil = False
77
+
78
+ last_cpu_sample = None
79
+ step_counter = 0
80
+ while not self._sysmon_stop.wait(timeout=10):
81
+ if not has_psutil:
82
+ return
83
+ step_counter += 1
84
+ try:
85
+ cpu = psutil.cpu_percent(interval=0.1)
86
+ mem = psutil.virtual_memory().percent
87
+ storage.log_system_metrics(self._id, step_counter, cpu, mem)
88
+ except Exception:
89
+ pass
90
+
91
+ self._sysmon_thread = threading.Thread(
92
+ target=_collect, name="rm-sysmon", daemon=True
93
+ )
94
+ self._sysmon_thread.start()
95
+
96
+ def _stop_sysmon(self):
97
+ self._sysmon_stop.set()
98
+ if self._sysmon_thread:
99
+ self._sysmon_thread.join(timeout=2)
100
+
101
+ def _on_exit(self):
102
+ """Mark the run as crashed if it exits without finish()/fail()."""
103
+ if self._finished:
104
+ return
105
+ self._stop_sysmon()
106
+ storage.finish_run(self._id, "crashed")
107
+ self._finished = True
108
+
109
+
110
+ def init(project: str, name: str | None = None, config: dict | None = None,
111
+ total_steps: int | None = None) -> Run:
112
+ """
113
+ Create (or reuse) a project and start a new run.
114
+
115
+ import runmonitor as rm
116
+ run = rm.init("mnist-experiment", config={"lr": 0.001}, total_steps=1000)
117
+
118
+ Opens http://localhost:8080 for the live dashboard.
119
+ """
120
+ storage.init_db()
121
+ project_id = storage.create_project(project)
122
+ run_id = uuid.uuid4().hex[:12]
123
+ config_json = json.dumps(config or {})
124
+ storage.create_run(run_id, project_id, name, config_json, total_steps)
125
+ return Run(run_id, project_id, name, config or {}, total_steps)
@@ -0,0 +1,54 @@
1
+ """Run the dashboard standalone — no training script needed.
2
+
3
+ runmonitor # if pip-installed (console script)
4
+ python -m runmonitor # from a checkout / vendored copy
5
+ RUNMONITOR_PORT=9000 runmonitor # choose a port (set before launch)
6
+
7
+ Importing ``runmonitor`` already auto-starts the dashboard daemon, so this
8
+ just makes sure it's up, opens a browser, and keeps the process alive.
9
+ """
10
+ import argparse
11
+ import os
12
+ import threading
13
+ import webbrowser
14
+
15
+
16
+ def main() -> None:
17
+ parser = argparse.ArgumentParser(
18
+ prog="runmonitor",
19
+ description="Live, terminal-styled experiment-tracking dashboard.",
20
+ )
21
+ parser.add_argument(
22
+ "--port", type=int, default=None,
23
+ help="Preferred port (most reliable via RUNMONITOR_PORT before launch).",
24
+ )
25
+ parser.add_argument(
26
+ "--no-browser", action="store_true",
27
+ help="Do not open a browser window.",
28
+ )
29
+ args = parser.parse_args()
30
+ if args.port:
31
+ os.environ.setdefault("RUNMONITOR_PORT", str(args.port))
32
+
33
+ from .storage import init_db
34
+ from . import server
35
+
36
+ init_db()
37
+ server._start_server() # idempotent — reuses the daemon if already running
38
+ url = f"http://localhost:{server._port}"
39
+ print(f" runmonitor dashboard → {url}")
40
+ if args.port and args.port != server._port:
41
+ print(f" (port {args.port} was unavailable; bound {server._port} instead)")
42
+ if not args.no_browser:
43
+ try:
44
+ webbrowser.open(url)
45
+ except Exception:
46
+ pass
47
+ try:
48
+ threading.Event().wait() # block forever; Ctrl-C to quit
49
+ except KeyboardInterrupt:
50
+ print("\n bye.")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runmonitor"
7
+ version = "0.2.0"
8
+ description = "Lean, local experiment tracker with a live, terminal-styled web dashboard."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "bub4tz", email = "ezgroupnl@gmail.com" }]
13
+ keywords = [
14
+ "machine-learning", "experiment-tracking", "metrics", "dashboard",
15
+ "training", "monitoring", "mlops", "tensorboard", "wandb",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: Science/Research",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
29
+ "Topic :: System :: Monitoring",
30
+ ]
31
+ dependencies = ["flask>=2.0"]
32
+
33
+ [project.optional-dependencies]
34
+ system = ["psutil>=5.0"]
35
+
36
+ [project.scripts]
37
+ runmonitor = "runmonitor.__main__:main"
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/empero-org/runmonitor"
41
+ Repository = "https://github.com/empero-org/runmonitor"
42
+ Issues = "https://github.com/empero-org/runmonitor/issues"
43
+
44
+ [tool.setuptools]
45
+ packages = ["runmonitor"]
46
+ include-package-data = true
47
+
48
+ [tool.setuptools.package-dir]
49
+ runmonitor = "."
50
+
51
+ [tool.setuptools.package-data]
52
+ runmonitor = ["templates/*.html", "static/*.css"]
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: runmonitor
3
+ Version: 0.2.0
4
+ Summary: Lean, local experiment tracker with a live, terminal-styled web dashboard.
5
+ Author-email: bub4tz <ezgroupnl@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 k
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/empero-org/runmonitor
29
+ Project-URL: Repository, https://github.com/empero-org/runmonitor
30
+ Project-URL: Issues, https://github.com/empero-org/runmonitor/issues
31
+ Keywords: machine-learning,experiment-tracking,metrics,dashboard,training,monitoring,mlops,tensorboard,wandb
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
43
+ Classifier: Topic :: System :: Monitoring
44
+ Requires-Python: >=3.10
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Requires-Dist: flask>=2.0
48
+ Provides-Extra: system
49
+ Requires-Dist: psutil>=5.0; extra == "system"
50
+ Dynamic: license-file
51
+
52
+ # runmonitor
53
+
54
+ Lean, local experiment tracker with a live, **terminal-styled** web dashboard. Import and go.
55
+
56
+ ```python
57
+ import runmonitor as rm
58
+
59
+ run = rm.init("my-experiment", config={"lr": 0.001, "batch_size": 32}, total_steps=1000)
60
+
61
+ for step in range(1000):
62
+ loss, acc = train_step()
63
+ run.log({"loss": loss, "accuracy": acc}, step)
64
+ if step % 100 == 0:
65
+ run.save("checkpoint.pt")
66
+
67
+ run.finish()
68
+ ```
69
+
70
+ Open `http://localhost:8080` — your loss curve is already live and drawing itself.
71
+
72
+ ## Install
73
+
74
+ ```bash
75
+ pip install runmonitor # core
76
+ pip install "runmonitor[system]" # + CPU/RAM tracking (psutil)
77
+ ```
78
+
79
+ Then in any training script:
80
+
81
+ ```python
82
+ import runmonitor as rm
83
+ ```
84
+
85
+ Or run the dashboard on its own (no training script needed):
86
+
87
+ ```bash
88
+ runmonitor # opens the dashboard in your browser
89
+ python -m runmonitor # equivalent, for a checkout/vendored copy
90
+ RUNMONITOR_PORT=9000 runmonitor # pick a port (set before launch)
91
+ ```
92
+
93
+ ## The dashboard
94
+
95
+ A single, evolving view in the empero black/purple palette:
96
+
97
+ | Element | What it shows |
98
+ |---|---|
99
+ | **Metric ticker** | Every logged metric as `key=value`. Click one to **select** it. |
100
+ | **Live hero curve** | The selected metric, drawn point-by-point and growing every step. |
101
+ | **Anomaly detection** | Rolling z-score flags spikes — vertical markers on the curve + a status line ("⚠ anomaly at step N" / "● all calm"). |
102
+ | **Run header** | Run id, current step, status pill, elapsed, steps/sec, ETA, progress bar. |
103
+ | **Streak / best badges** | 🔥 improving-streak and 🏆 personal-best on the selected metric (direction-aware). |
104
+ | **Compare** | Pick a second run to overlay on the selected metric. |
105
+ | **System pane** | CPU % and RAM % over time (needs `psutil`). |
106
+ | **Hyperparameters / Artifacts** | Config passed to `rm.init()` and any saved files. |
107
+ | **Export** | Download the full run as CSV or JSON. |
108
+ | **Theme** | Midnight (black/purple) by default; toggle to light bone-paper. |
109
+
110
+ ## API
111
+
112
+ ```python
113
+ # Start a run (creates the project if new)
114
+ run = rm.init(project: str, name: str | None = None,
115
+ config: dict | None = None,
116
+ total_steps: int | None = None) -> Run
117
+
118
+ run.log(metrics: dict[str, float], step: int) # log metrics at a step
119
+ run.save(filepath: str) -> dict # save an artifact file
120
+ run.finish() # mark finished
121
+ run.fail() # mark crashed
122
+ ```
123
+
124
+ ## Storage
125
+
126
+ Everything lives in `~/.runmonitor/`:
127
+ - `runs.db` — SQLite database (WAL mode, thread-safe)
128
+ - `artifacts/<run_id>/` — saved files per run
129
+
130
+ No database setup, no API keys, no cloud.
131
+
132
+ ## Requirements
133
+
134
+ - Python 3.10+
135
+ - Flask
136
+ - Chart.js (loaded from CDN in the dashboard)
137
+ - `psutil` (optional, for system metrics)
138
+
139
+ ## Publishing to PyPI (maintainers)
140
+
141
+ ```bash
142
+ python -m pip install --upgrade build twine
143
+ python -m build # → dist/runmonitor-<version>.tar.gz + .whl
144
+ python -m twine check dist/*
145
+ python -m twine upload dist/* # needs a PyPI account + API token
146
+ ```
147
+
148
+ Bump `version` in `pyproject.toml` before each release.
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ __init__.py
5
+ __main__.py
6
+ pyproject.toml
7
+ server.py
8
+ storage.py
9
+ ./__init__.py
10
+ ./__main__.py
11
+ ./server.py
12
+ ./storage.py
13
+ ./static/style.css
14
+ ./templates/dashboard.html
15
+ runmonitor.egg-info/PKG-INFO
16
+ runmonitor.egg-info/SOURCES.txt
17
+ runmonitor.egg-info/dependency_links.txt
18
+ runmonitor.egg-info/entry_points.txt
19
+ runmonitor.egg-info/requires.txt
20
+ runmonitor.egg-info/top_level.txt
21
+ static/style.css
22
+ templates/dashboard.html
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ runmonitor = runmonitor.__main__:main
@@ -0,0 +1,4 @@
1
+ flask>=2.0
2
+
3
+ [system]
4
+ psutil>=5.0