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.5.0.tar.gz (19.4 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.5.0-py3-none-any.whl (20.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for wholoads-0.5.0.tar.gz
Algorithm Hash digest
SHA256 da015b915f3b33b7273d663cf4d8eb0485830d3b3cbb5781e1d7e9738d2eca87
MD5 b7f0b3fe9334dd03202cab76530e9766
BLAKE2b-256 f970925a60de1c9f81913511eded1650c6e0a8fa0114ca2bc3d264b759a39870

See more details on using hashes here.

Provenance

The following attestation bundles were made for wholoads-0.5.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.5.0-py3-none-any.whl.

File metadata

  • Download URL: wholoads-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 20.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.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 85e7c6bdedd09d8094f9a8dff8cf44eaebee6808ec39c3a129ace5d9e0d408e4
MD5 c40305169caacdf35dc4105215cb49ed
BLAKE2b-256 459055822dd79bfeea599055736ce6c4e2a96a045123c3f571f2cb20728d6a3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for wholoads-0.5.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