mdbkit 0.2.1__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 (36) hide show
  1. mdbkit-0.4.0/PKG-INFO +888 -0
  2. mdbkit-0.4.0/README.md +864 -0
  3. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/__init__.py +1 -1
  4. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/analysis.py +130 -7
  5. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/cli.py +238 -13
  6. mdbkit-0.4.0/mdbkit/compare.py +175 -0
  7. mdbkit-0.4.0/mdbkit/demo.py +401 -0
  8. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/ftdc.py +22 -0
  9. mdbkit-0.4.0/mdbkit/lab.py +385 -0
  10. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/parser.py +50 -0
  11. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/render.py +154 -3
  12. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/triage.py +157 -6
  13. mdbkit-0.4.0/mdbkit.egg-info/PKG-INFO +888 -0
  14. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit.egg-info/SOURCES.txt +4 -0
  15. {mdbkit-0.2.1 → mdbkit-0.4.0}/pyproject.toml +1 -1
  16. mdbkit-0.4.0/tests/test_demo_lab.py +482 -0
  17. {mdbkit-0.2.1 → mdbkit-0.4.0}/tests/test_ftdc.py +54 -0
  18. mdbkit-0.2.1/PKG-INFO +0 -628
  19. mdbkit-0.2.1/README.md +0 -604
  20. mdbkit-0.2.1/mdbkit.egg-info/PKG-INFO +0 -628
  21. {mdbkit-0.2.1 → mdbkit-0.4.0}/LICENSE +0 -0
  22. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/advisor.py +0 -0
  23. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/explain.py +0 -0
  24. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/filtering.py +0 -0
  25. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/rebuild.py +0 -0
  26. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/report.py +0 -0
  27. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit/scripts.py +0 -0
  28. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit.egg-info/dependency_links.txt +0 -0
  29. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit.egg-info/entry_points.txt +0 -0
  30. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit.egg-info/requires.txt +0 -0
  31. {mdbkit-0.2.1 → mdbkit-0.4.0}/mdbkit.egg-info/top_level.txt +0 -0
  32. {mdbkit-0.2.1 → mdbkit-0.4.0}/setup.cfg +0 -0
  33. {mdbkit-0.2.1 → mdbkit-0.4.0}/tests/test_explain.py +0 -0
  34. {mdbkit-0.2.1 → mdbkit-0.4.0}/tests/test_mdbkit.py +0 -0
  35. {mdbkit-0.2.1 → mdbkit-0.4.0}/tests/test_rebuild_report.py +0 -0
  36. {mdbkit-0.2.1 → mdbkit-0.4.0}/tests/test_triage.py +0 -0
