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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ Thumbs.db
.vscode/
*.swp
*.swo

ignore/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

- Changed default `gua` state paths to `~/.gua/gua.db`, `~/.gua/gua.pid`,
and `~/.gua/gua.log`; the default database now acts as an appendable local
history database.
- Record daemon run intervals in SQLite and attach samples to a run, so
`gua report` uses recorded intervals by default. `--interval` is now an
override and a fallback for legacy rows without interval metadata.

## 1.0.2 - 2026-05-15

- Hardened `gua status` and `gua stop` so stale PID files do not act on
Expand Down
59 changes: 31 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ uv tool install gpu-usage-audit
gua doctor
gua daemon --interval 30s
gua status
gua report --since 1h --interval 30s
gua report --since 1h
gua stop
```

`gua doctor` is intentionally read-only. It checks only the current
machine: OS/kernel/Python, `/dev/nvidia*`, `nvidia-smi -L`, NVML
load/init/device count/driver version, and the database path the daemon
would write to. The default is `/tmp/gua.db`; pass `gua doctor --db PATH`
would write to. The default is `~/.gua/gua.db`; pass `gua doctor --db PATH`
when you plan to use a different daemon database.

Use `gua doctor --json` for the same report in a machine-readable form.
Expand Down Expand Up @@ -77,7 +77,7 @@ uvx --from "./$WHEEL" gua doctor
## What you get

```
$ gua report --since 1h --interval 30s
$ gua report --since 1h
gua — lab-a100 (bare, driver 560.35.05) Window: 1:00:00

§1 Headline
Expand All @@ -90,7 +90,7 @@ gua — lab-a100 (bare, driver 560.35.05) Window: 1:00:00
(51 samples)

§2 Idle capacity
converted from card-ticks to GPU-hours using the report --interval
converted from card-ticks to GPU-hours using recorded daemon interval
idle-held: ~0.31 GPU-hours, ~1.53 GPUs equivalently unavailable
truly-idle: ~0.12 GPU-hours, ~1.00 GPUs equivalently free

Expand All @@ -116,9 +116,9 @@ The 3-bar collapses every card × every tick over the window into the
active / idle-held / truly-idle split. **`idle-held` rows are the
embarrassing category**: a process is holding GPU memory but the SM
utilization is below 10%. §2 converts those card-ticks into GPU-hours
with `--interval`; §4 groups process rows by identity, GPU, and tick
before ranking users, so multiple same-user processes on one GPU/tick
count once.
using the interval recorded by the daemon; §4 groups process rows by
identity, GPU, and tick before ranking users, so multiple same-user
processes on one GPU/tick count once.

## Demo (no GPU required)

Expand All @@ -142,7 +142,7 @@ gua doctor
```

Doctor should show the current machine, visible `/dev/nvidia*` device
files, `nvidia-smi -L` GPUs, NVML device count, and `/tmp/gua.db` status.
files, `nvidia-smi -L` GPUs, NVML device count, and `~/.gua/gua.db` status.
`nvidia-ml-py` is installed by default with `gpu-usage-audit`; if doctor
reports that `pynvml` is not importable, reinstall the isolated tool
environment:
Expand All @@ -166,7 +166,7 @@ gua status
Run the report:

```sh
gua report --since 1h --interval 30s
gua report --since 1h
```

Stop the background collector when the collection window is done:
Expand All @@ -175,12 +175,12 @@ Stop the background collector when the collection window is done:
gua stop
```

If `--db` is omitted, both `daemon` and `report` use `/tmp/gua.db`.
`daemon` refuses to start when that database file already exists, so a
new collection run does not silently append to an old test database. If
`gua doctor` reports that the database already exists, either run
`gua report` against the existing data or choose a fresh `--db PATH` for
the next daemon run.
If `--db` is omitted, `daemon`, `report`, `status`, and `stop` use
`~/.gua/` for local user state: `gua.db`, `gua.pid`, and `gua.log`. The
default database is a local history database, so later daemon runs append
to it and reports read the accumulated data. If you pass a custom
`--db PATH`, `daemon` still refuses an existing file to avoid silently
appending to an ad hoc collection run.

