Metadata-Version: 2.4
Name: whybig
Version: 0.1.0
Summary: Why is this so big? Treemap + ranked culprits and fixes for Docker images, Python wheels, model checkpoints, JS bundles, and git repos.
Author-email: Sophie Nguyen <sophie.nguyenthuthuy@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/sophie-nguyenthuthuy/whybig
Keywords: size,treemap,docker,wheel,checkpoint,bundle,git
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# whybig

[![CI](https://github.com/sophie-nguyenthuthuy/whybig/actions/workflows/ci.yml/badge.svg)](https://github.com/sophie-nguyenthuthuy/whybig/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/whybig.svg)](https://pypi.org/project/whybig/)
[![Python](https://img.shields.io/pypi/pyversions/whybig.svg)](https://pypi.org/project/whybig/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

**"Why is this so big?"** — point it at a Docker image, a Python wheel, a model
checkpoint, a JS bundle, or a git repo. Get a treemap plus ranked culprits and
concrete fixes.

```
$ whybig api-image.tar --html report.html

whybig — api-image.tar
kind: docker-image   total: 812.4 MB
layers: 14

Top culprits:
    340.2 MB   41%  root/.cache/pip
    121.7 MB   14%  usr/lib/python3.11/site-packages/torch
     48.9 MB    6%  app/.git

Findings (3, ~401.5 MB reclaimable):
  [WB001]   340.2 MB  pip cache shipped
           340.2 MB of pip cache at root/.cache/pip
           fix: pip install --no-cache-dir, or RUN --mount=type=cache,target=/root/.cache/pip
  [WB004]    48.9 MB  .git directory shipped
           ...
```

Zero dependencies, pure stdlib, Python 3.9+. Nothing is executed or unpickled —
checkpoints are analyzed from their headers only.

## Install

```bash
pip install whybig        # or just copy the directory; it's stdlib-only
whybig <path> [--html report.html] [--json] [--kind KIND] [--top N]
```

## What it understands

| Artifact | Detected by | What you learn |
|---|---|---|
| **Docker image** | `docker save` tar, OCI layout dir | merged layer tree, per-layer sizes + commands, bytes shadowed by later layers |
| **Python wheel** | `.whl` | compressed size per file, package metadata |
| **Model checkpoint** | `.safetensors`, `.gguf`, `.pt/.pth/.ckpt` | per-tensor sizes, dtype breakdown, optimizer-state detection |
| **JS bundle** | `.js/.mjs/.cjs` + source map | generated bytes attributed back to original source files |
| **Git repo** | dir with `.git` | on-disk (packed) weight of all history per path, HEAD vs history-only |
| **dir / archive** | fallback | plain size tree, generic rules still apply |

`--html` writes a **self-contained treemap** (no CDN, works offline, dark/light):
click to zoom, breadcrumb to go back, findings listed below the map.

## Rules

| Rule | Finds | Fix it suggests |
|---|---|---|
| WB001 | pip cache shipped | `--no-cache-dir` / BuildKit cache mount |
| WB002 | apt lists/cache shipped | `rm -rf /var/lib/apt/lists/*` in same RUN |
| WB003 | `__pycache__` / `.pyc` | `PYTHONDONTWRITEBYTECODE=1`, ignore files |
| WB004 | `.git` inside artifact | `.dockerignore`, multi-stage |
| WB005 | tests inside wheel | package `exclude = ["tests*"]` |
| WB006 | files duplicated across layers | merge RUN/COPY steps, multi-stage |
| WB007 | float32-dominated checkpoint | bf16/fp16 or quantize |
| WB008 | optimizer state in checkpoint | save `state_dict()` only |
| WB009 | source maps in prod | build without maps / hidden maps |
| WB010 | `node_modules` shipped | `npm ci --omit=dev` |
| WB011 | large blobs in git history only | `git filter-repo` / LFS |
| WB012 | static libs (`.a`) in runtime image | drop `-dev` packages from final stage |

## Contributing

The contributor unit is deliberately small:

- **One analyzer per artifact type** — `whybig/analyzers/<type>.py` exposing
  `KIND` and `analyze(path) -> Report`. Register it in `analyzers/__init__.py`
  and `detect.py`. Wanted: `.deb`/`.rpm`, `.apk` (Android), `.jar`, ONNX,
  conda envs, npm packages.
- **One size heuristic per rule** — `whybig/rules/<name>.py` exposing `RULE`
  (next free `WBnnn`), `TITLE`, `APPLIES` (set of kinds), and
  `check(report) -> [Finding]`. Auto-discovered; no registration needed.
  Wanted: locales/i18n data, conda `pkgs/` cache, `.DS_Store`/thumbnail litter,
  debug symbols in `.so`, duplicate vendored deps.

Every rule needs a test that builds its fixture programmatically (see
`tests/`) — no binary fixtures in the repo.

```bash
python3 -m pytest tests/ -q
```

## License

[MIT](LICENSE)
