Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
pytest>=8.0
black>=24.0
pre-commit>=3.7
psutil>=5.9
4 changes: 4 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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__,
Expand Down Expand Up @@ -126,6 +128,8 @@ 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)
Comment thread
anna-follestad-4ss marked this conversation as resolved.


Expand Down
23 changes: 23 additions & 0 deletions src/memory_log.py
Original file line number Diff line number Diff line change
@@ -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}")
Comment thread
anna-follestad-4ss marked this conversation as resolved.
21 changes: 21 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 == []
Loading