> The daemon requires the NVIDIA driver and `libnvidia-ml.so.1`. On a
> driverless host it exits with a friendly NVML initialization error. For
Expand All @@ -193,7 +193,7 @@ point remains installed for compatibility, but new examples use `gua`.

| Command | What it does |
| -------- | ----------------------------------------------------------- |
| `daemon` | Starts the collector in the background. Samples real NVML telemetry on every tick and writes to a new database. NVIDIA host required. |
| `daemon` | Starts the collector in the background. Samples real NVML telemetry on every tick and writes to the local history database. NVIDIA host required. |
| `start` | Alias for `gua daemon`. |
| `status` | Shows whether the background collector PID is still running. Also clears a stale PID file when it points to a missing or unrelated process. |
| `stop` | Stops the background collector with SIGTERM. |
Expand All @@ -208,13 +208,14 @@ gua start [--db PATH] [--interval D] [--pid-file PATH] [--log-file PATH]
gua daemon --foreground [--db PATH] [--interval D]
```

- `--db PATH` (default `/tmp/gua.db`) — SQLite file to create and write
to. The daemon exits with an error if the file already exists. WAL mode
is enabled automatically.
- `--db PATH` (default `~/.gua/gua.db`) — SQLite history database. The
default path is appended across daemon runs; a custom path still exits
with an error if the file already exists. WAL mode is enabled
automatically.
- `--interval D` (default `30s`) — how often to sample. Accepts `30s`,
`1m`, `200ms`, etc.
- `--pid-file PATH` (default `/tmp/gua.pid`) — background PID file.
- `--log-file PATH` (default `/tmp/gua.log`) — stdout/stderr from the
- `--pid-file PATH` (default `~/.gua/gua.pid`) — background PID file.
- `--log-file PATH` (default `~/.gua/gua.log`) — stdout/stderr from the
background collector.
- `--foreground` — keep the collector attached to the current process.
Use this for systemd or debugging.
Expand All @@ -232,15 +233,15 @@ managed collector before acting on it; stale PID files are cleared.
gua report [--db PATH] [--since D] [--interval D] [--width N]
```

- `--db PATH` (default `/tmp/gua.db`) — same SQLite file the daemon writes
to. The report exits with an error if the file does not exist.
- `--db PATH` (default `~/.gua/gua.db`) — same SQLite file the daemon
writes to. The report exits with an error if the file does not exist.
- `--since D` (default `1h`) — the report window. **No upper bound** —
`--since 365d` is accepted. The effective window is min(`--since`, age
of oldest sample), so passing a huge `--since` is the same as "all
data". Units: `ms`, `s`, `m`, `h`, `d` (no `w`; use `7d`).
- `--interval D` (default `30s`) — **must match what the daemon used**.
This is how §2 (Idle capacity) and §4 (Top identities) convert tick counts
to GPU-hours. Mismatched intervals → wrong GPU-hours.
- `--interval D` — optional override for §2 (Idle capacity) and §4 (Top
identities). By default, reports use the interval recorded by each daemon
run. Legacy rows without interval metadata fall back to 30s.
- `--width N` (default `60`) — width of the §1 three-bar in characters.

### `demo`
Expand All @@ -257,8 +258,10 @@ gua demo [--db PATH] [--ticks N] [--interval D]

### Operational notes

- **Same `--interval` on both sides.** If you ran the daemon with
`--interval 30s`, run `gua report --interval 30s` too.
- **Intervals are recorded.** New daemon runs store their sampling interval
in the database, so `gua report` can compute GPU-hours without repeating
`--interval`. Use report `--interval D` only to override or to interpret
legacy rows.
- **Let it run for a while.** §1/§3 are meaningful after one tick;
§4 (Top identities) needs hours; §5 (Heatmap) needs days.
- **WAL leaves sidecar files** (`gua.db-wal`, `gua.db-shm`). They are
Expand Down
42 changes: 22 additions & 20 deletions projects/bare-metal-1.0/handoff.ko.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Bare Metal 1.0 Handoff

갱신일: 2026-05-15
갱신일: 2026-05-27

## 이어받을 때 먼저 볼 것

