Skip to content

Commit 1f664a6

Browse files
erosselliKelsey-Ethyca
authored andcommitted
Add API to run memory heap dump (#6973)
1 parent 54fb637 commit 1f664a6

File tree

6 files changed

+135
-2
lines changed

6 files changed

+135
-2
lines changed

src/fides/api/api/v1/endpoints/admin.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from fides.api.db.database import configure_db, migrate_db, reset_db
99
from fides.api.oauth.utils import verify_oauth_client_prod
1010
from fides.api.util.api_router import APIRouter
11+
from fides.api.util.memory_watchdog import (
12+
_capture_heap_dump,
13+
get_memory_watchdog_enabled,
14+
)
1115
from fides.common.api import scope_registry
16+
from fides.common.api.scope_registry import HEAP_DUMP_EXEC
1217
from fides.config import CONFIG
1318

1419
ADMIN_ROUTER = APIRouter(prefix=API_PREFIX, tags=["Admin"])
@@ -82,3 +87,37 @@ def db_action(action: DBActions, revision: Optional[str] = "head") -> Dict:
8287
"message": f"Fides database action performed successfully: {action_text}"
8388
}
8489
}
90+
91+
92+
@ADMIN_ROUTER.post(
93+
"/admin/heap-dump",
94+
tags=["Admin"],
95+
dependencies=[Security(verify_oauth_client_prod, scopes=[HEAP_DUMP_EXEC])],
96+
status_code=status.HTTP_200_OK,
97+
)
98+
def trigger_heap_dump() -> Dict:
99+
"""
100+
Trigger a heap dump for memory diagnostics.
101+
102+
Captures and logs detailed memory profiling information including:
103+
- Process memory stats (RSS, VMS)
104+
- Top object type counts
105+
- Garbage collector stats
106+
- Uncollectable objects (memory leaks)
107+
108+
The full heap dump report is logged to error logs.
109+
"""
110+
if not get_memory_watchdog_enabled():
111+
raise HTTPException(
112+
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
113+
detail="Heap dump functionality is not enabled. Set memory_watchdog_enabled to true in application configuration.",
114+
)
115+
116+
logger.warning("Manual heap dump triggered via API")
117+
_capture_heap_dump()
118+
119+
return {
120+
"data": {
121+
"message": "Heap dump captured successfully. Check server logs for detailed report."
122+
}
123+
}

src/fides/api/oauth/roles.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DATA_USE_READ,
1717
DATASET_READ,
1818
EVALUATION_READ,
19+
HEAP_DUMP_EXEC,
1920
MASKING_EXEC,
2021
MASKING_READ,
2122
MESSAGING_CREATE_OR_UPDATE,
@@ -145,6 +146,7 @@ class RoleRegistryEnum(Enum):
145146
PRIVACY_REQUEST_NOTIFICATIONS_CREATE_OR_UPDATE,
146147
PRIVACY_REQUEST_EMAIL_INTEGRATIONS_SEND,
147148
USER_PERMISSION_ASSIGN_OWNERS,
149+
HEAP_DUMP_EXEC,
148150
]
149151

