Metadata-Version: 2.4
Name: wholoads
Version: 0.5.0
Summary: Find who's overloading your infrastructure
Project-URL: Homepage, https://github.com/timur-ND/wholoads
Project-URL: Documentation, https://github.com/timur-ND/wholoads#readme
Project-URL: Repository, https://github.com/timur-ND/wholoads
Project-URL: Issues, https://github.com/timur-ND/wholoads/issues
Project-URL: Funding, https://ko-fi.com/wholoads
Author-email: timur-nd <timur.nasridin1@gmail.com>
License-Expression: MIT
Keywords: clickhouse,dba,debugging,devops,kubernetes,monitoring,performance,postgresql,sre
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: kubernetes>=29.0
Requires-Dist: paramiko>=3.0
Requires-Dist: psycopg[binary]>=3.1
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: types-paramiko>=3.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# wholoads

**Find who's overloading your infrastructure. Fast.**

[![PyPI](https://img.shields.io/pypi/v/wholoads)](https://pypi.org/project/wholoads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)

---

One command to answer **"who is killing my system right now?"** — whether it's PostgreSQL, ClickHouse, or Kubernetes.

```
$ wholoads pg
╭─────────────────────────────────────────────────────────╮
│  PostgreSQL — db-prod-01 (192.168.1.10)                 │
│  Uptime: 47d 3h │ Connections: 142/200 │ DB size: 89GB  │
╰─────────────────────────────────────────────────────────╯

🔴 TOP CPU CONSUMERS
┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
┃ #  ┃ Query (truncated)                    ┃ Calls    ┃ Total   ┃ % of all ┃
┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
│ 1  │ SELECT * FROM orders WHERE status... │ 1.2M     │ 4h 12m  │ 34.7%    │
│ 2  │ UPDATE inventory SET quantity = ...  │ 890K     │ 2h 05m  │ 17.1%    │
│ 3  │ SELECT u.*, p.* FROM users u JO...  │ 456K     │ 1h 33m  │ 12.8%    │
└────┴───────────────────────────────────────┴──────────┴─────────┴──────────┘

🟡 WORST CACHE HIT RATIO
  orders_archive: 23.4% (shared_blks_read: 4.2M)
  audit_log:      45.1% (shared_blks_read: 1.8M)

💡 RECOMMENDATIONS
  1. Query #1: Seq Scan on `orders` (2.1M rows) → CREATE INDEX CONCURRENTLY ...
  2. Table `orders_archive`: cache hit 23% → consider partitioning or archival
  3. 142/200 connections used → review connection pooling (pgbouncer)

$ wholoads ch
$ wholoads k8s --namespace production
```

## Installation

```bash
pip install wholoads
```

## Quick Start

```bash
# Generate a config template
wholoads init

# Edit connection settings
vim ~/.config/wholoads/config.yaml

# Run analysis
wholoads pg
wholoads ch
wholoads k8s
```

## Configuration

`~/.config/wholoads/config.yaml`:

```yaml
# PostgreSQL targets
postgresql:
  targets:
    - name: db-prod-01
      # Connection method: direct | ssh
      method: direct
      host: 192.168.1.10
      port: 5432
      user: monitoring
      password_env: WHOLOADS_PG_PASSWORD  # read from env var
      dbname: myapp
      
    - name: db-prod-02
      method: ssh
      ssh_host: db-prod-02.internal
      ssh_user: admin
      ssh_key: ~/.ssh/id_ed25519
      # psql runs as postgres user on the remote host
      pg_user: postgres
      dbname: zabbix

  # Analysis settings
  settings:
    top_n: 10                    # how many top queries per category
    min_calls: 100               # ignore queries with fewer calls
    cache_hit_threshold: 95.0    # flag tables below this %
    explain: true                # auto-run EXPLAIN for top queries
    explain_format: json         # text | json
    
# ClickHouse targets
clickhouse:
  targets:
    - name: ch-analytics
      method: direct
      host: ch-cluster.internal
      port: 8123                 # HTTP interface
      user: readonly
      password_env: WHOLOADS_CH_PASSWORD
      
    - name: ch-datalayer
      method: ssh
      ssh_host: ch-datalayer-01.internal
      ssh_user: admin
      # uses clickhouse-client on remote host
      
  settings:
    top_n: 10
    min_query_duration_ms: 1000
    include_system_queries: false
    
# Kubernetes targets
kubernetes:
  targets:
    - name: prod-cluster
      method: kubeconfig
      context: prod-context
      # or explicit kubeconfig path:
      # kubeconfig: ~/.kube/prod.yaml
      
    - name: staging
      method: kubeconfig
      context: staging-context
      
  settings:
    namespaces: []               # empty = all namespaces
    exclude_namespaces:
      - kube-system
      - monitoring
    sort_by: cpu                 # cpu | memory | restarts
    top_n: 20

# Output settings  
output:
  format: rich                   # rich | json | csv | markdown
  color: true
  truncate_query: 80            # max query display length
  
# Global SSH defaults
ssh:
  timeout: 10
  known_hosts: ~/.ssh/known_hosts
  # can be overridden per target
```

## Plugins

### PostgreSQL (`wholoads pg`)

Answers:
- **Who consumes CPU?** — top queries by `total_exec_time` from `pg_stat_statements`
- **Who reads disk?** — top queries by `shared_blks_read`
- **Who misses cache?** — worst `cache_hit_ratio` per query and per table
- **Who returns too much?** — top queries by `rows / calls`
- **Who holds locks?** — long-running transactions and lock waits
- **Who eats connections?** — connections by user/application/state

Optional deep-dive: runs `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` on top queries and parses the plan for Seq Scans, estimation mismatches, disk sorts.

### ClickHouse (`wholoads ch`)

Answers:
- **Who consumes CPU?** — top queries from `system.query_log` by `query_duration_ms`
- **Who reads data?** — top by `read_bytes` / `read_rows`
- **Who from?** — breakdown by `user`, `initial_address`, `client_name`
- **Who writes?** — top inserters by `written_bytes`
- **What merges?** — active merges and mutations from `system.merges` / `system.mutations`
- **What's growing?** — tables by size growth rate

### Kubernetes (`wholoads k8s`)

Answers:
- **Who eats CPU?** — pods sorted by CPU usage vs requests/limits
- **Who eats memory?** — pods sorted by memory usage vs requests/limits
- **Who restarts?** — pods with high restart count, with last termination reason
- **Who's pending?** — unschedulable pods with reasons
- **Who's throttled?** — pods hitting CPU throttling
- **Who has no limits?** — pods running without resource limits (risky)

## Output Formats

```bash
wholoads pg                          # rich terminal output (default)
wholoads pg --format json            # JSON for piping
wholoads pg --format csv             # CSV for spreadsheets
wholoads pg --format markdown        # Markdown for reports/tickets
wholoads pg --format json | jq '.top_cpu[0]'   # composable
```

## Multiple Targets

```bash
wholoads pg                          # uses first target in config
wholoads pg --target db-prod-02      # specific target
wholoads pg --all                    # all configured PG targets
```

## Writing Custom Plugins

```python
from wholoads.plugin import BasePlugin, Finding, Severity

class RedisPlugin(BasePlugin):
    name = "redis"
    description = "Find who's overloading Redis"
    
    def collect(self, target) -> list[Finding]:
        # Connect and gather data
        info = self.execute("INFO ALL")
        clients = self.execute("CLIENT LIST")
        slowlog = self.execute("SLOWLOG GET 20")
        
        findings = []
        # Analyze and produce findings
        findings.append(Finding(
            severity=Severity.RED,
            category="memory",
            title="Big key detected",
            detail="key 'sessions:cache' is 2.1GB",
            recommendation="Consider splitting or TTL"
        ))
        return findings
```

Drop your plugin in `~/.config/wholoads/plugins/` and it's auto-discovered.

## Contributing

Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

Priority areas:
- New plugins (Redis, MySQL, Nginx, RabbitMQ, MongoDB)
- Output formatters
- Connection methods
- Tests and CI

## Support the Project

If `wholoads` saves you time during incidents, consider supporting development:

<a href="https://ko-fi.com/wholoads"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support on Ko-fi"></a>

[![GitHub Sponsors](https://img.shields.io/github/sponsors/USERNAME?style=social)](https://github.com/sponsors/USERNAME)

You can also:
- ⭐ Star the repo — it helps visibility
- 🐛 Report bugs and request features
- 📝 Write a plugin for your favorite system
- 📣 Share with colleagues who debug infrastructure

## License

MIT — use it however you want.
