Introduction
A step-by-step guide to help you open, use, and validate the new Profiler screen. Follow from Step 1 to the end, in order. Each step tells you what to do and what you will see. You don't need to know anything about profiling beforehand — the 💡 boxes explain the concepts right when they come up.
How to read this manual
Each step has:
- Do — the exact action (click, type, run such-and-such script).
- See — what should happen on screen. This is your “pass / fail”.
- Why — only when it helps you understand (you can skip it if you're in a hurry).
Concept
Concept box. Explains a term right when it appears.
Heads-up
Heads-up. A detail that tends to be confusing.
Before you start
Concept
What the Profiler is
MongoDB can keep a “logbook” of everything it runs — every query, how long it took, whether it used an index — in a collection called system.profile. The Profiler screen turns that logbook on and shows it graphically: charts, filters, and even index recommendations.
You will need:
- 1The dev server restarted after the last build (without that the screen won't even load — that's what causes the
spacingPxerror). - 2A connected MongoDB connection. It can be local, on-prem, or Atlas M10+. On Atlas M0/M2/M5 (free) the profiler is blocked by MongoDB — it works, but the screen will warn that it's restricted (we test this in Step 41).
- 3The database user needs to be able to run administration commands (
setProfilingLevel,createIndex). A “database owner” user does the job.
Estimated time for the full walkthrough: ~30 minutes.
Prepare the lab database
For the Profiler to have something to show, we first create a database with plenty of data.
Create the profiler_lab database
Open the Mongo Shell tab (or the Scratchpad) in NoSqlStudio, paste the script below, and run it.
use profiler_lab;
db.dropDatabase();
use profiler_lab;
['orders', 'products', 'customers'].forEach((c) => db.getCollection(c).drop());
function seed(coll, n, gen) {
const c = db.getCollection(coll);
let buf = [];
for (let i = 0; i < n; i++) {
buf.push(gen(i));
if (buf.length === 165000) { c.insertMany(buf); buf = []; }
}
if (buf.length) c.insertMany(buf);
print(coll + ': ' + c.countDocuments());
}
const ST = ['pending', 'paid', 'shipped', 'cancelled', 'refunded'];
const RG = ['north', 'south', 'east', 'west'];
seed('orders', 120000, (i) => ({
orderNo: i, status: ST[i % 5], region: RG[i % 4],
customerId: (i * 7) % 5000, total: Math.round(Math.random() * 1e5) / 100,
items: 1 + (i % 8), createdAt: new Date(Date.now() - (i % 90) * 864e5),
note: 'order line '.repeat(4),
}));
seed('products', 20000, (i) => ({
sku: 'SKU-' + i, category: ['a', 'b', 'c', 'd', 'e'][i % 5],
price: Math.round(Math.random() * 5e4) / 100, stock: i % 500,
active: i % 3 !== 0,
}));
seed('customers', 5000, (i) => ({
customerId: i, tier: ['free', 'pro', 'enterprise'][i % 3],
city: RG[i % 4], spend: Math.round(Math.random() * 1e6) / 100,
}));After a few seconds, the shell prints:
orders: 120000
products: 20000
customers: 5000120 thousand orders is enough for a query without an index to become visibly slow — and slowness is exactly what we want to diagnose.
Heads-up
If later on the queries don't exceed 100 ms (fast machine), come back here and change 120000 to 300000.
Open the Profiler screen
Open it for the first time
Open the Profiler in one of these 3 ways (they all lead to the same place — test the other two later, in Step 46):
- Main menu → Database Profiler, or the shortcut Ctrl+Alt+Shift+P.
- Toolbar → Monitoring → Database Profiler menu.
- Right-click a connection in the sidebar → Profiler.
A new tab named Profiler opens, with the connection name next to it. From top to bottom, the tab has: a header bar, a control strip, and four section buttons (Dashboard, Live Feed, Index Advisor, Sessions).
Choose the database
In the header, in the Database field, choose `profiler_lab`.
The screen now works on that database. Since we haven't turned profiling on yet, the Dashboard shows a message like “No profiling data yet”.
Heads-up
The profiler is per database. Everything in this manual uses profiler_lab. If you choose another database, you won't see the triggers.
Turn on profiling
Understand the control strip
Right below the header there is a strip with:
- Profiling — three buttons:
Off·Slow ops·All ops. - slowms and Sample — two numeric fields + an Apply button.
- Filter — opens a filter builder.
- ⏱ Capture — a safe timed capture.
- system.profile — a small usage bar for the collection + a Resize button.
- Health — a grade (A–F) with a score.
Concept
The three levels. Off = records nothing. Slow ops = records only what exceeded the slowms threshold. All ops = records everything. To test, we'll go with All ops to see all the traffic.
Turn it on at the “All ops” level
Click the All ops button.
- The status dot on the left turns blue.
- A warning pill “⚠ All ops” appears (a reminder that level 2 has a performance cost).
- A quick message confirms “Profiling level set to 2”.
This is the heart of the tool. In the old profiler, this just copied a command for you to paste into the shell. Now the click actually turns profiling on. If the dot turned blue, the main technical path works. ✅
Generate traffic (the “triggers”)
Now we'll run queries in the shell so the Profiler has something to show. Keep the Profiler tab open — it refreshes itself every few seconds.
Trigger A — slow queries
Run in the shell:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;Within 1–3 seconds, in the Live Feed section, two new query operation rows on profiler_lab.orders, with a red COLLSCAN badge.
Concept
COLLSCAN vs IXSCAN. COLLSCAN = MongoDB read document by document because there was no index — slow. IXSCAN = it used an index (a shortcut) — fast. Red badge = bad; blue badge = good.
Trigger B — inefficient query
Run in the shell:
use profiler_lab;
db.orders.find({ status: 'refunded', region: 'north', items: 7 }).toArray();One more row in the feed. In the Examined → Returned column the number turns red — it examined 120 thousand documents to return just a few.
Concept
Inefficient. When the database “examines” much more than it “returns”, it's wasting work — a strong candidate for an index.
Trigger C — writes
Run in the shell:
use profiler_lab;
db.orders.insertOne({ orderNo: 999001, status: 'paid', region: 'east', items: 2 });
db.orders.updateMany({ status: 'pending' }, { $set: { flagged: true } });
db.orders.deleteMany({ flagged: true, items: 3 });Three new operations: insert, update, remove.
Triggers D, E, F — aggregation, count, and cursors
Run in the shell:
use profiler_lab;
db.orders.aggregate([
{ $match: { status: 'paid' } },
{ $group: { _id: '$region', n: { $sum: 1 }, rev: { $sum: '$total' } } },
]).toArray();
db.orders.countDocuments({ region: 'west' });
db.orders.distinct('status');
let cur = db.orders.find({}).batchSize(500).limit(5000);
let k = 0; while (cur.hasNext()) { cur.next(); k++; } print(k);Operations of type command (the aggregation, the count) and several getmore (the cursor reading in batches).
Trigger G — the same query repeated
Run in the shell:
use profiler_lab;
for (let i = 0; i < 40; i++) db.orders.find({ status: 'paid' }).limit(10).toArray();Nothing special in the feed right now — but keep it in mind: in Step 24 this becomes a single row in the “Query shapes” panel, with a count of 40.
Explore the Live Feed
Open the feed
Click the Live Feed section (at the top).
A table with the operations, the most recent at the top. Columns: time, type, collection, duration (color-coded — green fast, red slow), plan (COLLSCAN/IXSCAN badge), examined→returned, and app. At the top, “X of Y operations”.
See the live mode working
Run Trigger A (the one from Step 6) again and keep an eye on the feed:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;New rows appear on their own at the top within seconds.
Pause and resume
In the header, click the ● Live button (it becomes ⏸ Paused). Run Trigger A again:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;The feed does not grow — it's frozen. Click ⏸ Paused to go back to ● Live: it starts growing again.
Pausing is useful for inspecting a row without the list jumping around.
Quick filters (the preset chips)
Click, one at a time, the chips at the top of the feed:
- Slow > 100ms — only slow operations.
- COLLSCANs — only the ones without an index (red badge).
- Inefficient — highlights the one from Step 7.
- Writes — only insert/update/remove (the ones from Step 8).
- Last 5 min — only recent traffic.
The table shrinks to show only what matches. The active chip stays highlighted. Click it again to turn it off.
Fine-grained filters
Do, in sequence:
- 1Click the type chips (
query,insert,getmore…) to turn them on/off. - 2In the All collections dropdown, choose
orders. - 3In the search box, type
refunded.
Each filter narrows the list. They combine with each other.
Clear everything
Click Clear filters (it appears when there is an active filter).
The table goes back to showing everything.
Explore the Dashboard
Open the Dashboard
Click the Dashboard section.
Four number cards at the top (Operations, Slow ops, Collection scans, Average duration) and, below them, several charts.
The indicators (KPIs)
The four numbers reflect everything you triggered. “Collection scans” shows the percentage of COLLSCANs — the higher, the worse.
Timeline
The Operations over time chart shows bands stacked by type (reads, inserts, updates, deletes, commands), with a color-coded legend.
Index usage (donut)
The Index usage donut chart splits the operations into COLLSCAN (red), IXSCAN (blue), and others. Right now it should be dominated by red.
Latency distribution
Bars grouping the operations by time range (0–1ms, …, >5s).
Hottest collections (treemap)
Blocks proportional to the total time spent on each collection. orders should be the biggest block.
Click a treemap block
Click the orders block.
The screen jumps to the Live Feed, already filtered by orders. (Go back to the Dashboard afterwards.)
Query shapes
The Query shapes table groups queries with the same “shape”. The query you repeated 40× in Step 10 shows up as a single row, with a count of 40 and the total time summed up.
This is how you discover “which query, summing all its runs, weighs most on the database” — even if each individual execution is fast.
Slowest operations
The final table lists the 8 slowest operations. Keep it in mind for the next step.
Investigate an operation (drill-down)
Open the detail panel
Click any row — in the Live Feed, in the query shapes table, or in the slowest one.
A panel slides in from the right with the full detail of the operation.
Read the numbers
A grid with Duration, Plan, Yields, Docs examined, Keys examined, Returned. If the operation is inefficient, a red warning says it is a “strong index candidate”.
Request the live “explain”
Click Explain this query.
The tool runs explain() on the spot and shows the real plan, the execution time, and how many documents were examined — useful for confirming the diagnosis on the current database.
Concept
Explain. This is MongoDB describing how it intends to run the query. It confirms whether it would use an index or not.
See the command and the details
In the panel, look at the Command section (the operation's JSON) and click the copy icon. Then click Execution stats and Raw document to expand/collapse them.
The command is copied to the clipboard; the sections open and close.
Close the panel
Close it with the X at the top of the panel — and open another and close it by clicking the dimmed area beside it.
The panel disappears both ways.
Use the Index Advisor (the highlight)
Generate recommendations
Run in the shell (3 problematic queries, repeated 30×):
use profiler_lab;
for (let i = 0; i < 30; i++) {
db.orders.find({ status: 'shipped' }).toArray();
db.orders.find({ region: 'north' }).sort({ createdAt: -1 }).limit(20).toArray();
db.products.find({ category: 'c', active: true }).toArray();
}Wait a few seconds. (Continues in the next step.)
Open the Index Advisor
Click the Index Advisor section.
At the top, a Health card with a grade (A–F) — probably low (C/D), because you generated many COLLSCANs. Below it, a list of index recommendations — about 3 cards.
Read a recommendation
Each card has: a severity pill (High/Medium/Low), the collection, the query “shape”, chips with the fields of the suggested index (with ↑/↓ arrows), and an impact line (“N operations · X s total · M scans”).
Concept
Why those fields in that order. The advisor follows the ESR rule: equality fields first, then the sort fields, then the range ones. That's the order that makes the index most efficient.
Create an index with 1 click
On a card, click Create index. Check the command in the modal and confirm.
An “Index created” message. The card for that recommendation changes to ✓ Already indexed.
Create the rest
Repeat Step 34 for the other recommendations.
They all become ✓ Already indexed.
Confirm it improved
Run the same queries again
Run the same script from Step 31 again (repeated here so you don't have to go back):
use profiler_lab;
for (let i = 0; i < 30; i++) {
db.orders.find({ status: 'shipped' }).toArray();
db.orders.find({ region: 'north' }).sort({ createdAt: -1 }).limit(20).toArray();
db.products.find({ category: 'c', active: true }).toArray();
}The queries run again — now with the indexes you created in Stage 8 already in effect.
See COLLSCAN turn into IXSCAN
Go to the Live Feed.
The same queries now show a blue IXSCAN badge and a much shorter duration. They used to read 120 thousand documents; now they use the index.
See the Health grade go up
Go back to the Index Advisor (or click the Health pill in the control strip).
The grade went up (e.g., from C/D to A/B) and there are fewer recommendations. You just completed a full cycle: diagnose → fix → prove. ✅
Sessions and report
Concept
Session. A “snapshot” of the current profiling state, saved with a name. It's used to compare “before” and “after” a change.
Save a session
Go to Sessions, type a name (e.g., after the indexes), and click Save session.
The session appears in the list, with its indicators and the date.
Compare
In the list, select two entries — for example the saved session and the Current window (which is always at the top).
A Comparison table appears with a color-coded Δ column — green when it improved (fewer COLLSCANs, less time), red when it got worse.
Export the report
Click Export report.
It downloads a .json file with health, recommendations, query shapes, and the slowest operations — to attach to a ticket or keep on file.
Control and safety features
Safe timed capture
Concept
Why it exists. Leaving level 2 on and forgetting about it is dangerous in production. The “capture” turns level 2 on, runs a countdown, and turns itself off at the end.
In the control strip, click ⏱ Capture and choose 30s. During those 30s, run any trigger — for example Trigger A:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;Profiling goes to level 2 and a pill with the countdown appears. When it reaches zero, the level goes back on its own to what it was before, with the message “Capture complete”. (You can also click Cancel to end it earlier.)
Profiling filter
Click Filter, fill Namespace with orders, and click Apply filter. Then run Trigger A:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;Then click Filter → Clear filter.
With the filter active, only operations on orders are recorded (and the button shows “Filter active”). When you clear it, it goes back to recording everything.
Manage the system.profile collection
Concept
Capped collection. The profiler's logbook has a fixed size; when it fills up, it deletes the oldest entries. The default (1 MB) fills up fast.
Watch the system.profile bar in the control strip (it shows used / total). Click Resize, choose 10 MB, and confirm.
The bar now reflects ~/10 MB. The modal warns that the current history is erased on resize.
Adjust slowms and the sampling rate
Change slowms to 50 and click Apply. Then click Slow ops and run Trigger A:
use profiler_lab;
db.orders.find({ status: 'shipped' }).toArray().length;
db.orders.find({ region: 'south' }).sort({ total: -1 }).toArray().length;At the “Slow ops” level, only operations slower than the defined slowms enter the feed — the fast ones are ignored.
Finishing touches
Other ways to open it
Test the 3 entry points from Step 2 that you haven't used yet.
They all open the Profiler tab. If one is already open, it focuses the existing one instead of duplicating it.
Languages
In Settings → Language, switch the language (there are 5). Go back to the Profiler.
The entire Profiler screen appears translated — with no “raw” texts like profiler.xyz.
Themes
Toggle the theme between Light, Dark, and Neon.
Colors, cards, and charts adapt; no invisible text.
Cleanup (when you're done)
Run in the shell:
use profiler_lab;
db.setProfilingLevel(0);
db.dropDatabase();Profiling is turned off and the lab database is gone.
Summary of what you validated
| Stage | Feature |
|---|---|
| 2–3 | Open the Profiler (3 entry points) + choose database |
| 3 | Actually turn profiling on (Off / Slow / All) |
| 4–5 | Live Feed: real time, pause, presets, filters, search |
| 6 | Dashboard: KPIs, timeline, donut, latency, treemap, query shapes |
| 7 | Drill-down: details, live explain(), command, raw |
| 8–9 | Index Advisor: recommendations, create index, prove the improvement |
| 10 | Sessions: save, compare, export report |
| 11 | Timed capture, filter, resize, slowms |
| 12 | Alternative entry points, 5 languages, 3 themes |