150152
ROLES_TO_SCOPES_MAPPING: Dict[str, List] = {

src/fides/api/util/memory_watchdog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _capture_heap_dump() -> None:
8080
report_lines = [
8181
"", # Leading newline for visual separation
8282
"=" * 80,
83-
"MEMORY DUMP - THRESHOLD EXCEEDED",
83+
"MEMORY DUMP",
8484
"=" * 80,
8585
"",
8686
]

src/fides/common/api/scope_registry.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
VIEW_DATA = "view_data"
7777
WEBHOOK = "webhook"
7878
WORKER_STATS = "worker-stats"
79+
HEAP_DUMP = "heap_dump"
7980

8081
ASSIGN_OWNERS = "assign_owners"
8182

@@ -146,6 +147,8 @@
146147

147148
ENCRYPTION_EXEC = f"{ENCRYPTION}:{EXEC}"
148149

150+
HEAP_DUMP_EXEC = f"{HEAP_DUMP}:{EXEC}"
151+
149152
EVALUATION_CREATE = f"{EVALUATION}:{CREATE}"
150153
EVALUATION_READ = f"{EVALUATION}:{READ}"
151154
EVALUATION_UPDATE = f"{EVALUATION}:{UPDATE}"
@@ -295,6 +298,7 @@
295298
DATASET_READ: "View datasets",
296299
DATASET_TEST: "Run a standalone privacy request test for a dataset",
297300
ENCRYPTION_EXEC: "Encrypt data",
301+
HEAP_DUMP_EXEC: "Execute a heap dump for memory diagnostics",
298302
MESSAGING_TEMPLATE_UPDATE: "Update messaging templates",
299303
EVALUATION_CREATE: "Create evaluation",
300304
EVALUATION_READ: "Read evaluations",

tests/ctl/api/test_admin.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,36 @@
22
import pytest
33
from starlette.testclient import TestClient
44

5+
from fides.api.models.application_config import ApplicationConfig
56
from fides.api.util.endpoint_utils import API_PREFIX
67
from fides.config import FidesConfig
78

89

10+
@pytest.fixture(scope="function")
11+
def memory_watchdog_enabled(db):
12+
"""Fixture to enable memory watchdog for tests."""
13+
ApplicationConfig.create_or_update(
14+
db,
15+
data={"api_set": {"execution": {"memory_watchdog_enabled": True}}},
16+
)
17+
yield
18+
ApplicationConfig.create_or_update(
19+
db,
20+
data={"api_set": {"execution": {"memory_watchdog_enabled": False}}},
21+
)
22+
23+
24+
@pytest.fixture(scope="function")
25+
def memory_watchdog_disabled(db):
26+
"""Fixture to explicitly disable memory watchdog for tests."""
27+
ApplicationConfig.create_or_update(
28+
db,
29+
data={"api_set": {"execution": {"memory_watchdog_enabled": False}}},
30+
)
31+
yield
32+
# Reset is handled by default behavior (memory_watchdog_enabled defaults to False)
33+
34+
935
def test_db_reset_dev_mode_enabled(
1036
test_config: FidesConfig,
1137
test_client: TestClient,
@@ -36,3 +62,65 @@ def test_db_reset_dev_mode_disabled(
3662

3763
assert response.status_code == 501
3864
assert response.json()["detail"] == error_message
65+
66+
67+
def test_heap_dump_when_disabled(
68+
test_config: FidesConfig,
69+
test_client: TestClient,
70+
) -> None:
71+
"""Test that heap dump returns 405 when memory_watchdog_enabled is False (default)."""
72+
response = test_client.post(
73+
test_config.cli.server_url + API_PREFIX + "/admin/heap-dump/",
74+
headers=test_config.user.auth_header,
75+
)
76+
77+
assert response.status_code == 405
78+
assert response.json()["detail"] == (
79+
"Heap dump functionality is not enabled. "
80+
"Set memory_watchdog_enabled to true in application configuration."
81+
)
82+
83+
84+
@pytest.mark.usefixtures("memory_watchdog_disabled")
85+
def test_heap_dump_explicitly_disabled(
86+
test_config: FidesConfig,
87+
test_client: TestClient,
88+
) -> None:
89+
"""Test that heap dump returns 405 when memory_watchdog_enabled is explicitly set to False."""
90+
response = test_client.post(
91+
test_config.cli.server_url + API_PREFIX + "/admin/heap-dump/",
92+
headers=test_config.user.auth_header,
93+
)
94+
95+
assert response.status_code == 405
96+
assert response.json()["detail"] == (
97+
"Heap dump functionality is not enabled. "
98+
"Set memory_watchdog_enabled to true in application configuration."
99+
)
100+
101+
102+
@pytest.mark.usefixtures("memory_watchdog_enabled")
103+
def test_heap_dump_logs_heap_stats(
104+
test_config: FidesConfig,
105+
test_client: TestClient,
106+
loguru_caplog,
107+
) -> None:
108+
"""Test that heap dump endpoint logs heap statistics."""
109+
response = test_client.post(
110+
test_config.cli.server_url + API_PREFIX + "/admin/heap-dump/",
111+
headers=test_config.user.auth_header,
112+
)
113+
114+
assert response.status_code == 200
115+
116+
# Verify that the heap dump was logged
117+
log_output = loguru_caplog.text
118+
119+
# Check for the info message that heap dump was triggered
120+
assert "Manual heap dump triggered via API" in log_output
121+
122+
# Check for key sections of the heap dump in logs
123+
assert "MEMORY DUMP" in log_output
124+
assert "PROCESS MEMORY STATS" in log_output
125+
assert "OBJECT TYPE COUNTS (Top 10)" in log_output
126+
assert "GARBAGE COLLECTOR STATS" in log_output

tests/ops/util/test_memory_watchdog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ def test_capture_heap_dump_single_log_message(
481481
logged_message = str(mock_logger.error.call_args[0][0])
482482

483483
# Verify all expected sections are in the single message
484-
assert "MEMORY DUMP - THRESHOLD EXCEEDED" in logged_message
484+
assert "MEMORY DUMP" in logged_message
485485
assert "PROCESS MEMORY STATS" in logged_message
486486
assert "RSS (Resident Set Size)" in logged_message
487487
assert "512.0 MiB" in logged_message

0 commit comments

Comments
 (0)