wholoads 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wholoads-0.1.0/.github/FUNDING.yml +4 -0
- wholoads-0.1.0/.github/workflows/ci.yml +62 -0
- wholoads-0.1.0/.gitignore +33 -0
- wholoads-0.1.0/PKG-INFO +305 -0
- wholoads-0.1.0/README.md +259 -0
- wholoads-0.1.0/STRUCTURE.md +57 -0
- wholoads-0.1.0/pyproject.toml +95 -0
- wholoads-0.1.0/src/wholoads/__init__.py +3 -0
- wholoads-0.1.0/src/wholoads/__main__.py +4 -0
- wholoads-0.1.0/src/wholoads/cli.py +170 -0
- wholoads-0.1.0/src/wholoads/config.py +108 -0
- wholoads-0.1.0/src/wholoads/core/__init__.py +0 -0
- wholoads-0.1.0/src/wholoads/core/connection.py +220 -0
- wholoads-0.1.0/src/wholoads/core/plugin.py +109 -0
- wholoads-0.1.0/src/wholoads/output.py +216 -0
- wholoads-0.1.0/src/wholoads/plugins/__init__.py +0 -0
- wholoads-0.1.0/src/wholoads/plugins/postgresql.py +453 -0
- wholoads-0.1.0/tests/__init__.py +0 -0
- wholoads-0.1.0/tests/test_core.py +114 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
|
|
30
|
+
- name: Lint with ruff
|
|
31
|
+
run: ruff check src/
|
|
32
|
+
|
|
33
|
+
- name: Type check with mypy
|
|
34
|
+
run: mypy src/wholoads/
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: pytest --cov=wholoads --cov-report=xml
|
|
38
|
+
|
|
39
|
+
release:
|
|
40
|
+
needs: test
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write
|
|
45
|
+
contents: read
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- name: Set up Python
|
|
51
|
+
uses: actions/setup-python@v5
|
|
52
|
+
with:
|
|
53
|
+
python-version: "3.12"
|
|
54
|
+
|
|
55
|
+
- name: Install build tools
|
|
56
|
+
run: pip install build
|
|
57
|
+
|
|
58
|
+
- name: Build package
|
|
59
|
+
run: python -m build
|
|
60
|
+
|
|
61
|
+
- name: Publish to PyPI
|
|
62
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Testing & coverage
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
coverage.xml
|
|
21
|
+
htmlcov/
|
|
22
|
+
|
|
23
|
+
# Mypy
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
|
|
26
|
+
# Ruff
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
|
|
29
|
+
# IDE
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
*.swp
|
|
33
|
+
*.swo
|
wholoads-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wholoads
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find who's overloading your infrastructure
|
|
5
|
+
Project-URL: Homepage, https://github.com/timur-ND/wholoads
|
|
6
|
+
Project-URL: Documentation, https://github.com/timur-ND/wholoads#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/timur-ND/wholoads
|
|
8
|
+
Project-URL: Issues, https://github.com/timur-ND/wholoads/issues
|
|
9
|
+
Project-URL: Funding, https://ko-fi.com/wholoads
|
|
10
|
+
Author-email: timur-nd <timur.nasridin1@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
Keywords: clickhouse,dba,debugging,devops,kubernetes,monitoring,performance,postgresql,sre
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: System :: Monitoring
|
|
26
|
+
Classifier: Topic :: System :: Systems Administration
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Requires-Dist: kubernetes>=29.0
|
|
30
|
+
Requires-Dist: paramiko>=3.0
|
|
31
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
32
|
+
Requires-Dist: pydantic>=2.0
|
|
33
|
+
Requires-Dist: pyyaml>=6.0
|
|
34
|
+
Requires-Dist: rich>=13.0
|
|
35
|
+
Requires-Dist: typer>=0.12
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
38
|
+
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
43
|
+
Requires-Dist: types-paramiko>=3.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# wholoads
|
|
48
|
+
|
|
49
|
+
**Find who's overloading your infrastructure. Fast.**
|
|
50
|
+
|
|
51
|
+
[](https://pypi.org/project/wholoads/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
[](https://www.python.org/downloads/)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
One command to answer **"who is killing my system right now?"** — whether it's PostgreSQL, ClickHouse, or Kubernetes.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
$ wholoads pg
|
|
61
|
+
╭─────────────────────────────────────────────────────────╮
|
|
62
|
+
│ PostgreSQL — db-prod-01 (192.168.1.10) │
|
|
63
|
+
│ Uptime: 47d 3h │ Connections: 142/200 │ DB size: 89GB │
|
|
64
|
+
╰─────────────────────────────────────────────────────────╯
|
|
65
|
+
|
|
66
|
+
🔴 TOP CPU CONSUMERS
|
|
67
|
+
┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
|
|
68
|
+
┃ # ┃ Query (truncated) ┃ Calls ┃ Total ┃ % of all ┃
|
|
69
|
+
┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
|
|
70
|
+
│ 1 │ SELECT * FROM orders WHERE status... │ 1.2M │ 4h 12m │ 34.7% │
|
|
71
|
+
│ 2 │ UPDATE inventory SET quantity = ... │ 890K │ 2h 05m │ 17.1% │
|
|
72
|
+
│ 3 │ SELECT u.*, p.* FROM users u JO... │ 456K │ 1h 33m │ 12.8% │
|
|
73
|
+
└────┴───────────────────────────────────────┴──────────┴─────────┴──────────┘
|
|
74
|
+
|
|
75
|
+
🟡 WORST CACHE HIT RATIO
|
|
76
|
+
orders_archive: 23.4% (shared_blks_read: 4.2M)
|
|
77
|
+
audit_log: 45.1% (shared_blks_read: 1.8M)
|
|
78
|
+
|
|
79
|
+
💡 RECOMMENDATIONS
|
|
80
|
+
1. Query #1: Seq Scan on `orders` (2.1M rows) → CREATE INDEX CONCURRENTLY ...
|
|
81
|
+
2. Table `orders_archive`: cache hit 23% → consider partitioning or archival
|
|
82
|
+
3. 142/200 connections used → review connection pooling (pgbouncer)
|
|
83
|
+
|
|
84
|
+
$ wholoads ch
|
|
85
|
+
$ wholoads k8s --namespace production
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install wholoads
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Generate a config template
|
|
98
|
+
wholoads init
|
|
99
|
+
|
|
100
|
+
# Edit connection settings
|
|
101
|
+
vim ~/.config/wholoads/config.yaml
|
|
102
|
+
|
|
103
|
+
# Run analysis
|
|
104
|
+
wholoads pg
|
|
105
|
+
wholoads ch
|
|
106
|
+
wholoads k8s
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
`~/.config/wholoads/config.yaml`:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
# PostgreSQL targets
|
|
115
|
+
postgresql:
|
|
116
|
+
targets:
|
|
117
|
+
- name: db-prod-01
|
|
118
|
+
# Connection method: direct | ssh
|
|
119
|
+
method: direct
|
|
120
|
+
host: 192.168.1.10
|
|
121
|
+
port: 5432
|
|
122
|
+
user: monitoring
|
|
123
|
+
password_env: WHOLOADS_PG_PASSWORD # read from env var
|
|
124
|
+
dbname: myapp
|
|
125
|
+
|
|
126
|
+
- name: db-prod-02
|
|
127
|
+
method: ssh
|
|
128
|
+
ssh_host: db-prod-02.internal
|
|
129
|
+
ssh_user: admin
|
|
130
|
+
ssh_key: ~/.ssh/id_ed25519
|
|
131
|
+
# psql runs as postgres user on the remote host
|
|
132
|
+
pg_user: postgres
|
|
133
|
+
dbname: zabbix
|
|
134
|
+
|
|
135
|
+
# Analysis settings
|
|
136
|
+
settings:
|
|
137
|
+
top_n: 10 # how many top queries per category
|
|
138
|
+
min_calls: 100 # ignore queries with fewer calls
|
|
139
|
+
cache_hit_threshold: 95.0 # flag tables below this %
|
|
140
|
+
explain: true # auto-run EXPLAIN for top queries
|
|
141
|
+
explain_format: json # text | json
|
|
142
|
+
|
|
143
|
+
# ClickHouse targets
|
|
144
|
+
clickhouse:
|
|
145
|
+
targets:
|
|
146
|
+
- name: ch-analytics
|
|
147
|
+
method: direct
|
|
148
|
+
host: ch-cluster.internal
|
|
149
|
+
port: 8123 # HTTP interface
|
|
150
|
+
user: readonly
|
|
151
|
+
password_env: WHOLOADS_CH_PASSWORD
|
|
152
|
+
|
|
153
|
+
- name: ch-datalayer
|
|
154
|
+
method: ssh
|
|
155
|
+
ssh_host: ch-datalayer-01.internal
|
|
156
|
+
ssh_user: admin
|
|
157
|
+
# uses clickhouse-client on remote host
|
|
158
|
+
|
|
159
|
+
settings:
|
|
160
|
+
top_n: 10
|
|
161
|
+
min_query_duration_ms: 1000
|
|
162
|
+
include_system_queries: false
|
|
163
|
+
|
|
164
|
+
# Kubernetes targets
|
|
165
|
+
kubernetes:
|
|
166
|
+
targets:
|
|
167
|
+
- name: prod-cluster
|
|
168
|
+
method: kubeconfig
|
|
169
|
+
context: prod-context
|
|
170
|
+
# or explicit kubeconfig path:
|
|
171
|
+
# kubeconfig: ~/.kube/prod.yaml
|
|
172
|
+
|
|
173
|
+
- name: staging
|
|
174
|
+
method: kubeconfig
|
|
175
|
+
context: staging-context
|
|
176
|
+
|
|
177
|
+
settings:
|
|
178
|
+
namespaces: [] # empty = all namespaces
|
|
179
|
+
exclude_namespaces:
|
|
180
|
+
- kube-system
|
|
181
|
+
- monitoring
|
|
182
|
+
sort_by: cpu # cpu | memory | restarts
|
|
183
|
+
top_n: 20
|
|
184
|
+
|
|
185
|
+
# Output settings
|
|
186
|
+
output:
|
|
187
|
+
format: rich # rich | json | csv | markdown
|
|
188
|
+
color: true
|
|
189
|
+
truncate_query: 80 # max query display length
|
|
190
|
+
|
|
191
|
+
# Global SSH defaults
|
|
192
|
+
ssh:
|
|
193
|
+
timeout: 10
|
|
194
|
+
known_hosts: ~/.ssh/known_hosts
|
|
195
|
+
# can be overridden per target
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Plugins
|
|
199
|
+
|
|
200
|
+
### PostgreSQL (`wholoads pg`)
|
|
201
|
+
|
|
202
|
+
Answers:
|
|
203
|
+
- **Who consumes CPU?** — top queries by `total_exec_time` from `pg_stat_statements`
|
|
204
|
+
- **Who reads disk?** — top queries by `shared_blks_read`
|
|
205
|
+
- **Who misses cache?** — worst `cache_hit_ratio` per query and per table
|
|
206
|
+
- **Who returns too much?** — top queries by `rows / calls`
|
|
207
|
+
- **Who holds locks?** — long-running transactions and lock waits
|
|
208
|
+
- **Who eats connections?** — connections by user/application/state
|
|
209
|
+
|
|
210
|
+
Optional deep-dive: runs `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` on top queries and parses the plan for Seq Scans, estimation mismatches, disk sorts.
|
|
211
|
+
|
|
212
|
+
### ClickHouse (`wholoads ch`)
|
|
213
|
+
|
|
214
|
+
Answers:
|
|
215
|
+
- **Who consumes CPU?** — top queries from `system.query_log` by `query_duration_ms`
|
|
216
|
+
- **Who reads data?** — top by `read_bytes` / `read_rows`
|
|
217
|
+
- **Who from?** — breakdown by `user`, `initial_address`, `client_name`
|
|
218
|
+
- **Who writes?** — top inserters by `written_bytes`
|
|
219
|
+
- **What merges?** — active merges and mutations from `system.merges` / `system.mutations`
|
|
220
|
+
- **What's growing?** — tables by size growth rate
|
|
221
|
+
|
|
222
|
+
### Kubernetes (`wholoads k8s`)
|
|
223
|
+
|
|
224
|
+
Answers:
|
|
225
|
+
- **Who eats CPU?** — pods sorted by CPU usage vs requests/limits
|
|
226
|
+
- **Who eats memory?** — pods sorted by memory usage vs requests/limits
|
|
227
|
+
- **Who restarts?** — pods with high restart count, with last termination reason
|
|
228
|
+
- **Who's pending?** — unschedulable pods with reasons
|
|
229
|
+
- **Who's throttled?** — pods hitting CPU throttling
|
|
230
|
+
- **Who has no limits?** — pods running without resource limits (risky)
|
|
231
|
+
|
|
232
|
+
## Output Formats
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
wholoads pg # rich terminal output (default)
|
|
236
|
+
wholoads pg --format json # JSON for piping
|
|
237
|
+
wholoads pg --format csv # CSV for spreadsheets
|
|
238
|
+
wholoads pg --format markdown # Markdown for reports/tickets
|
|
239
|
+
wholoads pg --format json | jq '.top_cpu[0]' # composable
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Multiple Targets
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
wholoads pg # uses first target in config
|
|
246
|
+
wholoads pg --target db-prod-02 # specific target
|
|
247
|
+
wholoads pg --all # all configured PG targets
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Writing Custom Plugins
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from wholoads.plugin import BasePlugin, Finding, Severity
|
|
254
|
+
|
|
255
|
+
class RedisPlugin(BasePlugin):
|
|
256
|
+
name = "redis"
|
|
257
|
+
description = "Find who's overloading Redis"
|
|
258
|
+
|
|
259
|
+
def collect(self, target) -> list[Finding]:
|
|
260
|
+
# Connect and gather data
|
|
261
|
+
info = self.execute("INFO ALL")
|
|
262
|
+
clients = self.execute("CLIENT LIST")
|
|
263
|
+
slowlog = self.execute("SLOWLOG GET 20")
|
|
264
|
+
|
|
265
|
+
findings = []
|
|
266
|
+
# Analyze and produce findings
|
|
267
|
+
findings.append(Finding(
|
|
268
|
+
severity=Severity.RED,
|
|
269
|
+
category="memory",
|
|
270
|
+
title="Big key detected",
|
|
271
|
+
detail="key 'sessions:cache' is 2.1GB",
|
|
272
|
+
recommendation="Consider splitting or TTL"
|
|
273
|
+
))
|
|
274
|
+
return findings
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Drop your plugin in `~/.config/wholoads/plugins/` and it's auto-discovered.
|
|
278
|
+
|
|
279
|
+
## Contributing
|
|
280
|
+
|
|
281
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
282
|
+
|
|
283
|
+
Priority areas:
|
|
284
|
+
- New plugins (Redis, MySQL, Nginx, RabbitMQ, MongoDB)
|
|
285
|
+
- Output formatters
|
|
286
|
+
- Connection methods
|
|
287
|
+
- Tests and CI
|
|
288
|
+
|
|
289
|
+
## Support the Project
|
|
290
|
+
|
|
291
|
+
If `wholoads` saves you time during incidents, consider supporting development:
|
|
292
|
+
|
|
293
|
+
<a href="https://ko-fi.com/wholoads"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support on Ko-fi"></a>
|
|
294
|
+
|
|
295
|
+
[](https://github.com/sponsors/USERNAME)
|
|
296
|
+
|
|
297
|
+
You can also:
|
|
298
|
+
- ⭐ Star the repo — it helps visibility
|
|
299
|
+
- 🐛 Report bugs and request features
|
|
300
|
+
- 📝 Write a plugin for your favorite system
|
|
301
|
+
- 📣 Share with colleagues who debug infrastructure
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT — use it however you want.
|
wholoads-0.1.0/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# wholoads
|
|
2
|
+
|
|
3
|
+
**Find who's overloading your infrastructure. Fast.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/wholoads/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.python.org/downloads/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
One command to answer **"who is killing my system right now?"** — whether it's PostgreSQL, ClickHouse, or Kubernetes.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ wholoads pg
|
|
15
|
+
╭─────────────────────────────────────────────────────────╮
|
|
16
|
+
│ PostgreSQL — db-prod-01 (192.168.1.10) │
|
|
17
|
+
│ Uptime: 47d 3h │ Connections: 142/200 │ DB size: 89GB │
|
|
18
|
+
╰─────────────────────────────────────────────────────────╯
|
|
19
|
+
|
|
20
|
+
🔴 TOP CPU CONSUMERS
|
|
21
|
+
┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
|
|
22
|
+
┃ # ┃ Query (truncated) ┃ Calls ┃ Total ┃ % of all ┃
|
|
23
|
+
┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
|
|
24
|
+
│ 1 │ SELECT * FROM orders WHERE status... │ 1.2M │ 4h 12m │ 34.7% │
|
|
25
|
+
│ 2 │ UPDATE inventory SET quantity = ... │ 890K │ 2h 05m │ 17.1% │
|
|
26
|
+
│ 3 │ SELECT u.*, p.* FROM users u JO... │ 456K │ 1h 33m │ 12.8% │
|
|
27
|
+
└────┴───────────────────────────────────────┴──────────┴─────────┴──────────┘
|
|
28
|
+
|
|
29
|
+
🟡 WORST CACHE HIT RATIO
|
|
30
|
+
orders_archive: 23.4% (shared_blks_read: 4.2M)
|
|
31
|
+
audit_log: 45.1% (shared_blks_read: 1.8M)
|
|
32
|
+
|
|
33
|
+
💡 RECOMMENDATIONS
|
|
34
|
+
1. Query #1: Seq Scan on `orders` (2.1M rows) → CREATE INDEX CONCURRENTLY ...
|
|
35
|
+
2. Table `orders_archive`: cache hit 23% → consider partitioning or archival
|
|
36
|
+
3. 142/200 connections used → review connection pooling (pgbouncer)
|
|
37
|
+
|
|
38
|
+
$ wholoads ch
|
|
39
|
+
$ wholoads k8s --namespace production
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install wholoads
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Generate a config template
|
|
52
|
+
wholoads init
|
|
53
|
+
|
|
54
|
+
# Edit connection settings
|
|
55
|
+
vim ~/.config/wholoads/config.yaml
|
|
56
|
+
|
|
57
|
+
# Run analysis
|
|
58
|
+
wholoads pg
|
|
59
|
+
wholoads ch
|
|
60
|
+
wholoads k8s
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
`~/.config/wholoads/config.yaml`:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
# PostgreSQL targets
|
|
69
|
+
postgresql:
|
|
70
|
+
targets:
|
|
71
|
+
- name: db-prod-01
|
|
72
|
+
# Connection method: direct | ssh
|
|
73
|
+
method: direct
|
|
74
|
+
host: 192.168.1.10
|
|
75
|
+
port: 5432
|
|
76
|
+
user: monitoring
|
|
77
|
+
password_env: WHOLOADS_PG_PASSWORD # read from env var
|
|
78
|
+
dbname: myapp
|
|
79
|
+
|
|
80
|
+
- name: db-prod-02
|
|
81
|
+
method: ssh
|
|
82
|
+
ssh_host: db-prod-02.internal
|
|
83
|
+
ssh_user: admin
|
|
84
|
+
ssh_key: ~/.ssh/id_ed25519
|
|
85
|
+
# psql runs as postgres user on the remote host
|
|
86
|
+
pg_user: postgres
|
|
87
|
+
dbname: zabbix
|
|
88
|
+
|
|
89
|
+
# Analysis settings
|
|
90
|
+
settings:
|
|
91
|
+
top_n: 10 # how many top queries per category
|
|
92
|
+
min_calls: 100 # ignore queries with fewer calls
|
|
93
|
+
cache_hit_threshold: 95.0 # flag tables below this %
|
|
94
|
+
explain: true # auto-run EXPLAIN for top queries
|
|
95
|
+
explain_format: json # text | json
|
|
96
|
+
|
|
97
|
+
# ClickHouse targets
|
|
98
|
+
clickhouse:
|
|
99
|
+
targets:
|
|
100
|
+
- name: ch-analytics
|
|
101
|
+
method: direct
|
|
102
|
+
host: ch-cluster.internal
|
|
103
|
+
port: 8123 # HTTP interface
|
|
104
|
+
user: readonly
|
|
105
|
+
password_env: WHOLOADS_CH_PASSWORD
|
|
106
|
+
|
|
107
|
+
- name: ch-datalayer
|
|
108
|
+
method: ssh
|
|
109
|
+
ssh_host: ch-datalayer-01.internal
|
|
110
|
+
ssh_user: admin
|
|
111
|
+
# uses clickhouse-client on remote host
|
|
112
|
+
|
|
113
|
+
settings:
|
|
114
|
+
top_n: 10
|
|
115
|
+
min_query_duration_ms: 1000
|
|
116
|
+
include_system_queries: false
|
|
117
|
+
|
|
118
|
+
# Kubernetes targets
|
|
119
|
+
kubernetes:
|
|
120
|
+
targets:
|
|
121
|
+
- name: prod-cluster
|
|
122
|
+
method: kubeconfig
|
|
123
|
+
context: prod-context
|
|
124
|
+
# or explicit kubeconfig path:
|
|
125
|
+
# kubeconfig: ~/.kube/prod.yaml
|
|
126
|
+
|
|
127
|
+
- name: staging
|
|
128
|
+
method: kubeconfig
|
|
129
|
+
context: staging-context
|
|
130
|
+
|
|
131
|
+
settings:
|
|
132
|
+
namespaces: [] # empty = all namespaces
|
|
133
|
+
exclude_namespaces:
|
|
134
|
+
- kube-system
|
|
135
|
+
- monitoring
|
|
136
|
+
sort_by: cpu # cpu | memory | restarts
|
|
137
|
+
top_n: 20
|
|
138
|
+
|
|
139
|
+
# Output settings
|
|
140
|
+
output:
|
|
141
|
+
format: rich # rich | json | csv | markdown
|
|
142
|
+
color: true
|
|
143
|
+
truncate_query: 80 # max query display length
|
|
144
|
+
|
|
145
|
+
# Global SSH defaults
|
|
146
|
+
ssh:
|
|
147
|
+
timeout: 10
|
|
148
|
+
known_hosts: ~/.ssh/known_hosts
|
|
149
|
+
# can be overridden per target
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Plugins
|
|
153
|
+
|
|
154
|
+
### PostgreSQL (`wholoads pg`)
|
|
155
|
+
|
|
156
|
+
Answers:
|
|
157
|
+
- **Who consumes CPU?** — top queries by `total_exec_time` from `pg_stat_statements`
|
|
158
|
+
- **Who reads disk?** — top queries by `shared_blks_read`
|
|
159
|
+
- **Who misses cache?** — worst `cache_hit_ratio` per query and per table
|
|
160
|
+
- **Who returns too much?** — top queries by `rows / calls`
|
|
161
|
+
- **Who holds locks?** — long-running transactions and lock waits
|
|
162
|
+
- **Who eats connections?** — connections by user/application/state
|
|
163
|
+
|
|
164
|
+
Optional deep-dive: runs `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` on top queries and parses the plan for Seq Scans, estimation mismatches, disk sorts.
|
|
165
|
+
|
|
166
|
+
### ClickHouse (`wholoads ch`)
|
|
167
|
+
|
|
168
|
+
Answers:
|
|
169
|
+
- **Who consumes CPU?** — top queries from `system.query_log` by `query_duration_ms`
|
|
170
|
+
- **Who reads data?** — top by `read_bytes` / `read_rows`
|
|
171
|
+
- **Who from?** — breakdown by `user`, `initial_address`, `client_name`
|
|
172
|
+
- **Who writes?** — top inserters by `written_bytes`
|
|
173
|
+
- **What merges?** — active merges and mutations from `system.merges` / `system.mutations`
|
|
174
|
+
- **What's growing?** — tables by size growth rate
|
|
175
|
+
|
|
176
|
+
### Kubernetes (`wholoads k8s`)
|
|
177
|
+
|
|
178
|
+
Answers:
|
|
179
|
+
- **Who eats CPU?** — pods sorted by CPU usage vs requests/limits
|
|
180
|
+
- **Who eats memory?** — pods sorted by memory usage vs requests/limits
|
|
181
|
+
- **Who restarts?** — pods with high restart count, with last termination reason
|
|
182
|
+
- **Who's pending?** — unschedulable pods with reasons
|
|
183
|
+
- **Who's throttled?** — pods hitting CPU throttling
|
|
184
|
+
- **Who has no limits?** — pods running without resource limits (risky)
|
|
185
|
+
|
|
186
|
+
## Output Formats
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
wholoads pg # rich terminal output (default)
|
|
190
|
+
wholoads pg --format json # JSON for piping
|
|
191
|
+
wholoads pg --format csv # CSV for spreadsheets
|
|
192
|
+
wholoads pg --format markdown # Markdown for reports/tickets
|
|
193
|
+
wholoads pg --format json | jq '.top_cpu[0]' # composable
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Multiple Targets
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
wholoads pg # uses first target in config
|
|
200
|
+
wholoads pg --target db-prod-02 # specific target
|
|
201
|
+
wholoads pg --all # all configured PG targets
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Writing Custom Plugins
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from wholoads.plugin import BasePlugin, Finding, Severity
|
|
208
|
+
|
|
209
|
+
class RedisPlugin(BasePlugin):
|
|
210
|
+
name = "redis"
|
|
211
|
+
description = "Find who's overloading Redis"
|
|
212
|
+
|
|
213
|
+
def collect(self, target) -> list[Finding]:
|
|
214
|
+
# Connect and gather data
|
|
215
|
+
info = self.execute("INFO ALL")
|
|
216
|
+
clients = self.execute("CLIENT LIST")
|
|
217
|
+
slowlog = self.execute("SLOWLOG GET 20")
|
|
218
|
+
|
|
219
|
+
findings = []
|
|
220
|
+
# Analyze and produce findings
|
|
221
|
+
findings.append(Finding(
|
|
222
|
+
severity=Severity.RED,
|
|
223
|
+
category="memory",
|
|
224
|
+
title="Big key detected",
|
|
225
|
+
detail="key 'sessions:cache' is 2.1GB",
|
|
226
|
+
recommendation="Consider splitting or TTL"
|
|
227
|
+
))
|
|
228
|
+
return findings
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Drop your plugin in `~/.config/wholoads/plugins/` and it's auto-discovered.
|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
236
|
+
|
|
237
|
+
Priority areas:
|
|
238
|
+
- New plugins (Redis, MySQL, Nginx, RabbitMQ, MongoDB)
|
|
239
|
+
- Output formatters
|
|
240
|
+
- Connection methods
|
|
241
|
+
- Tests and CI
|
|
242
|
+
|
|
243
|
+
## Support the Project
|
|
244
|
+
|
|
245
|
+
If `wholoads` saves you time during incidents, consider supporting development:
|
|
246
|
+
|
|
247
|
+
<a href="https://ko-fi.com/wholoads"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support on Ko-fi"></a>
|
|
248
|
+
|
|
249
|
+
[](https://github.com/sponsors/USERNAME)
|
|
250
|
+
|
|
251
|
+
You can also:
|
|
252
|
+
- ⭐ Star the repo — it helps visibility
|
|
253
|
+
- 🐛 Report bugs and request features
|
|
254
|
+
- 📝 Write a plugin for your favorite system
|
|
255
|
+
- 📣 Share with colleagues who debug infrastructure
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT — use it however you want.
|