- `projects/bare-metal-1.0/status.ko.md`: 현재 완료 상태, 1.0.1 검증 결과, 1.0.2 release prep 상태.
- `projects/bare-metal-1.0/status.ko.md`: 현재 완료 상태, 1.0.2 release/publish 상태, 최신 로컬 검증 결과.
- `README.md`: 실제 사용자 문서와 release/install/runbook/report 표면.
- `src/gpu_usage_audit/__main__.py`: `gua` CLI, background daemon lifecycle, PID handling.
- `src/gpu_usage_audit/report.py`: report SQL 집계.
Expand All @@ -17,14 +17,14 @@
- Kubernetes, Slurm, Docker/Podman fallback, remote node, cluster-wide report는 1.0 범위 밖이다.
- `nvidia-ml-py`는 기본 dependency다.
- `gpu-usage-audit[nvml]` extra는 compatibility를 위해 빈 alias로 남긴다.
- DB schema는 v1을 유지한다: `host`, `gpu_sample`, `proc_sample`.
- 기본 DB는 `/tmp/gua.db`다.
- DB schema는 additive v1을 유지한다: `host`, `daemon_run`, `gpu_sample`, `proc_sample`.
- 기본 상태 경로는 `~/.gua/`이고, 기본 DB/PID/log는 `~/.gua/gua.db`, `~/.gua/gua.pid`, `~/.gua/gua.log`다.
- `gua daemon`은 기본 백그라운드 실행이다.
- `gua daemon --foreground`는 systemd/debugging 용도다.
- `gua start`는 `gua daemon` alias다.
- `gua status`와 `gua stop`은 pid file 기반 background collector 관리용이다.
- `daemon`은 기존 DB 파일이 있으면 실패한다.
- `report`는 DB 파일이 없으면 실패한다.
- `daemon`은 기본 DB에는 append하고, custom `--db PATH`가 기존 파일이면 실패한다.
- `report`는 DB 파일이 없으면 실패하고, 기본적으로 daemon_run에 기록된 interval로 GPU-hours를 계산한다.
- `daemon`과 `demo`는 host row의 `env_kind`를 항상 `"bare"`로 기록한다.
- auto-runtime proposal/project 문서는 삭제했다. Kubernetes/Slurm/Docker/Podman 확장을 다시
시작하려면 새 proposal로 시작한다.
Expand All @@ -39,45 +39,47 @@
- GitHub Release `v1.0.1`: published.
- PyPI `gpu-usage-audit 1.0.1`: published.
- NVIDIA host acceptance: 사용자가 실제 host에서 수집 정상 동작을 확인했다.
- 1.0.2 release prep: 진행 중. #14 lifecycle/report cleanup을 patch release로 배포한다.
package version은 `1.0.2`로 bump했고 local build/wheel smoke는 통과했다.
- 1.0.2 lifecycle/report cleanup release: completed in PR #14/#15 and tag `v1.0.2`.
- GitHub Release `v1.0.2`: published.
- PyPI `gpu-usage-audit 1.0.2`: published.

## 마지막 로컬 검증

```sh
uv run ruff check
uv run ruff format --check
uv run mypy
uv run pytest
uv build --out-dir /tmp/gua-dist-1.0.2-prep
bash scripts/smoke-dist-wheel.sh /tmp/gua-dist-1.0.2-prep/gpu_usage_audit-1.0.2-py3-none-any.whl
uv run python -m mypy
uv run python -m pytest
env GITHUB_REF_NAME=v1.0.2 uv run python scripts/check-tag-version.py
uv build --out-dir /tmp/gua-dist-current
bash scripts/smoke-dist-wheel.sh /tmp/gua-dist-current/gpu_usage_audit-1.0.2-py3-none-any.whl
```

결과는 `pytest` 124 passed, `mypy` 25 source files, `ruff format` 26 files 기준이다.
현재 로컬에서는 direct entrypoint인 `uv run mypy`, `uv run pytest` 대신
`uv run python -m ...` 경로가 안정적으로 동작한다.

## 현재 cleanup PR 방향
## 1.0.2 포함 cleanup

