Skip to main content

Find who's overloading your infrastructure

Project description

wholoads

Find who's overloading your infrastructure. Fast.

PyPI License: MIT Python 3.10+


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

pip install wholoads

Quick Start

# 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:

# 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

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

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

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 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:

Support on Ko-fi

GitHub Sponsors

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wholoads-0.2.0.tar.gz (18.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wholoads-0.2.0-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file wholoads-0.2.0.tar.gz.

File metadata

  • Download URL: wholoads-0.2.0.tar.gz
  • Upload date:
  • Size: 18.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wholoads-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b843130a6cd92fe9e88272332f911c15928879591579dd82e9dfa4a01ab16000
MD5 428807161673e00c4aa68f5e99922351
BLAKE2b-256 0a457c819a9ace984f02fe236a70cbdfd454cc2231943d8002681732cd66639d

See more details on using hashes here.

Provenance

The following attestation bundles were made for wholoads-0.2.0.tar.gz:

Publisher: ci.yml on timur-ND/wholoads

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wholoads-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: wholoads-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for wholoads-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c1b3214d0ae758f96dccdfaf151da6113cee3beddfd5e65d19134a7a1792450f
MD5 0f33dd1c009872c2af2b26e6327454d4
BLAKE2b-256 60062776e8ffd0e767291628cd4bb45907a15b72dce2c174e3eb147a557c123b

See more details on using hashes here.

Provenance

The following attestation bundles were made for wholoads-0.2.0-py3-none-any.whl:

Publisher: ci.yml on timur-ND/wholoads

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page