mdbkit-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,888 @@
1
+ Metadata-Version: 2.4
2
+ Name: mdbkit
3
+ Version: 0.4.0
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
+ Author: Saqib Ameen Subhan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/CHANGEME/mdbkit
8
+ Project-URL: Issues, https://github.com/CHANGEME/mdbkit/issues
9
+ Keywords: mongodb,logs,logv2,mtools,index,dba,slow-query
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: System :: Systems Administration
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # mdbkit
26
+
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)
31
+
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.
35
+
36
+ A spiritual successor to mtools' log tools, which never learned to read the
37
+ JSON log format introduced in 4.4.
38
+
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
+ ```
45
+
46
+ Two of those need an index. One is already fine. That distinction is the
47
+ whole point.
48
+
49
+ ---
50
+
51
+ ## Try it right now — no MongoDB required
52
+
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:
55
+
56
+ ```bash
57
+ pip install mdbkit
58
+
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?
65
+ mdbkit advise demo.log --indexes indexes.json --schema schema.json
66
+ mdbkit explain explain.json # read a saved explain plan
67
+ ```
68
+
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
+ ---
132
+
133
+ ## Install
134
+
135
+ **Most systems:**
136
+ ```bash
137
+ pip install mdbkit
138
+ ```
139
+
140
+ **Ubuntu 20.04 / Debian / Amazon Linux 2 (Python 3.8 hosts):**
141
+ ```bash
142
+ sudo apt install pipx # or: sudo dnf install pipx
143
+ pipx install mdbkit
144
+ pipx ensurepath && source ~/.bashrc
145
+ ```
146
+
147
+ **Modern Ubuntu/Debian complaining about "externally-managed-environment":**
148
+ ```bash
149
+ pip install mdbkit --break-system-packages
150
+ ```
151
+
152
+ **Air-gapped database hosts:**
153
+ ```bash
154
+ pip download mdbkit -d ./wheels # on a connected machine
155
+ # copy ./wheels across, then:
156
+ pip install --no-index --find-links ./wheels mdbkit
157
+ ```
158
+
159
+ **Upgrading:**
160
+ ```bash
161
+ pip install --upgrade mdbkit # or: pipx upgrade mdbkit
162
+ mdbkit --version
163
+ ```
164
+
165
+ Requires Python 3.8+. mdbkit never updates itself and never checks for
166
+ updates — upgrades are always explicit.
167
+
168
+ > mdbkit is a Python package on PyPI. It is **not** in `apt`/`dnf`/`yum`.
169
+
170
+ ---
171
+
172
+ ## The four questions it answers
173
+
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*"`.
176
+
177
+ ### 1. Why is my database slow?
178
+
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
183
+ ```
184
+
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.
188
+
189
+ ### 2. What index would fix it?
190
+
191
+ ```bash
192
+ # Optional but much sharper: export what already exists.
193
+ mdbkit export-script indexes > export_indexes.js
194
+ mdbkit export-script schema > export_schema.js
195
+
196
+ mongosh --quiet --host your_db_host --port 27017 \
197
+ --username your_username --password your_password \
198
+ --authenticationDatabase admin \
199
+ --eval "$(cat export_indexes.js)" > indexes.json # repeat for schema
200
+
201
+ mdbkit advise mongod.log --indexes indexes.json --schema schema.json --ns shop.orders
202
+ ```
203
+
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.
207
+
208
+ ### 3. What happened at 3am?
209
+
210
+ ```bash
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
+ ```
215
+
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.
220
+
221
+ ### 4. Did my change actually help?
222
+
223
+ ```bash
224
+ mdbkit compare before.log --after after.log
225
+ ```
226
+
227
+ ```
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]
234
+ ```
235
+
236
+ The natural follow-up to `advise`: you created the index, a day passed, and
237
+ this tells you whether it worked.
238
+
239
+ **Bonus — who connected?**
240
+
241
+ ```bash
242
+ mdbkit connections mongod.log
243
+ ```
244
+
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.
248
+
249
+ ---
250
+
251
+ ## Trying it against a real cluster
252
+
253
+ `mdbkit lab` starts a **disposable local MongoDB** so you can test against a
254
+ real server without touching anything that matters.
255
+
256
+ ```bash
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
261
+ ```
262
+
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).
267
+
268
+ Full options and more examples: [`mdbkit lab`](#mdbkit-lab) in the reference.
269
+
270
+ ---
271
+
272
+ ## Command reference
273
+
274
+ Every command reads files or stdin and writes to stdout. `--help` works on any
275
+ command (`mdbkit queries --help`). Global: `mdbkit --version`.
276
+
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
+ ```
288
+
289
+ ---
290
+
291
+ ### `mdbkit loginfo <log>`
292
+
293
+ Overall log summary: server version, host, restarts, connections accepted,
294
+ slow-query count, warning/error counts, and a per-component line breakdown.
295
+
296
+ | Option | Description |
297
+ |---|---|
298
+ | `--json` | Machine-readable output |
299
+
300
+ ```bash
301
+ mdbkit loginfo /var/log/mongodb/mongod.log
302
+ mdbkit loginfo mongod.log.2.gz --json
303
+ ```
304
+
305
+ ---
306
+
307
+ ### `mdbkit queries <log>`
308
+
309
+ Slow queries grouped by **query shape** — literal values stripped, so the same
310
+ query with different parameters is counted once.
311
+
312
+ | Option | Default | Description |
313
+ |---|---|---|
314
+ | `--sort FIELD` | `totalMs` | Order by `totalMs`, `count`, `mean`, `max`, `docsExamined`, or `scanRatio` |
315
+ | `--limit N` | all | Show only the top N shapes |
316
+ | `--min-ms N` | 0 | Ignore operations faster than N milliseconds |
317
+ | `--include-system` | off | Include internal `admin`/`config`/`local` namespaces (hidden by default — they are server housekeeping, not your workload) |
318
+ | `--report FILE` | | Write a shareable `.md` or `.html` report instead (see [Shareable reports](#shareable-reports----report-file)) |
319
+ | `--json` | | Machine-readable output |
320
+
321
+ **Reading the columns:**
322
+
323
+ | Column | Meaning |
324
+ |---|---|
325
+ | `cumMs` | Time summed across **all** occurrences of that shape — not one query |
326
+ | `mean` / `max` | Per-occurrence average and worst case |
327
+ | `docsEx` | Documents examined, summed across all occurrences |
328
+ | `scan` | Documents examined per document returned. `1:1` is ideal; `3444:1` means a missing or weak index |
329
+ | `plan` | The plan MongoDB chose: `COLLSCAN` (no index), `IXSCAN{fields}` (index used), `IDHACK` (`_id` lookup), `+SORT` (in-memory sort). `?` = the plan was not recorded on that line |
330
+ | `shape` | Fields and operators queried, with the sort |
331
+
332
+ ```bash
333
+ mdbkit queries mongod.log
334
+ mdbkit queries mongod.log --sort scanRatio --limit 10
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
363
+ ```
364
+
365
+ ---
366
+
367
+ ### `mdbkit connections <log>`
368
+
369
+ Connection churn and **who authenticated**: totals, peak concurrent count,
370
+ per-source-IP breakdown with first/last seen, the client applications and
371
+ drivers, and a per-user table.
372
+
373
+ | Option | Description |
374
+ |---|---|
375
+ | `--json` | Machine-readable output |
376
+
377
+ ```bash
378
+ mdbkit connections mongod.log
379
+ ```
380
+
381
+ ```
382
+ source ip accepted ended first seen last seen appName
383
+ ---------- -------- ----- ------------------- ------------------- ------------
384
+ 10.20.9.77 220 0 2026-07-01 08:49:30 2026-07-01 08:49:30 checkout-api
385
+ 10.20.4.11 4 1 2026-07-01 08:00:15 2026-07-01 09:29:30 OrderService
386
+
387
+ authenticated users
388
+ user auth db ok failed last authenticated from
389
+ ------------ ------- --- ------ ------------------- -----------
390
+ svc_checkout admin 221 0 2026-07-01 08:49:30 10.20.9.77
391
+ etl_batch admin 0 5 2026-07-01 08:51:54 10.20.11.40
392
+
393
+ etl_batch: 5 failed authentication(s) — last error: AuthenticationFailed
394
+ ```
395
+
396
+ This answers the question that starts most access incidents: *did that
397
+ account connect, from where, and when last?* If the log shows no
398
+ authentication events at all, mdbkit says so — either auth is disabled, or
399
+ the window contains no new logins because clients are reusing connections.
400
+
401
+ ---
402
+
403
+ ### `mdbkit filter <log>`
404
+
405
+ Streams **matching raw log lines** to stdout. Output stays valid logv2 JSON, so
406
+ it chains with other tools (including mdbkit itself).
407
+
408
+ | Option | Description |
409
+ |---|---|
410
+ | `--component NAME` | `COMMAND`, `NETWORK`, `REPL`, `STORAGE`, `INDEX`, `WRITE`, `QUERY`, `CONTROL`, … |
411
+ | `--severity S` | `I` info, `W` warning, `E` error, `F` fatal |
412
+ | `--ns NAMESPACE` | Exact namespace, e.g. `shop.orders` |
413
+ | `--slow N` | Only operations with `durationMillis` >= N |
414
+ | `--from TIMESTAMP` | Lower time bound (inclusive) |
415
+ | `--to TIMESTAMP` | Upper time bound (inclusive) |
416
+ | `--msg TEXT` | Substring match on the message field |
417
+ | `--limit N` | Print only the **first** N matches |
418
+ | `--last N` | Print only the **last** N matches — usually what you want during an incident |
419
+ | `--as-explain` | Rebuild each matching slow query as a runnable `mongosh` `.explain()` command instead of printing the raw log line |
420
+ | `--explain-script` | With `--as-explain`, wrap in `EJSON.stringify()` plus usage comments so it can be saved as a `.js` file |
421
+
422
+ **Timestamp formats accepted** by `--from` / `--to`:
423
+
424
+ ```
425
+ 2026-07-01T08:00:00+04:00 with an explicit offset (production logs)
426
+ 2026-07-01T08:00:00Z UTC
427
+ 2026-07-01T08:00:00 no offset — read as the log's own timezone
428
+ 2026-07-01 08:00:00 space instead of T
429
+ 2026-07-01T08:00 minute precision
430
+ 2026-07-01 whole day
431
+ ```
432
+
433
+ ```bash
434
+ mdbkit filter mongod.log --severity E --last 20 # errors (most recent 20)
435
+ mdbkit filter mongod.log --severity F # fatal — always investigate
436
+ mdbkit filter mongod.log --severity W --last 50 # warnings
437
+ mdbkit filter mongod.log --component REPL --msg election
438
+ mdbkit filter mongod.log --slow 500 --ns shop.orders --limit 50
439
+ mdbkit filter mongod.log --from 2026-07-01T14:30:00+04:00 --to 2026-07-01T15:00:00+04:00
440
+ mdbkit filter mongod.log --slow 100 | mdbkit queries -
441
+ ```
442
+
443
+ **From a slow query in the log to an explain plan**, without hand-writing the
444
+ query — `--as-explain` rebuilds the command that ran:
445
+
446
+ ```bash
447
+ # See the actual commands behind your slowest operations
448
+ mdbkit filter mongod.log --ns shop.orders --slow 500 --last 3 --as-explain
449
+
450
+ # Or produce a runnable script, get the plan, and analyze it
451
+ mdbkit filter mongod.log --slow 500 --last 1 --as-explain --explain-script > q.js
452
+ mongosh --quiet --host your_db_host --username your_username \\
453
+ --password your_password --authenticationDatabase admin \\
454
+ --eval "$(cat q.js)" > explain.json
455
+ mdbkit explain explain.json
456
+ ```
457
+
458
+ > Rebuilt commands contain the **real values** from your log (not redacted
459
+ > shapes) — treat them as sensitive.
460
+
461
+ ---
462
+
463
+ ### `mdbkit advise <log>`
464
+
465
+ Deterministic **candidate** index recommendations from observed slow-query
466
+ shapes, using the ESR guideline (Equality → Sort → Range). Rules, not AI: the
467
+ same log always produces the same advice.
468
+
469
+ | Option | Default | Description |
470
+ |---|---|---|
471
+ | `--indexes FILE` | | `indexes.json` from `mdbkit export-script indexes` — enables overlap checks against existing indexes |
472
+ | `--schema FILE` | | `schema.json` from `mdbkit export-script schema` — enables field-type caveats and confidence adjustment |
473
+ | `--ns NAMESPACE` | all | Only advise on one namespace (recommended on large logs) |
474
+ | `--limit N` | 10 | Show only the top N recommendations (`0` = all) |
475
+ | `--min-ms N` | 0 | Ignore operations faster than N milliseconds |
476
+ | `--min-count N` | 1 | Only advise on shapes seen at least N times |
477
+ | `--include-system` | off | Include internal `admin`/`config`/`local` namespaces |
478
+ | `--json` | | Machine-readable output |
479
+
480
+ Each recommendation carries a candidate key pattern, the evidence behind it, a
481
+ confidence level, caveats, and a validation step. mdbkit never advises dropping
482
+ an index — at most it flags an overlap to investigate.
483
+
484
+ ```bash
485
+ mdbkit advise mongod.log
486
+ mdbkit advise mongod.log --indexes indexes.json --schema schema.json
487
+ mdbkit advise mongod.log --ns shop.orders --limit 3
488
+ ```
489
+
490
+ ---
491
+
492
+ ### `mdbkit explain <file>`
493
+
494
+ Analyzes a saved `explain("executionStats")` document: the plan chain, the
495
+ examined-vs-returned math, plain-English verdicts, and — when the plan needs
496
+ help — a candidate index from the same advisor engine.
497
+
498
+ | Option | Description |
499
+ |---|---|
500
+ | `--indexes FILE` | Overlap check against existing indexes |
501
+ | `--schema FILE` | Field-type caveats |
502
+ | `--json` | Machine-readable output |
503
+
504
+ **Full example.** Get a plan for a query and analyze it:
505
+
506
+ ```bash
507
+ # 1. Capture the plan (adjust host/credentials for your deployment)
508
+ mongosh --quiet \\
509
+ --host your_db_host \\
510
+ --port 27017 \\
511
+ --username your_username \\
512
+ --password your_password \\
513
+ --authenticationDatabase admin \\
514
+ --eval 'EJSON.stringify(db.getSiblingDB("shop").orders.find({status:"open"}).sort({ts:-1}).explain("executionStats"))' \\
515
+ > explain.json
516
+
517
+ # 2. Analyze it
518
+ mdbkit explain explain.json
519
+
520
+ # 3. Sharper, with your existing indexes and sampled schema
521
+ mdbkit explain explain.json --indexes indexes.json --schema schema.json
522
+ ```
523
+
524
+ Don't want to write the query by hand? `mdbkit filter ... --as-explain`
525
+ rebuilds it from the log for you (see the `filter` section above).
526
+
527
+ Legacy `mongo` shell and Compass output containing `NumberLong(...)`,
528
+ `ISODate(...)` or `ObjectId(...)` is accepted — mdbkit unwraps those
529
+ automatically, so you do not have to re-export.
530
+
531
+ ---
532
+
533
+ ### `mdbkit triage <log>`
534
+
535
+ **"Triage" means: quickly work out what is wrong and what to look at first.**
536
+ Run this when something has gone wrong — or has just gone wrong — and you need
537
+ one screen that says what happened, how bad it is, and where to look next.
538
+ **Defaults to the last 60 minutes of log time.**
539
+
540
+ | Option | Default | Description |
541
+ |---|---|---|
542
+ | `--window N` | 60 | Analyze the last N minutes of log time; `0` = the whole file |
543
+ | `--dbpath PATH` | auto | Override the data directory used for the disk check |
544
+ | `--no-sysprobe` | off | Skip local disk/memory/CPU probes — use when analyzing a log copied off the host |
545
+ | `--ftdc PATH` | | `diagnostic.data` directory — adds CPU, memory, cache, queue and connection history from MongoDB's own recorder |
546
+ | `--report FILE` | | Write a shareable `.md` or `.html` report instead of terminal output |
547
+ | `--json` | | Machine-readable output |
548
+
549
+ ```bash
550
+ mdbkit triage /var/log/mongodb/mongod.log
551
+ mdbkit triage mongod.log --window 30
552
+ mdbkit triage mongod.log --ftdc /var/lib/mongodb/diagnostic.data
553
+ mdbkit triage mongod.log --report incident.html
554
+ mdbkit triage mongod.log --window 0 --no-sysprobe
555
+ ```
556
+
557
+ ---
558
+
559
+ ### `mdbkit ftdc {summary|timeline|export} <path>`
560
+
561
+ Decodes `diagnostic.data` — **FTDC (Full-Time Diagnostic Data Capture)**, the
562
+ metrics recorder every mongod already runs. It holds CPU, memory, WiredTiger
563
+ cache, connection, queue and operation history for every node, with no
564
+ monitoring agent installed and no database connection. It is compressed BSON,
565
+ not encrypted; mdbkit decodes it offline.
566
+
567
+ | Action | Description |
568
+ |---|---|
569
+ | `summary` | min / avg / max / last per metric, plus per-second rates for counters |
570
+ | `timeline` | Values bucketed over time — shows *when* something spiked |
571
+ | `export` | CSV to stdout, for a spreadsheet or your own tooling |
572
+
573
+ | Option | Default | Description |
574
+ |---|---|---|
575
+ | `--last DURATION` | `4h` | Analyze only the most recent window — `90m`, `4h`, `2d` |
576
+ | `--all` | off | Analyze the entire history (see the performance note below) |
577
+ | `--metric LABEL` | all | Restrict to one metric (repeatable), e.g. `--metric conns.current` |
578
+ | `--step SECONDS` | 60 | Timeline bucket size |
579
+ | `--from` / `--to` | | Explicit time bounds (same formats as `filter`) |
580
+ | `--json` | | Machine-readable output |
581
+
582
+ **Performance note.** `diagnostic.data` can hold weeks of per-second samples —
583
+ a few hundred megabytes covering thousands of chunks and several thousand
584
+ metrics each. Decoding all of it is CPU-bound and takes minutes, so these
585
+ commands **default to the last 4 hours** and skip older chunks before
586
+ decompressing them. On a 250 MB directory that is the difference between about
587
+ a second and about a minute. Use `--last`/`--from`/`--to` to move the window,
588
+ and `--all` when you really do want the whole history.
589
+
590
+ ```bash
591
+ mdbkit ftdc summary /var/lib/mongodb/diagnostic.data
592
+ mdbkit ftdc timeline diagnostic.data --metric conns.current --step 300
593
+ mdbkit ftdc export diagnostic.data > metrics.csv
594
+ ```
595
+
596
+ Metric labels include `ops.*` (insert/query/update/delete/getmore/command),
597
+ `conns.current`, `conns.available`, `queue.readers`, `queue.writers`,
598
+ `cache.usedBytes`, `cache.maxBytes`, `cache.dirtyBytes`, `tickets.*`,
599
+ `mem.residentMB`, and on Linux `sys.cpu.*` and `sys.mem.availableKB`.
600
+
601
+ The data directory can be copied off the host and analyzed elsewhere — it
602
+ contains metrics only, never document contents.
603
+
604
+ ---
605
+
606
+ ### Shareable reports — `--report FILE`
607
+
608
+ `triage` and `queries` can write a self-contained report instead of printing to
609
+ the terminal — for a ticket, a handover, or a post-incident review.
610
+
611
+ ```bash
612
+ mdbkit triage mongod.log --report incident.html # styled, self-contained
613
+ mdbkit triage mongod.log --report incident.md # for tickets and PRs
614
+ mdbkit queries mongod.log --limit 20 --report slow-queries.md
615
+ ```
616
+
617
+ The format follows the file extension: `.html` or `.md`.
618
+
619
+ Markdown output looks like this:
620
+
621
+ ```markdown
622
+ # MongoDB incident triage
623
+
624
+ *window 2026-07-01 08:10 -> 09:10 · generated 2026-07-01 09:12*
625
+
626
+ ## Findings
627
+
628
+ - **[CRIT] Replica set instability** — 3 election/stepdown event(s) at 08:41:02, 08:58:14
629
+ - Starting an election, since we've seen no PRIMARY in election timeout period
630
+ - *next:* `Correlate with connection storms and slow checkpoints below`
631
+ - **[WARN] Connection storm** — 2 minute(s) at >= 60 new connections/min; peak 480 at 08:41
632
+ - 10.2.1.7: 312 in the peak minute
633
+ - *next:* `mdbkit connections <log>`
634
+ - **[OK] Errors** — No error/fatal severity lines in window.
635
+ ```
636
+
637
+ The HTML version carries the same content with a dark, print-friendly
638
+ stylesheet. It is **fully self-contained**: inline CSS, no JavaScript, no
639
+ external assets or CDN references, so it opens on an air-gapped machine and
640
+ sends nothing anywhere.
641
+
642
+ Reports contain the same information as the terminal output — query **shapes**
643
+ and metrics, never literal values from your documents.
644
+
645
+ ---
646
+
647
+ ### `mdbkit demo`
648
+
649
+ Generates a realistic MongoDB structured log so you can evaluate mdbkit — or
650
+ run a live demo — without a cluster. Output is deterministic for a given
651
+ seed, so a demo behaves identically every time, including on a projector.
652
+
653
+ | Option | Default | Description |
654
+ |---|---|---|
655
+ | `--scenario` | `mixed` | `healthy`, `incident`, or `mixed` |
656
+ | `--minutes N` | 90 | How much log time to generate |
657
+ | `--seed N` | 7 | Same seed produces byte-identical output |
658
+ | `-o, --out FILE` | stdout | Write to a file |
659
+ | `--with-extras` | off | Also write `indexes.json`, `schema.json` and `explain.json` beside the log |
660
+
661
+ ```bash
662
+ mdbkit demo -o demo.log # 90 minutes, mixed
663
+ mdbkit demo --scenario incident --minutes 30 -o incident.log
664
+ mdbkit demo --scenario healthy -o quiet.log # nothing wrong: the control case
665
+ mdbkit demo | mdbkit queries - # straight down a pipe
666
+ ```
667
+
668
+ The `incident` scenario contains an index build, a connection storm from a
669
+ single client, a replica set election, plan-executor errors, a slow
670
+ WiredTiger checkpoint, and a burst of unindexed queries afterwards — the
671
+ shape of a real bad afternoon.
672
+
673
+ ---
674
+
675
+ ### `mdbkit lab`
676
+
677
+ Starts a **disposable local MongoDB** for testing, reproducing a slow query,
678
+ or rehearsing a demo. This is the only command that starts external
679
+ processes; see [SECURITY.md](SECURITY.md) for exactly how it is bounded.
680
+
681
+ Requires `mongod` on your `PATH` (and `mongosh` to initiate the replica set
682
+ and seed data). Linux and macOS.
683
+
684
+ | Action | What it does |
685
+ |---|---|
686
+ | `start` | Create and start a replica set, print the connection string and log paths |
687
+ | `seed` | Insert sample data and run a workload with deliberately interesting queries |
688
+ | `status` | Show ports, pids and whether each node is running |
689
+ | `logs` | Print the log file paths, ready to pipe into other commands |
690
+ | `stop` | Stop the nodes, keep the data |
691
+ | `destroy` | Stop and delete the lab (requires `--yes`) |
692
+
693
+ | Option | Default | Description |
694
+ |---|---|---|
695
+ | `--dir PATH` | `~/.mdbkit-lab` | Where the lab lives |
696
+ | `--nodes N` | 3 | Replica set size |
697
+ | `--port N` | 28110 | Base port — deliberately far from 27017 |
698
+ | `--slowms N` | 0 | Log every operation, which is what makes the log worth reading |
699
+ | `--standalone` | off | Single node, no replica set |
700
+ | `--docs N` | 50000 | Documents inserted by `seed` |
701
+ | `--yes` | | Confirm `destroy` |
702
+
703
+ **The full loop:**
704
+
705
+ ```bash
706
+ mdbkit lab start # 3-node replica set on 28110-28112
707
+ mdbkit lab seed # sample data + a mixed workload
708
+
709
+ mdbkit queries $(mdbkit lab logs | head -1)
710
+ mdbkit advise $(mdbkit lab logs | head -1)
711
+
712
+ mdbkit lab destroy --yes # remove everything
713
+ ```
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
+
775
+ `seed` runs indexed point lookups alongside deliberately unindexed queries —
776
+ an equality-plus-range-plus-sort with no supporting index, an aggregation
777
+ that scans the collection, and updates whose predicate has no index — so the
778
+ log immediately contains something worth analysing.
779
+
780
+ **Safety.** The lab binds to `127.0.0.1` only, refuses to use or delete any
781
+ directory it did not create, and never touches a MongoDB it did not start.
782
+ It is a laptop and scratch-VM tool, not a deployment tool.
783
+
784
+ ---
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
+
824
+ ### `mdbkit export-script {schema|indexes}`
825
+
826
+ Prints a small `mongosh` script to stdout. **mdbkit never connects to your
827
+ database** — you run these yourself, so you can read exactly what they do
828
+ first. Both are read-only and export **field names and types only, never
829
+ document values**.
830
+
831
+ ```bash
832
+ mdbkit export-script indexes > export_indexes.js
833
+ mdbkit export-script schema > export_schema.js
834
+ ```
835
+
836
+ ---
837
+
838
+ ## Roadmap
839
+
840
+ Terminal output is and will remain first-class — this tool is built for the
841
+ Linux box the database actually runs on.
842
+
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.
863
+
864
+ mdbkit is validated against real-world structured logs (tens of thousands of
865
+ lines) in addition to its synthetic test fixtures.
866
+
867
+ ## Bugs, feature requests, questions
868
+
869
+ Please use [GitHub Issues](../../issues) — it keeps problems and fixes public
870
+ so the next person can find them. Real-world log lines that parse wrongly are
871
+ the most valuable bug reports of all (redact literals first!).
872
+
873
+ ## Security
874
+
875
+ mdbkit is offline by design: the codebase contains no network code, never
876
+ executes or evaluates input, and treats every log line as untrusted data
877
+ (strict JSON parsing only — shell constructors are never evaluated). See
878
+ [SECURITY.md](SECURITY.md) for the reporting process.
879
+
880
+ ## Non-affiliation
881
+
882
+ mdbkit is an independent community project. It is **not affiliated with,
883
+ endorsed by, or sponsored by MongoDB, Inc.** "MongoDB" is a registered
884
+ trademark of MongoDB, Inc., used here only to describe compatibility.
885
+
886
+ ## License
887
+
888
+ MIT — see [LICENSE](LICENSE).