mdbkit 0.3.0__tar.gz → 0.4.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.
Files changed (70) hide show
  1. {mdbkit-0.3.0/mdbkit.egg-info → mdbkit-0.4.0}/PKG-INFO +321 -195
  2. mdbkit-0.3.0/PKG-INFO → mdbkit-0.4.0/README.md +864 -762
  3. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit/__init__.py +1 -1
  4. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/mdbkit/analysis.py +13 -0
  5. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit/cli.py +97 -13
  6. mdbkit-0.4.0/mdbkit/compare.py +175 -0
  7. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/mdbkit/ftdc.py +22 -0
  8. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/mdbkit/parser.py +43 -0
  9. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/mdbkit/render.py +114 -0
  10. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/mdbkit/triage.py +42 -5
  11. mdbkit-0.3.0/README.md → mdbkit-0.4.0/mdbkit.egg-info/PKG-INFO +888 -738
  12. mdbkit-0.4.0/mdbkit.egg-info/SOURCES.txt +31 -0
  13. {mdbkit-0.3.0 → mdbkit-0.4.0}/pyproject.toml +1 -1
  14. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/tests/test_demo_lab.py +167 -0
  15. {mdbkit-0.3.0 → mdbkit-0.4.0}/tests/test_ftdc.py +54 -0
  16. mdbkit-0.3.0/mdbkit/analysis.py +0 -567
  17. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/__init__.py +0 -3
  18. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/advisor.py +0 -319
  19. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/analysis.py +0 -567
  20. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/cli.py +0 -655
  21. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/ftdc.py +0 -654
  22. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/parser.py +0 -156
  23. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/render.py +0 -336
  24. mdbkit-0.3.0/mdbkit/build/lib/mdbkit/triage.py +0 -810
  25. mdbkit-0.3.0/mdbkit/demo.py +0 -401
  26. mdbkit-0.3.0/mdbkit/explain.py +0 -281
  27. mdbkit-0.3.0/mdbkit/filtering.py +0 -93
  28. mdbkit-0.3.0/mdbkit/ftdc.py +0 -654
  29. mdbkit-0.3.0/mdbkit/lab.py +0 -385
  30. mdbkit-0.3.0/mdbkit/mdbkit/__init__.py +0 -3
  31. mdbkit-0.3.0/mdbkit/mdbkit/advisor.py +0 -319
  32. mdbkit-0.3.0/mdbkit/mdbkit/cli.py +0 -655
  33. mdbkit-0.3.0/mdbkit/mdbkit/demo.py +0 -401
  34. mdbkit-0.3.0/mdbkit/mdbkit/explain.py +0 -281
  35. mdbkit-0.3.0/mdbkit/mdbkit/filtering.py +0 -93
  36. mdbkit-0.3.0/mdbkit/mdbkit/lab.py +0 -385
  37. mdbkit-0.3.0/mdbkit/mdbkit/rebuild.py +0 -185
  38. mdbkit-0.3.0/mdbkit/mdbkit/report.py +0 -187
  39. mdbkit-0.3.0/mdbkit/mdbkit/scripts.py +0 -69
  40. mdbkit-0.3.0/mdbkit/parser.py +0 -156
  41. mdbkit-0.3.0/mdbkit/rebuild.py +0 -185
  42. mdbkit-0.3.0/mdbkit/render.py +0 -336
  43. mdbkit-0.3.0/mdbkit/report.py +0 -187
  44. mdbkit-0.3.0/mdbkit/scripts.py +0 -69
  45. mdbkit-0.3.0/mdbkit/tests/test_ftdc.py +0 -320
  46. mdbkit-0.3.0/mdbkit/triage.py +0 -810
  47. mdbkit-0.3.0/mdbkit.egg-info/SOURCES.txt +0 -66
  48. mdbkit-0.3.0/tests/test_demo_lab.py +0 -315
  49. mdbkit-0.3.0/tests/test_explain.py +0 -119
  50. mdbkit-0.3.0/tests/test_mdbkit.py +0 -331
  51. mdbkit-0.3.0/tests/test_rebuild_report.py +0 -160
  52. mdbkit-0.3.0/tests/test_triage.py +0 -173
  53. {mdbkit-0.3.0 → mdbkit-0.4.0}/LICENSE +0 -0
  54. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit/advisor.py +0 -0
  55. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/demo.py +0 -0
  56. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/explain.py +0 -0
  57. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/filtering.py +0 -0
  58. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/lab.py +0 -0
  59. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/rebuild.py +0 -0
  60. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/report.py +0 -0
  61. {mdbkit-0.3.0/mdbkit/build/lib → mdbkit-0.4.0}/mdbkit/scripts.py +0 -0
  62. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit.egg-info/dependency_links.txt +0 -0
  63. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit.egg-info/entry_points.txt +0 -0
  64. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit.egg-info/requires.txt +0 -0
  65. {mdbkit-0.3.0 → mdbkit-0.4.0}/mdbkit.egg-info/top_level.txt +0 -0
  66. {mdbkit-0.3.0 → mdbkit-0.4.0}/setup.cfg +0 -0
  67. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/tests/test_explain.py +0 -0
  68. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/tests/test_mdbkit.py +0 -0
  69. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/tests/test_rebuild_report.py +0 -0
  70. {mdbkit-0.3.0/mdbkit → mdbkit-0.4.0}/tests/test_triage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdbkit
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Offline toolkit for MongoDB 4.4+ structured logs: log analysis, slow-query shapes, connection churn, and deterministic index advice. A spiritual successor to mtools' log tools.
5
5
  Author: Saqib Ameen Subhan
