From b2d071f404944d9db4c67a93a3debecfb4e2b09f Mon Sep 17 00:00:00 2001 From: anna-follestad-4ss Date: Tue, 18 Aug 2026 10:52:03 +0200 Subject: [PATCH 1/2] include memory logger --- .env.example | 10 ++++++++++ README.md | 6 ++++++ requirements-dev.txt | 1 + src/app.py | 6 +++++- src/memory_log.py | 23 +++++++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/memory_log.py diff --git a/.env.example b/.env.example index 4962895..dc2fe20 100644 --- a/.env.example +++ b/.env.example @@ -26,3 +26,13 @@ DASH_DEBUG=true MOCK_PLATFORM_HEADER=82 MOCK_PLATFORM_SPACER=20 + + +# --------------------------------------------------------------------------- +# Development aid: print memory usage to the terminal +# --------------------------------------------------------------------------- +# Prints the app's RSS memory usage every time you click a page link. +# +# Off by default. Needs psutil, which is only in requirements-dev.txt, so +# never turn this on anywhere requirements.txt alone was installed. +LOG_MEMORY=false diff --git a/README.md b/README.md index 7bf68eb..6a2119e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,12 @@ properties on the page root, and `.shell` pads itself by those same variables — that's what actually reserves the space so page content doesn't render underneath the bars. +## Memory logging + +Set `LOG_MEMORY=true` in `.env` to print the app's RSS memory usage to the +terminal every time you click a page link. It's a dev aid only, off by +default. + ## Styling / theming Colours, fonts and spacing live in two places that must be kept in step: diff --git a/requirements-dev.txt b/requirements-dev.txt index 3568291..8627e39 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,4 @@ pytest>=8.0 black>=24.0 pre-commit>=3.7 +psutil>=5.9 diff --git a/src/app.py b/src/app.py index 7662bf1..bc58a44 100644 --- a/src/app.py +++ b/src/app.py @@ -14,6 +14,7 @@ from dash import Dash, Input, Output, callback, dcc, html from dotenv import load_dotenv +import memory_log import theme # registers the "4subsea" Plotly template PAGE_TITLE = "Dashboard Template" @@ -25,6 +26,7 @@ DASH_DEBUG = os.getenv("DASH_DEBUG", "true").strip().lower() == "true" MOCK_PLATFORM_HEADER = int(os.getenv("MOCK_PLATFORM_HEADER", "0") or 0) MOCK_PLATFORM_SPACER = int(os.getenv("MOCK_PLATFORM_SPACER", "20") or 20) +LOG_MEMORY = os.getenv("LOG_MEMORY", "false").strip().lower() == "true" app = Dash( __name__, @@ -126,9 +128,11 @@ def mock_4insight(): @callback(Output("nav-bar", "children"), Input("url", "pathname")) def highlight_active_tab(pathname): """Function to highlight the active tab in the sidebar based on the current URL path.""" + if LOG_MEMORY: + memory_log.log_once(pathname) return nav_bar(pathname) if __name__ == "__main__": # Run the app. Set DASH_DEBUG in .env to toggle debug mode. - app.run(debug=DASH_DEBUG) + app.run(debug=DASH_DEBUG, port=1234) diff --git a/src/memory_log.py b/src/memory_log.py new file mode 100644 index 0000000..be9d4a7 --- /dev/null +++ b/src/memory_log.py @@ -0,0 +1,23 @@ +"""Prints RSS memory usage to the terminal on demand. + +Opt-in dev aid - see LOG_MEMORY in app.py and .env.example. Nothing here +writes to a file or feeds a log collector; it is only for watching a local +`python src/app.py` session by eye. +""" + +_process = None + + +def _rss_mb(): + global _process + import psutil + + if _process is None: + _process = psutil.Process() + return _process.memory_info().rss / (1024 * 1024) + + +def log_once(note=""): + """Print one line right now. `note` names what triggered it, e.g. a path.""" + suffix = f" ({note})" if note else "" + print(f"[memory] {_rss_mb():.1f} MB RSS{suffix}") From b42537bb33002cff3250159d9e8d484922525676 Mon Sep 17 00:00:00 2001 From: anna-follestad-4ss Date: Tue, 18 Aug 2026 11:03:04 +0200 Subject: [PATCH 2/2] add tests --- src/app.py | 2 +- tests/test_app.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app.py b/src/app.py index bc58a44..26b472e 100644 --- a/src/app.py +++ b/src/app.py @@ -135,4 +135,4 @@ def highlight_active_tab(pathname): if __name__ == "__main__": # Run the app. Set DASH_DEBUG in .env to toggle debug mode. - app.run(debug=DASH_DEBUG, port=1234) + app.run(debug=DASH_DEBUG) diff --git a/tests/test_app.py b/tests/test_app.py index 3772939..8dd30a8 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -150,3 +150,24 @@ def test_the_mock_header_carries_the_logo_and_placeholder_title_when_configured( assert bar.style["height"] == "82px" assert bottom_bar.style["height"] == "20px" assert text_of(bottom_bar.children) == "simulated spacer — not part of the app" + + +# --------------------------------------------------------------------------- +# Memory logging +# --------------------------------------------------------------------------- + + +def test_navigation_logs_memory_when_enabled(monkeypatch): + monkeypatch.setattr(app, "LOG_MEMORY", True) + calls = [] + monkeypatch.setattr(app.memory_log, "log_once", lambda note="": calls.append(note)) + app.highlight_active_tab("/analytics") + assert calls == ["/analytics"] + + +def test_navigation_does_not_log_memory_when_disabled(monkeypatch): + monkeypatch.setattr(app, "LOG_MEMORY", False) + calls = [] + monkeypatch.setattr(app.memory_log, "log_once", lambda note="": calls.append(note)) + app.highlight_active_tab("/analytics") + assert calls == []