- `/tmp/gua.pid`가 PID 재사용으로 다른 프로세스를 가리킬 수 있으므로 `status`/`stop` 전에
해당 PID가 실제 managed `gpu_usage_audit daemon` 프로세스인지 확인한다.
- report §2는 low-util 전체를 "waste"로 합치지 말고 `idle-held`와 `truly-idle`을 분리한다.
- report §2는 low-util 전체를 "waste"로 합치지 않고 `idle-held`와 `truly-idle`을 분리한다.
- report §4는 process row가 아니라 identity/GPU/tick 단위로 먼저 접어서 사용자별 GPU-hours를 계산한다.
- report 출력 자체에 sample 의미, classification rule, `--interval` 의존성, heatmap 의미를 짧게 노출한다.
- NVML process list 조회 실패는 idle-held를 과소평가할 수 있으므로 warning으로 남긴다.
- 1.0.2 release prep에서는 package version, README release asset 예시, CHANGELOG를 `1.0.2`로 맞춘다.

## 주의할 점

- 현재 로컬 개발 머신은 NVIDIA host가 아니다. `gua doctor`가 unsupported를 내는 것은 정상이다.
- `/tmp/gua.db`가 이미 존재한다. 기본 경로 daemon 실행이 거부되는 것은 기대 동작이다.
- `report --interval`은 daemon 수집 interval과 같아야 GPU-hours가 맞다.
- 기본 DB는 `~/.gua/gua.db`로 이동 중이다. 기본 경로는 기존 DB에 append한다.
- `report --interval`은 선택적 override다. 새 샘플은 daemon_run interval을 기록하고, legacy row만 fallback이 필요하다.
- SQLite WAL sidecar(`*.db-wal`, `*.db-shm`)는 마지막 connection이 닫히면 정리된다.
- 1.0.2를 자를 경우 `env GITHUB_REF_NAME=v1.0.2 uv run python scripts/check-tag-version.py`가
통과해야 한다.

## 다음 세션 추천 순서

1. `git status --short`로 사용자 변경 여부를 먼저 확인한다.
2. cleanup PR의 CI 결과와 review comments를 확인한다.
3. 필요하면 report wording을 실제 운영자가 읽기 쉬운 형태로 한 번 더 다듬는다.
4. merge 후 patch release가 필요하면 version bump와 changelog를 별도 PR로 처리한다.
2. untracked 파일(`package.json`, `package-lock.json`, `project_report.md`)의 의도를 먼저 확인한다.
3. 기본 검증은 `uv run python -m pytest`, `uv run python -m mypy` 경로를 우선 사용한다.
4. `~/.gua` 기본 경로와 recorded interval 변경을 1.0.3 patch 후보로 묶고, 기능 확장은 새 proposal로 분리한다.
52 changes: 38 additions & 14 deletions projects/bare-metal-1.0/status.ko.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Bare Metal 1.0 Status

갱신일: 2026-05-15
갱신일: 2026-05-27

## 요약

Bare Metal 1.0은 단일 NVIDIA 베어메탈 호스트만 대상으로 하는 형태로 1.0.1까지
릴리스됐고, 현재 1.0.2 release prep을 진행 중이다. `v1.0.1` GitHub Release와
PyPI publish는 완료됐고, 사용자가 실제 NVIDIA host에서 telemetry 수집이 정상
동작하는 것도 확인했다.
Bare Metal 1.0은 단일 NVIDIA 베어메탈 호스트만 대상으로 하는 형태로 1.0.2까지
릴리스됐다. `v1.0.2` GitHub Release와 PyPI publish가 완료됐고, 사용자가 실제
NVIDIA host에서 telemetry 수집이 정상 동작하는 것도 확인했다.

1.0.2 후보는 1.0.1 이후 코드 퀄리티 cleanup을 배포하기 위한 patch release다.
주요 초점은 background daemon PID 안전성, report 의미 가시성, 내부 문서 정합성이다.
1.0.2는 1.0.1 이후 코드 퀄리티 cleanup을 배포한 patch release다. 주요 초점은
background daemon PID 안전성, report 의미 가시성, 내부 문서 정합성이었다.

## 구현 상태