6
6
  License: MIT
@@ -24,275 +24,267 @@ Dynamic: license-file
24
24
 
25
25
  # mdbkit
26
26
 
27
- **An offline toolkit for MongoDB structured logs — log analysis, slow-query shapes, connection churn, and deterministic index advice.**
27
+ [![CI](https://github.com/saqibameen86/mdbkit/actions/workflows/ci.yml/badge.svg)](https://github.com/saqibameen86/mdbkit/actions)
28
+ [![PyPI](https://img.shields.io/pypi/v/mdbkit.svg)](https://pypi.org/project/mdbkit/)
29
+ [![Python](https://img.shields.io/pypi/pyversions/mdbkit.svg)](https://pypi.org/project/mdbkit/)
30
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
28
31
 
29
- A spiritual successor to [mtools](https://github.com/rueckstiess/mtools)' log tools (`mloginfo`, `mlogfilter`) for the structured JSON log format MongoDB has used since 4.4 — the format mtools never supported. Built for DBAs and ops engineers running self-managed MongoDB 4.4 / 5.0 / 6.0 / 7.0 / 8.0.
32
+ **An offline toolkit for MongoDB structured logs** — slow-query analysis,
33
+ deterministic index advice, incident triage, and diagnostic-data decoding.
34
+ For MongoDB 4.4 – 8.0, from the terminal, without connecting to anything.
30
35
 
31
- > **Privacy by design: mdbkit never makes a network call.** It reads log files (or stdin) and writes to stdout. No telemetry, no phoning home, no cloud. Your logs never leave your machine. It is safe to run on air-gapped database hosts.
36
+ A spiritual successor to mtools' log tools, which never learned to read the
37
+ JSON log format introduced in 4.4.
32
38
 
33
- ## Why
39
+ ```
40
+ namespace op count cumMs docsEx scan plan shape
41
+ shop.events aggregate 29 3.2m 25,810,000 98889:1 COLLSCAN+SORT {tenantId:eq, ts:gte} sort:{ts:-1}
42
+ shop.orders find 48 1.4m 6,000,000 2976:1 COLLSCAN+SORT {status:eq, createdAt:gt}
43
+ shop.users find 31 3.7s 31 1:1 IXSCAN{email} {email:eq}
44
+ ```
34
45
 
35
- MongoDB 4.4 switched to structured JSON logging, and the beloved mtools log commands stopped working — the issue has been open since 2020. Meanwhile, index recommendations from MongoDB's Performance Advisor require a paid Atlas tier or Cloud/Ops Manager. If you run Community Edition on your own infrastructure, you're back to reading raw JSON logs with `grep` and `jq`.
46
+ Two of those need an index. One is already fine. That distinction is the
47
+ whole point.
36
48
 
37
- mdbkit fills that gap: a single, dependency-free CLI that turns structured logs into answers.
49
+ ---
38
50
 
39
- ## Try it in 30 seconds
51
+ ## Try it right now — no MongoDB required
40
52
 
41
- No MongoDB required — `mdbkit demo` writes a realistic log with a real
42
- incident in it:
53
+ `mdbkit demo` writes a realistic log containing a real incident, so you can
54
+ evaluate the tool in about a minute without touching a cluster:
43
55
 
44
56
  ```bash
45
57
  pip install mdbkit
46
- mdbkit demo --with-extras -o demo.log
47
58
 
48
- mdbkit loginfo demo.log
49
- mdbkit queries demo.log
50
- mdbkit triage demo.log --window 0 --no-sysprobe
59
+ mdbkit demo --with-extras -o demo.log # a log + indexes.json, schema.json, explain.json
60
+
61
+ mdbkit loginfo demo.log # what is in this log?
62
+ mdbkit queries demo.log # which query shapes cost the most?
63
+ mdbkit triage demo.log --window 0 # what went wrong, and when?
64
+ mdbkit connections demo.log # who connected, and did anyone fail to?
51
65
  mdbkit advise demo.log --indexes indexes.json --schema schema.json
66
+ mdbkit explain explain.json # read a saved explain plan
52
67
  ```
53
68
 
54
- You will see a connection storm, a replica set election, an index build, and
55
- a collection scan burning 47 million document reads — then the candidate
56
- index that fixes it.
69
+ The generated log contains a connection storm from one client, a replica set
70
+ election, an index build, five failed logins from a service account, and an
71
+ aggregation burning 25 million document reads to return 261 documents — then
72
+ `advise` tells you which index fixes it.
73
+
74
+ Output is deterministic: the same `--seed` always produces the same log, so a
75
+ demo behaves identically every time. Scenarios are `incident`, `healthy` (the
76
+ control case — useful for seeing what "nothing wrong" looks like) and `mixed`.
77
+
78
+ Ready for a real server? Jump to [the workflows](#the-four-questions-it-answers).
79
+
80
+ ---
81
+
82
+ ## Is it safe to run on a production server?
83
+
84
+ This is the right question to ask of any tool someone hands you. The honest
85
+ answer, and how to check it yourself.
86
+
87
+ **What mdbkit never does:**
88
+
89
+ | | |
90
+ |---|---|
91
+ | Connect to your database | Analysis commands read **files**. There is no driver, no URI, no connection. |
92
+ | Send anything anywhere | There is no network code at all. No telemetry, no update check, no crash reporting. |
93
+ | Change anything | It is strictly read-only. Where an action would help, it **prints the command** for you to review and run. |
94
+ | Execute what it reads | Log lines and explain files are parsed as data with `json.loads`. Nothing is ever evaluated. |
95
+ | Pull in dependencies | Zero runtime dependencies. Nothing in the supply chain but the Python standard library. |
96
+
97
+ **Verify it yourself in 60 seconds** — this is a small, dependency-free
98
+ codebase specifically so that you can:
99
+
100
+ ```bash
101
+ # 1. No network, no shell-outs, no eval anywhere in the analysis code
102
+ pip show -f mdbkit | head -3
103
+ grep -rn "socket\|urllib\|requests\|http\|eval(\|exec(" $(python -c "import mdbkit,os;print(os.path.dirname(mdbkit.__file__))")
104
+
105
+ # 2. Confirm it has no dependencies
106
+ pip show mdbkit | grep Requires
107
+
108
+ # 3. Watch it make no connections while it runs (Linux)
109
+ strace -f -e trace=network mdbkit queries mongod.log 2>&1 | grep -c socket
110
+ ```
111
+
112
+ The grep returns nothing for every analysis module. The only file that starts
113
+ a process is `lab.py`, which exists to create a *throwaway test cluster* and
114
+ is documented as an explicit exception below.
115
+
116
+ **What it does read:** the log file you point it at; optionally
117
+ `diagnostic.data` (metrics only, never documents); optionally `indexes.json`
118
+ and `schema.json` that **you** generate with scripts mdbkit prints for you to
119
+ inspect first. On the database host it also reads `/proc` and calls `statvfs`
120
+ for disk and memory figures — nothing that leaves the machine.
121
+
122
+ **What leaves your machine: nothing.** There is no server to send it to.
123
+
124
+ **Still cautious?** That is reasonable. Run `mdbkit demo` first and see what
125
+ the output looks like on synthetic data, or `mdbkit lab` to try it against a
126
+ disposable local cluster before you point it at anything real. Both exist for
127
+ exactly this reason.
128
+
129
+ Full detail: [SECURITY.md](SECURITY.md).
130
+
131
+ ---
57
132
 
58
133
  ## Install
59
134
 
60
- **Recommended on any Linux/Mac/Windows:**
135
+ **Most systems:**
61
136
  ```bash
62
137
  pip install mdbkit
63
138
  ```
64
139
 
65
140
  **Ubuntu 20.04 / Debian / Amazon Linux 2 (Python 3.8 hosts):**
66
141
  ```bash
67
- # Step 1: install pipx (manages isolated Python tool environments)
68
- sudo apt install pipx # Ubuntu/Debian
69
- # or: sudo dnf install pipx # RHEL/Rocky/Amazon Linux
70
-
71
- # Step 2: install mdbkit
142
+ sudo apt install pipx # or: sudo dnf install pipx
72
143
  pipx install mdbkit
73
-
74
- # Step 3: if pipx is not on your PATH yet
75
144
  pipx ensurepath && source ~/.bashrc
76
145
  ```
77
146
 
78
- **If you get "externally-managed-environment" on modern Ubuntu (23.04+):**
147
+ **Modern Ubuntu/Debian complaining about "externally-managed-environment":**
79
148
  ```bash
80
149
  pip install mdbkit --break-system-packages
81
150
  ```
82
151
 
83
- **Air-gapped database hosts (no internet access):**
152
+ **Air-gapped database hosts:**
84
153
  ```bash
85
- # On a connected machine, download the wheel file
86
- pip download mdbkit -d ./wheels
87
-
88
- # Copy the ./wheels folder to the database host, then run
154
+ pip download mdbkit -d ./wheels # on a connected machine
155
+ # copy ./wheels across, then:
89
156
  pip install --no-index --find-links ./wheels mdbkit
90
157
  ```
91
158
 
92
- **Upgrading to a newer version:**
159
+ **Upgrading:**
93
160
  ```bash
94
- pip install --upgrade mdbkit # if installed with pip
95
- pipx upgrade mdbkit # if installed with pipx
96
- mdbkit --version # confirm
161
+ pip install --upgrade mdbkit # or: pipx upgrade mdbkit
162
+ mdbkit --version
97
163
  ```
98
- mdbkit never updates itself and never checks for updates — it makes no network
99
- calls at all. Upgrades are always explicit.
100
164
 
101
- Requires Python 3.8+. Zero runtime dependencies — safe to install on production hosts.
165
+ Requires Python 3.8+. mdbkit never updates itself and never checks for
166
+ updates — upgrades are always explicit.
102
167
 
103
- > mdbkit is a Python package distributed via PyPI. It is **not** available
104
- > via `apt install`, `dnf install`, or `yum install` — use `pip` or `pipx`.
168
+ > mdbkit is a Python package on PyPI. It is **not** in `apt`/`dnf`/`yum`.
105
169
 
106
- ## Quick start
107
-
108
- ```bash
109
- # Overall log summary: versions, restarts, connection counts, error/warning totals
110
- mdbkit loginfo /var/log/mongodb/mongod.log
111
-
112
- # Slow queries grouped by query shape (literals stripped), ranked by total time
113
- mdbkit queries mongod.log
114
- mdbkit queries mongod.log --sort scanRatio --limit 10 --json
115
-
116
- # Columns: cumMs = time summed across ALL occurrences of that shape (not one
117
- # query); docsEx = documents examined; scan = examined per doc returned;
118
- # plan = the plan MongoDB chose (COLLSCAN / IXSCAN{fields} / +SORT)
170
+ ---
119
171
 
120
- # Connection churn by source IP, appName, and driver
121
- mdbkit connections mongod.log
172
+ ## The four questions it answers
122
173
 
123
- # Filter raw log lines (output stays valid logv2 JSON — chainable)
124
- mdbkit filter mongod.log --slow 500 --component COMMAND --ns shop.orders
125
- mdbkit filter mongod.log --severity E --last 20 # 20 most recent errors
126
- mdbkit filter mongod.log --slow 200 --limit 50 # first 50 matches only
174
+ Every command reads files or stdin and accepts several files or a glob, so
175
+ rotated logs work as one stream: `mdbkit queries "mongod.log*"`.
127
176
 
128
- # Time ranges — with or without a timezone offset
129
- mdbkit filter mongod.log --from 2026-07-01T08:00:00+04:00 --to 2026-07-01T09:00:00+04:00
130
- mdbkit filter mongod.log --from 2026-07-01T08:00:00Z # UTC
131
- mdbkit filter mongod.log --from 2026-07-01T08:00:00 # log's own timezone
132
- mdbkit filter mongod.log --from 2026-07-01 | mdbkit queries -
177
+ ### 1. Why is my database slow?
133
178
 
134
- # Rotated/compressed logs work directly
135
- mdbkit queries mongod.log.2.gz
179
+ ```bash
180
+ mdbkit queries mongod.log # shapes ranked by total time
181
+ mdbkit queries mongod.log --sort scanRatio # worst examined:returned first
182
+ mdbkit queries mongod.log --shape 1 # full detail on one shape
136
183
  ```
137
184
 
138
- ### Step 1 — Export your indexes and schema (recommended)
185
+ `cumMs` is time summed across **all** occurrences of a shape, not one query.
186
+ `scan` is documents examined per document returned — `1:1` is healthy,
187
+ `98889:1` is a missing index. `plan` shows what MongoDB actually chose.
139
188
 
140
- mdbkit never connects to your database directly. Instead it prints small
141
- `mongosh` scripts you run yourself, so you can inspect exactly what they do
142
- before running them. The exports contain **field names and types only — no
143
- document values.**
189
+ ### 2. What index would fix it?
144
190
 
145
- **Generate the export scripts:**
146
191
  ```bash
192
+ # Optional but much sharper: export what already exists.
147
193
  mdbkit export-script indexes > export_indexes.js
148
194
  mdbkit export-script schema > export_schema.js
149
- ```
150
-
151
- **Run them against your database** (replace with your actual host, port, and credentials):
152
- ```bash
153
- # Basic (local, no auth):
154
- mongosh --quiet "mongodb://localhost/yourdb" export_indexes.js > indexes.json
155
- mongosh --quiet "mongodb://localhost/yourdb" export_schema.js > schema.json
156
195
 
157
- # With authentication (typical production setup):
158
- mongosh --quiet \
159
- --host your_db_host \
160
- --port 27017 \
161
- --username your_username \
162
- --password your_password \
196
+ mongosh --quiet --host your_db_host --port 27017 \
197
+ --username your_username --password your_password \
163
198
  --authenticationDatabase admin \
164
- --eval "$(cat export_indexes.js)" > indexes.json
199
+ --eval "$(cat export_indexes.js)" > indexes.json # repeat for schema
165
200
 
166
- mongosh --quiet \
167
- --host your_db_host \
168
- --port 27017 \
169
- --username your_username \
170
- --password your_password \
171
- --authenticationDatabase admin \
172
- --eval "$(cat export_schema.js)" > schema.json
201
+ mdbkit advise mongod.log --indexes indexes.json --schema schema.json --ns shop.orders
173
202
  ```
174
203
 
175
- > In these examples, replace `yourdb` with your actual database name (e.g. `shop`, `myapp`).
176
- > The `--authenticationDatabase` is usually `admin` unless you use per-db auth.
204
+ Every recommendation states the evidence it reasoned from, a confidence
205
+ level, the caveats, and how to validate it. It says *candidate*, not
206
+ *command*, and it never tells you to drop an index.
177
207
 
178
- ### Step 2 — Run index advice
208
+ ### 3. What happened at 3am?
179
209
 
180
210
  ```bash
181
- # Basic (without index/schema context — still useful, but lower confidence):
182
- mdbkit advise mongod.log
211
+ mdbkit triage /var/log/mongodb/mongod.log # last 60 minutes by default
212
+ mdbkit triage mongod.log --window 0 # the whole file
213
+ mdbkit triage mongod.log --report incident.html # something to attach to a ticket
214
+ ```
183
215
 
184
- # Full (with context — sharper recommendations):
185
- mdbkit advise mongod.log --indexes indexes.json --schema schema.json
216
+ Cluster health, elections, connection storms, hot collections, index builds,
217
+ error clusters, slow-query peaks — plus disk, memory, CPU and FTDC metrics
218
+ when run on the database host. Every finding ends with the next command to
219
+ run.
186
220
 
187
- # Focus on one collection (recommended for large logs):
188
- mdbkit advise mongod.log --indexes indexes.json --schema schema.json --ns shop.orders
221
+ ### 4. Did my change actually help?
222
+
223
+ ```bash
224
+ mdbkit compare before.log --after after.log
189
225
  ```
190
226
 
191
- Sample output:
192
227
  ```
193
- [1] shop.orders — confidence: HIGH
194
- query shape : {createdAt:gt, status:eq} sort:{createdAt:-1}
195
- candidate : { status: 1, createdAt: -1 }
196
- evidence : COLLSCAN observed in planSummary
197
- evidence : examined 251,400 docs to return 73 (3444:1)
198
- caveat : Every index adds write and storage overhead ...
199
- validate : Re-run the query with .explain('executionStats') ...
228
+ slow-query time DOWN 32% (6.0m -> 4.1m across compared shapes)
229
+ shapes: 1 improved, 0 regressed, 0 new, 0 gone, 4 unchanged
230
+
231
+ IMPROVED
232
+ shop.orders {createdAt:gt, status:eq} sort:{createdAt:-1}
233
+ mean 1.7s -> 33ms (-98%) scan 2976:1 -> 1:1 [COLLSCAN -> index, in-memory sort gone]
200
234
  ```
201
235
 
202
- With `--indexes`, mdbkit checks candidates against your existing indexes —
203
- flagging when an existing index should already cover the query (it flags, never
204
- auto-drops). With `--schema`, it warns about array (multikey) fields,
205
- low-cardinality booleans, and field-name typos, and adjusts confidence
206
- accordingly.
236
+ The natural follow-up to `advise`: you created the index, a day passed, and
237
+ this tells you whether it worked.
207
238
 
208
- ### Incident triage (beta)
239
+ **Bonus — who connected?**
209
240
 
210
241
  ```bash
211
- mdbkit triage /var/log/mongodb/mongod.log # last 60 minutes (default)
212
- mdbkit triage mongod.log --window 30 # last 30 minutes
213
- mdbkit triage mongod.log --window 0 # whole file
214
- mdbkit triage mongod.log --dbpath /data/db # if auto-discovery misses it
215
- mdbkit triage mongod.log --no-sysprobe # analyzing a log copied off-host
242
+ mdbkit connections mongod.log
216
243
  ```
217
244
 
218
- **Defaults to the last 60 minutes of log time**, because triage is for
219
- incidents happening now or just finished. In one command:
245
+ Per-IP churn with first/last seen, plus an authenticated-users table showing
246
+ successful and failed logins per account and when each last authenticated —
247
+ the question that starts most access incidents.
220
248
 
221
- - **cluster health** — this node's replica set role, every peer's last known
222
- state, heartbeat failures, and whether the node is still serving
223
- - restarts, error clusters, election/stepdown events
224
- - connection storms — with the peak minute and the top source IPs
225
- - slow-query volume and the peak minute, so you know *when* it hurt
226
- - COLLSCAN share of slow operations (the missing-index signal)
227
- - the hot collection plus its top three query shapes inline
228
- - index-build activity (a common cause of surprise load)
229
- - slow checkpoints, cache eviction pressure, flow control
230
- - disk / memory / CPU load, and the running mongod's RSS and uptime
231
-
232
- When run on the database host, mdbkit finds `dbPath` automatically — from the
233
- log's startup line, or the running `mongod` process, or `/etc/mongod.conf`,
234
- or common defaults — so the disk check works even when the current log has no
235
- startup event. **`diagnostic.data` lives inside the dbPath, so it is picked up
236
- automatically too**: you only need `--ftdc` to point somewhere else, such as a
237
- directory copied off another host. All probing is stdlib-only (`/proc`,
238
- `statvfs`); no shell-outs, nothing leaves the machine.
239
-
240
- Cluster health is derived entirely from the log — no connection to the
241
- database. It reports what the node last said about itself and its peers, which
242
- is the honest limit of an offline tool, and enough to answer "is this node
243
- serving, and what does it think of the others?"
244
-
245
- Read-only: it never connects to the database, and every finding ends with a
246
- next step for a human to review. Detectors marked beta are pattern-matched and
247
- clearly labeled while broader validation is pending.
249
+ ---
248
250
 
249
- ### Explain-plan analysis
251
+ ## Trying it against a real cluster
250
252
 
251
- Got a slow query in hand rather than a log? Save its explain output and ask
252
- mdbkit what's wrong:
253
+ `mdbkit lab` starts a **disposable local MongoDB** so you can test against a
254
+ real server without touching anything that matters.
253
255
 
254
256
  ```bash
255
- # in mongosh: EJSON.stringify(db.orders.find({...}).sort({...}).explain("executionStats"))
256
- mdbkit explain explain.json
257
+ mdbkit lab start # 3-node replica set on 127.0.0.1:28110-28112
258
+ mdbkit lab seed # 50k documents + a deliberately mixed workload
259
+ mdbkit queries $(mdbkit lab logs | head -1)
260
+ mdbkit lab destroy --yes # remove it entirely
257
261
  ```
258
262
 
259
- You get the plan chain (`SORT -> COLLSCAN`), the examined/returned math, plain-
260
- English verdicts (full collection scan, blocking in-memory sort, weakly
261
- selective index, covered query), and — when the plan needs help — the same
262
- evidence-backed candidate index the advisor would produce. Works with find and
263
- aggregate explains, classic and SBE (6.0+) plans, and sharded winning plans.
264
-
265
- ### Design principles
263
+ It binds to localhost only, uses ports far from 27017 so it can never be
264
+ confused with a real deployment, and refuses to touch any directory it did
265
+ not create. It is the one command that starts external processes — see
266
+ [SECURITY.md](SECURITY.md).
266
267
 
267
- * **Deterministic.** Same log in, same advice out. Rules, not AI. Every
268
- recommendation shows its evidence and its rule of reasoning.
269
- * **Candidates, not commands.** mdbkit never tells you to blindly run
270
- `createIndex`, and never advises dropping an index.
271
- * **Honest about uncertainty.** Shapes seen once are labeled low-confidence;
272
- `$or`, `$regex`, `$in` and low-selectivity operators carry explicit caveats.
273
- * **Offline, always.** No network code exists in this codebase.
268
+ Full options and more examples: [`mdbkit lab`](#mdbkit-lab) in the reference.
274
269
 
275
- ## What it reads
276
-
277
- Any MongoDB 4.4+ structured log: `mongod.log`, `mongos` logs, rotated `.gz`
278
- files, or stdin (`-`). Slow-query lines (`"msg":"Slow query"`) are logged by
279
- default for operations over `slowms` (100 ms); lower `slowms` or enable
280
- profiling level 1 to capture more:
281
-
282
- ```js
283
- db.setProfilingLevel(1, { slowms: 50 })
284
- ```
285
-
286
- Pre-4.4 plain-text logs are detected and politely refused — for those, the
287
- original mtools still works.
270
+ ---
288
271
 
289
272
  ## Command reference
290
273
 
291
274
  Every command reads files or stdin and writes to stdout. `--help` works on any
292
275
  command (`mdbkit queries --help`). Global: `mdbkit --version`.
293
276
 
294
- All commands that read a log accept a path to a `.log` file, a rotated `.gz`
295
- file, or `-` for stdin.
277
+ All commands that read a log accept one or more paths, a shell glob, a
278
+ rotated `.gz` file, or `-` for stdin. Several files are read as a single
279
+ stream in filename order, which matches MongoDB's rotation naming:
280
+
281
+ ```bash
282
+ mdbkit queries mongod.log # one file
283
+ mdbkit queries mongod.log.1 mongod.log # explicit list
284
+ mdbkit queries "mongod.log*" # glob (quote it)
285
+ mdbkit queries /var/log/mongodb/mongod.log.*.gz # compressed archives
286
+ cat mongod.log | mdbkit queries - # stdin
287
+ ```
296
288
 
297
289
  ---
298
290
 
@@ -341,6 +333,33 @@ query with different parameters is counted once.
341
333
  mdbkit queries mongod.log
342
334
  mdbkit queries mongod.log --sort scanRatio --limit 10
343
335
  mdbkit queries mongod.log --min-ms 500 --json
336
+ mdbkit queries "mongod.log*" # rotated logs as one stream
337
+ mdbkit queries mongod.log --shape 1 # drill into the worst offender
338
+ ```
339
+
340
+ `--shape N` expands one row of the table:
341
+
342
+ ```
343
+ namespace : shop.events
344
+ shape : {tenantId:eq, ts:gte} sort:{ts:-1}
345
+
346
+ occurrences : 29
347
+ total time : 3.2m
348
+ mean / max : 6.6s / 8.9s
349
+ docs examined : 25,810,000
350
+ docs returned : 261
351
+ scan ratio : 98889 examined per document returned
352
+
353
+ plans observed
354
+ COLLSCAN 29x
355
+
356
+ flags
357
+ COLLSCAN — no index used for at least one execution
358
+ in-memory SORT — results sorted after retrieval
359
+
360
+ client applications
361
+ ReportWorker 15x
362
+ OrderService 7x
344
363
  ```
345
364
 
346
365
  ---
@@ -693,6 +712,66 @@ mdbkit advise $(mdbkit lab logs | head -1)
693
712
  mdbkit lab destroy --yes # remove everything
694
713
  ```
695
714
 
715
+ **`mdbkit lab logs`** prints the log file path of every node, one per line,
716
+ so it composes with the other commands instead of you hunting for paths:
717
+
718
+ ```bash
719
+ mdbkit lab logs
720
+ # /home/you/.mdbkit-lab/node0/mongod.log
721
+ # /home/you/.mdbkit-lab/node1/mongod.log
722
+ # /home/you/.mdbkit-lab/node2/mongod.log
723
+
724
+ mdbkit queries $(mdbkit lab logs | head -1) # just the primary
725
+ mdbkit triage $(mdbkit lab logs) # all three as one stream
726
+ mdbkit loginfo $(mdbkit lab logs | sed -n 2p) # a specific secondary
727
+ ```
728
+
729
+ **A single node**, when you do not need replication — faster to start and it
730
+ does not require `mongosh`:
731
+
732
+ ```bash
733
+ mdbkit lab start --standalone
734
+ mdbkit lab seed --docs 5000
735
+ mdbkit queries $(mdbkit lab logs)
736
+ mdbkit lab destroy --yes
737
+ ```
738
+
739
+ **Several labs side by side**, for example to compare two MongoDB versions or
740
+ keep one running while you break another:
741
+
742
+ ```bash
743
+ mdbkit lab start --dir ~/lab-a --port 28110
744
+ mdbkit lab start --dir ~/lab-b --port 28210 --standalone
745
+
746
+ mdbkit lab status --dir ~/lab-a
747
+ mdbkit lab destroy --dir ~/lab-b --yes
748
+ ```
749
+
750
+ **Pause without losing data** — `stop` leaves the data directory intact so
751
+ you can start again later; only `destroy` deletes anything:
752
+
753
+ ```bash
754
+ mdbkit lab stop # nodes down, data kept
755
+ mdbkit lab start # back up with the same data
756
+ mdbkit lab status # ports, pids, running or not
757
+ ```
758
+
759
+ **A complete before/after experiment**, which is what `lab` is really for:
760
+
761
+ ```bash
762
+ mdbkit lab start && mdbkit lab seed
763
+ cp $(mdbkit lab logs | head -1) before.log
764
+
765
+ mongosh --port 28110 --eval \
766
+ 'db.getSiblingDB("shop").orders.createIndex({status:1, createdAt:-1})'
767
+
768
+ mdbkit lab seed # run the workload again with the index
769
+ cp $(mdbkit lab logs | head -1) after.log
770
+
771
+ mdbkit compare before.log --after after.log
772
+ mdbkit lab destroy --yes
773
+ ```
774
+
696
775
  `seed` runs indexed point lookups alongside deliberately unindexed queries —
697
776
  an equality-plus-range-plus-sort with no supporting index, an aggregation
698
777
  that scans the collection, and updates whose predicate has no index — so the
@@ -704,6 +783,44 @@ It is a laptop and scratch-VM tool, not a deployment tool.
704
783
 
705
784
  ---
706
785
 
786
+ ### `mdbkit compare BEFORE --after AFTER`
787
+
788
+ Diffs query shapes between two logs and reports what improved, what
789
+ regressed, and what is new. The natural follow-up to `advise`: you created an
790
+ index, a day passed, and this answers whether it worked.
791
+
792
+ | Option | Default | Description |
793
+ |---|---|---|
794
+ | `--after FILE...` | required | The log(s) from after the change |
795
+ | `--ns NAMESPACE` | all | Compare only one namespace |
796
+ | `--min-count N` | 3 | Ignore shapes seen fewer than N times, so noise in a quiet log does not read as a regression |
797
+ | `--min-ms N` | 0 | Ignore operations faster than this |
798
+ | `--limit N` | 15 | Shapes to print (`0` = all) |
799
+ | `--include-system` | off | Include internal `admin`/`config`/`local` namespaces |
800
+ | `--report FILE` | | Write a shareable `.md` or `.html` report |
801
+ | `--json` | | Machine-readable output |
802
+
803
+ ```bash
804
+ mdbkit compare before.log --after after.log
805
+ mdbkit compare before.log --after after.log --ns shop.orders
806
+ mdbkit compare "old/mongod.log*" --after "new/mongod.log*" --report change.html
807
+ ```
808
+
809
+ ```
810
+ slow-query time DOWN 32% (6.0m -> 4.1m across compared shapes)
811
+ shapes: 1 improved, 0 regressed, 0 new, 0 gone, 4 unchanged
812
+
813
+ IMPROVED
814
+ shop.orders {createdAt:gt, status:eq} sort:{createdAt:-1}
815
+ mean 1.7s -> 33ms (-98%) scan 2976:1 -> 1:1 [COLLSCAN -> index, in-memory sort gone]
816
+ ```
817
+
818
+ A shape counts as improved or regressed on a plan change (COLLSCAN becoming
819
+ an index scan, or the reverse), on an in-memory sort disappearing, or on mean
820
+ duration moving by more than 20%.
821
+
822
+ ---
823
+
707
824
  ### `mdbkit export-script {schema|indexes}`
708
825
 
709
826
  Prints a small `mongosh` script to stdout. **mdbkit never connects to your
@@ -723,17 +840,26 @@ mdbkit export-script schema > export_schema.js
723
840
  Terminal output is and will remain first-class — this tool is built for the
724
841
  Linux box the database actually runs on.
725
842
 
726
- **Shipped in v0.3:** `demo` log generation and `lab` disposable clusters, on
727
- top of v0.2's FTDC decoding, incident triage, query reconstruction and
728
- shareable reports.
729
-
730
- Next up:
731
- * `mdbkit compare before.log after.log` — did the index actually help?
732
- * Multiple log files and globs in one command, for rotated logs.
733
- * Per-shape drill-down (`mdbkit queries --shape N` with full detail).
734
- * Graduating the remaining beta detectors (checkpoints, eviction, flow
735
- control) once validated against real incident logs — see
736
- `docs/TESTING-PLAYBOOK.md`. Real logs very welcome.
843
+ **Shipped in v0.4:** `compare`, rotated-log globbing, per-shape drill-down —
844
+ on top of v0.3's `demo` and `lab`, and v0.2's FTDC decoding, incident triage,
845
+ query reconstruction and shareable reports.
846
+
847
+ Next up, roughly in order:
848
+
849
+ * **Sharded clusters.** `mongos` logs are a different shape, and the classic
850
+ sharded failure — a query with no shard key fanning out to every shard — is
851
+ visible in the log. Also chunk migrations, balancer windows and jumbo
852
+ chunks. Would come with `mdbkit lab --sharded` so it can be tested.
853
+ * **Startup configuration audit.** mongod logs warnings at startup about
854
+ transparent huge pages, readahead, ulimits, NUMA and filesystem choice.
855
+ These are classic production misconfigurations and they are already in
856
+ your log — nothing new needs collecting.
857
+ * **Redundant index detection** from `indexes.json` alone: an index on
858
+ `{a: 1}` is redundant when `{a: 1, b: 1}` exists. Purely offline, no
859
+ connection, no `$indexStats` needed.
860
+ * **Confirming the FTDC-based checkpoint, eviction and flow-control
861
+ detectors** against real `diagnostic.data` — see
862
+ `docs/TESTING-PLAYBOOK.md`. Real logs and metrics very welcome.
737
863
 
738
864
  mdbkit is validated against real-world structured logs (tens of thousands of
739
865
  lines) in addition to its synthetic test fixtures.