Skip to content

Commit 7446f88

Browse files
committed
Feature: extend health endpoint metrics
- add resident memory reporting to /health - document the health endpoint response schema - update Kubernetes health example payload - add pytest coverage for memory response fields - 2026-03-18 23:35:19
1 parent e69b093 commit 7446f88

5 files changed

Lines changed: 107 additions & 1 deletion

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Health
2+
3+
Returns lightweight service health and runtime sizing details for the running
4+
PyPNM API instance.
5+
6+
**GET** `/health`
7+
8+
## Purpose
9+
10+
Use this endpoint for:
11+
12+
- readiness and liveness checks
13+
- verifying the running package version
14+
- checking process uptime
15+
- checking current resident memory usage
16+
- monitoring `.data` growth by first-level directory
17+
18+
## Response Example
19+
20+
```json
21+
{
22+
"status": "ok",
23+
"service": {
24+
"name": "pypnm-docsis",
25+
"version": "1.4.3.0"
26+
},
27+
"uptime": {
28+
"starttime": 1773640097,
29+
"uptime": 65
30+
},
31+
"memory": {
32+
"rss_bytes": 12582912
33+
},
34+
"data": {
35+
"path": ".data",
36+
"size_bytes": 1761579619,
37+
"directories": {
38+
"json": 1728244816,
39+
"xlsx": 0,
40+
"pnm": 17349665,
41+
"csv": 2819493,
42+
"png": 2805111,
43+
"db": 3388387,
44+
"archive": 6968882,
45+
"msg_rsp": 3265
46+
}
47+
}
48+
}
49+
```
50+
51+
## Response Fields
52+
53+
| Field | Type | Description |
54+
| --- | --- | --- |
55+
| `status` | string | Top-level health status. Expected value is `ok` when the API is ready. |
56+
| `service.name` | string | Package/service name loaded from `pyproject.toml`. |
57+
| `service.version` | string | Running PyPNM version. |
58+
| `uptime.starttime` | integer | Service start time as Unix epoch seconds. |
59+
| `uptime.uptime` | integer | Elapsed uptime in whole seconds since `starttime`. |
60+
| `memory.rss_bytes` | integer | Current resident memory used by the running PyPNM process, in bytes. |
61+
| `data.path` | string | Runtime data root path. |
62+
| `data.size_bytes` | integer | Recursive apparent size of the `.data` directory in bytes. |
63+
| `data.directories` | object | Recursive apparent sizes for each first-level directory under `.data`. |
64+
65+
## Notes
66+
67+
- `memory.rss_bytes` is based on Linux resident set size (`VmRSS`).
68+
- `data.size_bytes` is logical file size, not filesystem block allocation.
69+
- `data.directories` helps identify which artifact classes are consuming disk.
70+
- This endpoint is intended to stay lightweight and local to the running service.

docs/api/fast-api/pypnm/system/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
| Reference | Description |
44
|------------|-------------|
5+
| [Health](health.md) | Service health, uptime, memory, and `.data` sizing |
56
| [Log](download-log.md) | PyPNM Log |
67
| [WebService](reload-web-service.md) | PyPNM Web Service |

docs/kubernetes/kind-freelens.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,15 @@ Example response:
4545
"status": "ok",
4646
"service": {
4747
"name": "pypnm-docsis",
48-
"version": "1.4.2.0"
48+
"version": "1.4.3.0"
4949
},
5050
"uptime": {
5151
"starttime": 1773640097,
5252
"uptime": 1
5353
},
54+
"memory": {
55+
"rss_bytes": 12582912
56+
},
5457
"data": {
5558
"path": ".data",
5659
"size_bytes": 1761579619,
@@ -68,6 +71,8 @@ Example response:
6871
}
6972
```
7073

74+
`memory.rss_bytes` reports the current resident memory used by the running PyPNM process in bytes.
75+
7176
If the returned `service.version` is older than expected, verify the tag used in the deploy command and confirm the namespace matches the running deployment.
7277

7378
## Connect with FreeLens

src/pypnm/api/main.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,15 @@ class HealthDataModel(BaseModel):
8888
directories: dict[str, int] = Field(default_factory=dict, description="Recursive apparent sizes for first-level directories under the data root.")
8989

9090

91+
class HealthMemoryModel(BaseModel):
92+
rss_bytes: int = Field(..., description="Resident memory usage for the running PyPNM process in bytes.")
93+
94+
9195
class HealthResponseModel(BaseModel):
9296
status: str = Field(..., description="Top-level health status for readiness probes.")
9397
service: HealthServiceModel = Field(..., description="Service identity and version metadata.")
9498
uptime: HealthUptimeModel = Field(..., description="Service process start time and uptime.")
99+
memory: HealthMemoryModel = Field(..., description="Process memory usage for the running PyPNM service.")
95100
data: HealthDataModel = Field(..., description="Runtime data directory sizing details.")
96101

97102

@@ -167,6 +172,26 @@ def _first_level_directory_sizes(folder_path: pathlib.Path) -> dict[str, int]:
167172
return sizes
168173

169174

175+
def _process_rss_bytes() -> int:
176+
"""Return resident memory for the current process in bytes using Linux procfs."""
177+
status_path = pathlib.Path("/proc/self/status")
178+
if not status_path.is_file():
179+
return 0
180+
181+
try:
182+
for line in status_path.read_text(encoding="utf-8").splitlines():
183+
if not line.startswith("VmRSS:"):
184+
continue
185+
parts = line.split()
186+
if len(parts) < 2:
187+
return 0
188+
return int(parts[1]) * 1024
189+
except (OSError, ValueError):
190+
return 0
191+
192+
return 0
193+
194+
170195
def _apply_muted_tag_policy(app_instance: FastAPI) -> None:
171196
_hard_muted_routes.clear()
172197
muted_tags = read_env_csv_set(ENV_MUTE_TAGS)
@@ -193,6 +218,7 @@ def health() -> HealthResponseModel:
193218
status="ok",
194219
service=HealthServiceModel(name=SERVICE_NAME, version=__version__),
195220
uptime=HealthUptimeModel(starttime=API_START_EPOCH, uptime=uptime_seconds),
221+
memory=HealthMemoryModel(rss_bytes=_process_rss_bytes()),
196222
data=HealthDataModel(
197223
path=str(data_root),
198224
size_bytes=_folder_size_bytes(data_root),

tests/test_api_health.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def test_health_includes_uptime(monkeypatch) -> None:
1414
monkeypatch.setattr(api_main, "API_START_EPOCH", 1_741_709_200)
1515
monkeypatch.setattr(api_main, "SERVICE_NAME", "pypnm-docsis")
1616
monkeypatch.setattr(api_main, "_data_root_path", lambda: Path(".data"))
17+
monkeypatch.setattr(api_main, "_process_rss_bytes", lambda: 12_582_912)
1718
monkeypatch.setattr(api_main, "_folder_size_bytes", lambda _: 4096)
1819
monkeypatch.setattr(api_main, "_first_level_directory_sizes", lambda _: {"pnm": 1024, "json": 2048})
1920
monkeypatch.setattr(api_main, "monotonic", lambda: 165.9)
@@ -30,6 +31,9 @@ def test_health_includes_uptime(monkeypatch) -> None:
3031
"starttime": 1_741_709_200,
3132
"uptime": 65,
3233
},
34+
"memory": {
35+
"rss_bytes": 12_582_912,
36+
},
3337
"data": {
3438
"path": ".data",
3539
"size_bytes": 4096,

0 commit comments

Comments
 (0)