Expand All @@ -21,13 +20,38 @@ PyPI publish는 완료됐고, 사용자가 실제 NVIDIA host에서 telemetry
| Packaging UX | 완료 | `nvidia-ml-py`가 기본 dependency이고 `nvml` extra는 빈 compatibility alias. |
| `gua` command surface | 완료 | `doctor`, `daemon`, `start`, `status`, `stop`, `report`, `demo` 제공. |
| Background daemon UX | 완료 | `gua daemon`은 기본 백그라운드 실행, `--foreground`는 systemd/debug용. |
| `daemon`/`report` DB UX | 완료 | 기본 DB는 `/tmp/gua.db`; daemon은 기존 DB를 거부하고 report는 없는 DB를 거부. |
| `daemon`/`report` DB UX | 진행 중 | 기본 상태 경로를 `~/.gua/`로 옮기고, 기본 DB는 append 가능한 history DB로 전환 중. custom `--db` 기존 파일은 계속 거부. |
| README bare-metal 문서 | 완료 | install, runbook, systemd 예시, 운영 notes가 1.0.2 기준. |
| Release | 진행 중 | package version은 `1.0.2`; local build/wheel smoke 완료, release prep PR과 tag publish가 남음. |
| Release | 완료 | `v1.0.2` GitHub Release와 PyPI publish 완료. |
| NVIDIA host acceptance | 완료 | 실제 NVIDIA host에서 수집 정상 동작 확인. |

## 마지막 확인 결과

2026-05-27 현재 작업 트리 로컬 검증:

```sh
uv run ruff check
uv run ruff format --check
uv run python -m mypy
uv run python -m pytest
env GITHUB_REF_NAME=v1.0.2 uv run python scripts/check-tag-version.py
uv build --out-dir /tmp/gua-dist-current
bash scripts/smoke-dist-wheel.sh /tmp/gua-dist-current/gpu_usage_audit-1.0.2-py3-none-any.whl
```

결과:

- `ruff check`: pass.
- `ruff format --check`: 26 files already formatted.
- `mypy`: no issues in 25 source files.
- `pytest`: 124 passed.
- tag-version check: `v1.0.2`와 `pyproject.toml` version 일치.
- `uv build`: sdist/wheel build 성공.
- wheel smoke: 성공.

현재 로컬 환경에서는 `uv run mypy`, `uv run pytest` direct entrypoint가 꼬여 있어
검증 명령은 `uv run python -m mypy`, `uv run python -m pytest` 경로를 사용했다.

2026-05-15 1.0.2 release prep 로컬 검증:

```sh
Expand Down Expand Up @@ -108,13 +132,13 @@ bash scripts/smoke-dist-wheel.sh /tmp/gua-dist-1.0.1-status/gpu_usage_audit-1.0.
- `/dev/nvidia*` 없음.
- `nvidia-smi`가 PATH에 없음.
- NVML init 실패: `libnvidia-ml.so.1` 없음.
- `/tmp/gua.db`가 이미 있어 daemon은 기본 경로로 시작하지 않음.
- 기본 DB는 `~/.gua/gua.db`로 이동 중이며, 기본 경로에서는 기존 DB에 append한다.

이 결과는 로컬 환경 한계이며, 제품 regression으로 보지 않는다.

## 다음 작업

1. 1.0.2 release prep PR에서 version, README release asset 예시, CHANGELOG를 갱신한다.
2. `uv run ruff check`, `uv run ruff format --check`, `uv run mypy`, `uv run pytest`,
`uv build`, wheel smoke, tag-version check를 다시 실행한다.
3. PR merge 후 `v1.0.2` tag를 push해 GitHub Release와 PyPI publish workflow를 실행한다.
1. 현재 작업 트리의 untracked 파일(`package.json`, `package-lock.json`, `project_report.md`)이
의도된 산출물인지 확인하고 track/delete 여부를 결정한다.
2. `~/.gua` 기본 경로와 recorded interval 변경을 1.0.3 patch 후보로 검증한다.
3. 기본 검증은 `ruff`, `mypy`, `pytest`, tag-version check, build, wheel smoke 순서로 유지한다.
Loading
Loading