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.
@@ -0,0 +1,4 @@
1
+ # Funding platforms for wholoads
2
+ github: USERNAME
3
+ ko_fi: wholoads
4
+ # custom: ["https://buymeacoffee.com/wholoads"]
@@ -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
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/wholoads)](https://pypi.org/project/wholoads/)
52
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
53
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](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
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/USERNAME?style=social)](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.
@@ -0,0 +1,259 @@
1
+ # wholoads
2
+
3
+ **Find who's overloading your infrastructure. Fast.**
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/wholoads)](https://pypi.org/project/wholoads/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](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
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/USERNAME?style=social)](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.