Custom tracks, annotations, and sharing
Put your own timings in the trace, annotate what you found, and share traces your team can open. The course's store on its own custom track, read by DevTools' trace engine, and an annotated trace you can load.
- 01Add your own tracksPut your code's work on a row of its own with
performance.measureorconsole.timeStamp, and pick the right one for the job. - 02Annotate a traceLabel a bar, a stretch of time and a link from a cause to its effect, so the trace explains itself.
- 03Share it safelySay what a saved trace holds, and which save options add more, before you send one.
Your own work, in the trace
Asha's message is still on your desk: Add to cart feels slow on her phone. By now you know why. Every click sorts 200,000 past order prices again. Then it draws all 1,800 teas again. A colleague is going to fix it, and you want to show them what you found.
So you plan to send them the trace. Then you picture them opening it. They see thousands of bars, with names like Task, Function call and Timer Fired. Nothing on the screen says "price index" or "Add to cart". Where should they look? And what does each bar mean for the shop?
A trace knows what the browser did. It doesn't know what your code meant by it. Take one of the store's functions, buildIndex(), the one that sorts the prices. The course added a small file to the store, timings.js, which tells the trace when buildIndex() starts and ends. So one call of it shows up in two places:
// buildIndex at load, from one recording, in milliseconds.const mainTrack = [4.8, 116.4, 72.7, 8.6, 2.2]; // five bars in the flame chartconst storeTrack = 208.4; // one entry on the store's own track const total = mainTrack.reduce((sum, ms) => sum + ms, 0).toFixed(1);console.log(`The Main track shows ${mainTrack.length} pieces, ${total} ms in all.`);console.log(`The Store track shows one buildIndex entry, ${storeTrack} ms.`);- Line 1 is a comment. Every time is in milliseconds (ms), thousandths of a second.
- Line 2 holds the bars the Main track drew for this one call. The Main track's flame chart shows which function ran when. It's drawn from samples: Chrome checks, over and over, which function is running.
- Line 3 holds the one entry the store's own track drew for the same call.
- Line 5 adds up the five bars.
- Line 6 prints
The Main track shows 5 pieces, 204.7 ms in all. - Line 7 prints
The Store track shows one buildIndex entry, 208.4 ms.
The two views agree on the time, to within 4 ms. But they tell the story differently. A sample is a snapshot with no length of its own, so DevTools estimates how long each call ran from the samples around it. Here, that estimate came out as five bars side by side. The Store track shows your view instead: one call, with the name you gave it.
Why it matters: your colleague can find one named bar in a second. Five bars with no story need a detective, like the one in Reading a performance trace.
Getting from a raw trace to one your colleague can read takes three steps. This picture shows all three:
- Add your own rows. Your code writes its own entries into the trace, with your names, on tracks of your own.
- Leave notes. You label the bar that matters, mark a stretch of time, and draw an arrow from a cause to its effect.
- Check, then send. You save the trace, read what the file holds, and only then send it.
The back office at Leaf and Kettle keeps a timetable of the day, one row per worker. It shows when the head cook and the counter were busy, but not what for. So you add a row of your own, called Tea bar. In it you write "kettle boiling, 6:02 to 6:06" and "checkout steps, 6:07 to 6:09". Now anyone who reads the timetable sees your work in your own words, lined up with everyone else's.
- In real life: The shop's timetable, one row per worker
- In DevTools: The trace's tracks: Network, Main, Frames and more
- In real life: A row of your own, called Tea bar
- In DevTools: A custom track, like the store's
Storetrack - In real life: "Kettle boiling, 6:02 to 6:06", written in it
- In DevTools: An entry: a name, a start and an end
- In real life: A small note beside it: "3 litres, the big kettle"
- In DevTools: Properties and a tooltip on the entry
A custom track is that row: a track in the Performance panel that your code fills, with names you choose. The course recorded the store with timings.js in Chrome, on a phone-sized screen with the CPU 4 times slower. It loaded the page, waited, then clicked Add to cart once. Then DevTools' own trace engine, the code the Performance panel uses to read a trace, read the recording.
The playground shows three rows of it. The Timings row holds a mark at the moment of the click. The Store row holds the store's own four entries. The Main row holds the flame chart's bars for buildIndex(), so you can compare. Pick an entry to see what the Summary tab says about it:
buildIndex271.6 ms208.4 msprimary200000Sorts past order pricesperformance.measurebuildIndex, made with performance.measure. A measure can carry properties and a tooltip, so the Summary tab shows them.
A trace shows what the browser did. Custom tracks add what your code meant. Annotations add what you found. Whoever opens the trace next sees all three.
User Timing in the trace
Start with a tool every browser has. User Timing is a standard way for your code to write down times, with names. MDN gives the reason in one line: the browser "cannot determine what is going on in your application." So your code tells it. Two calls do most of the work.
A mark is one moment
performance.mark() writes down one moment and gives it a name. It's like pressing the lap button on a stopwatch.
// A mark is one moment, with a name.const click = performance.mark("Add to cart"); console.log(`${click.name} is a ${click.entryType}.`);console.log(`It lasts ${click.duration} ms, because it's one moment.`);- Line 1 is a comment.
- Line 2 writes down this moment, named
Add to cart. It gives back the entry it made, and the code keeps it asclick. - Line 4 prints
Add to cart is a mark.Every entry has a type, and this one's type ismark. - Line 5 prints
It lasts 0 ms, because it's one moment.A mark has no length, so itsdurationis always 0.
The moment itself is in the mark's startTime, counted in milliseconds from the start of the page.
A measure is a stretch of time
performance.measure() writes down a stretch of time, from a start to an end. If a mark is one press of the lap button, a measure is the time between two presses.
// A measure is a stretch of time, from a start to an end.const start = performance.now();// ... the store sorts its 200,000 prices here ...const built = performance.measure("buildIndex", { start }); console.log(`${built.name} is a ${built.entryType}, from start until now.`);- Line 2 reads the clock before the work starts.
performance.now()gives the time in milliseconds, as a number. - Line 3 stands for the work itself. In the store, that's sorting 200,000 prices.
- Line 4 makes a measure named
buildIndex. It starts atstart. No end is given, so it ends now. - Line 6 prints
buildIndex is a measure, from start until now.
Both stay in the page
The browser keeps every mark and measure in the page's performance timeline. That's the list of timing entries the browser keeps for one page, and your code can read it. So the same times you see in DevTools can also go to your analytics: the service that collects data from your real visitors.
Leaf and Kettle keeps a logbook on the counter. When the kettle goes on, someone writes a line: "kettle on, 6:02." When it boils, they write another: "kettle boiling, 6:02 to 6:06." The lines stay until someone tears the pages out. Each evening, the manager reads the book and copies the numbers into the shop's records.
- In real life: A line saying "kettle on, 6:02"
- In DevTools:
performance.mark(): one moment - In real life: A line saying "kettle boiling, 6:02 to 6:06"
- In DevTools:
performance.measure(): a stretch of time - In real life: The logbook on the counter
- In DevTools: The page's performance timeline
- In real life: The manager reading it each evening
- In DevTools: Your code reading the entries and sending them to analytics
- In real life: Tearing out the full pages
- In DevTools:
performance.clearMarks()andperformance.clearMeasures()
And like the logbook's lines, entries pile up. Here are three clicks:
for (let click = 1; click <= 3; click++) { performance.mark("Add to cart");} console.log(`The page keeps ${performance.getEntriesByName("Add to cart").length} marks.`);performance.clearMarks("Add to cart");console.log(`After clearMarks, it keeps ${performance.getEntriesByName("Add to cart").length}.`);- Lines 1 to 3 mark Add to cart three times, once per click. Marks can share a name.
- Line 5 asks the page for every entry with that name. It prints
The page keeps 3 marks. - Line 6 clears every mark with that name.
- Line 7 prints
After clearMarks, it keeps 0.
The course checked this at a bigger size too. After 10,000 measures, the page still held all 10,000 of them. Why it matters: if your code marks every click and never clears, the list keeps growing. Read what you need, send it, then clear it.
Where DevTools draws them
The Performance panel draws marks and measures on its Timings track. A mark shows as one point in time, and a measure as a bar. Select one, and DevTools' reference says the Summary tab shows "its timestamp, total time, self time, and the detail object".
So User Timing already puts your names in the trace. But it puts all of them on one shared row. In a big app, marks from every part of the code land on that one Timings row. The next step gives your work rows of its own.
The extensibility API for custom tracks
Chrome's DevTools has a way to give your entries their own rows. Its docs call it the performance extensibility API: a way to extend the Performance panel with your own data. There are two ways in. Both are calls in your page's own code, so there's nothing to install.
Way one: a devtools object in a measure
Every mark and measure can carry a detail: a box for any extra data you like. DevTools looks inside it for one key, devtools. If that holds a track, DevTools draws the measure on that track:
const start = performance.now();// ... the store sorts its 200,000 prices here ...const built = performance.measure("buildIndex", { start, detail: { devtools: { track: "Store", trackGroup: "Leaf and Kettle" } },}); console.log(`${built.name} goes on the ${built.detail.devtools.track} track.`);- Lines 1 to 4 are the measure from before, spread over several lines.
- Line 5 adds the
detail. Inside it,devtoolsholds two names.tracknames your row:Store.trackGroupnames a group that keeps related rows together:Leaf and Kettle. - Line 8 prints
buildIndex goes on the Store track.The measure keeps itsdetail, so your code can read it too.
The devtools object can say more about the entry. The docs list three more keys for one:
color: one name from a fixed list, such as"primary","secondary"or"tertiary". Each name stands for one of DevTools' own colors.properties: pairs of words, like["Prices sorted", "200000"]. The Summary tab shows them when you select the entry.tooltipText: a short line that appears when you hover over the entry.
A mark can carry a devtools object too. With dataType: "marker", it becomes a marker: a line drawn down across all the tracks, at one moment. The docs say it shows "a custom marker in the Timings track". The store marks each click on Add to cart this way.
Way two: console.timeStamp
console.timeStamp() is a console method that Chrome extended for custom tracks. It takes everything as a list of arguments, with no object:
const start = performance.now();// ... the store draws 24 teas here ...console.timeStamp("showTeas(24)", start, performance.now(), "Store", "Leaf and Kettle", "secondary"); const left = performance.getEntriesByName("showTeas(24)").length;console.log(`Entries named showTeas(24) left in the page: ${left}.`);- Line 1 reads the clock. Line 2 stands for the work: drawing 24 teas.
- Line 3 hands over six things, in this order: the entry's name, its start, its end, the track, the track group and the color.
- Line 5 asks the page for entries named
showTeas(24). - Line 6 prints
Entries named showTeas(24) left in the page: 0.
That zero is the point. console.timeStamp() draws an entry on your track, and that's all. The docs say it "does not add entries to the browser's internal performance timeline." So your code can't read it back, and it can't carry properties or a tooltip.
In return, it's "designed for minimal runtime overhead". Overhead is the extra time the timing itself adds. The docs call it "suitable for instrumenting hot paths and production builds." To instrument code means to add timing calls like these to it. A hot path is code that runs very often, like a function called once for every row of a long list.
At Leaf and Kettle, there are two ways to note a kettle boiling. You can write a full line in the logbook: the time, which kettle, how many litres. It takes a moment, and the line stays for the manager. Or, in the busiest hour, you can put a quick tick on the timetable on the wall. It takes a second, and it leaves nothing in the book.
- In real life: A full line in the logbook: the time, the kettle, the litres
- In DevTools:
performance.measure()with properties and a tooltip - In real life: The line stays in the book for the manager
- In DevTools: The measure stays in the page's performance timeline
- In real life: A quick tick on the timetable on the wall
- In DevTools:
console.timeStamp(): an entry drawn in the Performance panel only - In real life: Ticks during the busiest hour
- In DevTools: Timing a hot path, or code in production
What each call costs
"Minimal overhead" is a claim, so the course timed it. A page made 10,000 calls of each kind, with no recording running, five times over. These are the middle results:
// 10,000 calls of each, timed in the page with no recording running, in ms.const measureMs = 16;const timeStampMs = 3.9; const perCall = (ms) => ((ms / 10000) * 1000).toFixed(2);console.log(`performance.measure: ${perCall(measureMs)} µs a call.`);console.log(`console.timeStamp: ${perCall(timeStampMs)} µs a call.`);- Lines 2 and 3 hold the recorded totals for 10,000 calls: 16 ms for the measures, 3.9 ms for the time stamps.
- Line 5 turns a total into the time for one call. It divides by 10,000, then multiplies by 1,000 to get microseconds (µs), millionths of a second.
- Line 6 prints
performance.measure: 1.60 µs a call. - Line 7 prints
console.timeStamp: 0.39 µs a call.
Both are tiny for one call. The store's buildIndex() runs once per load and once per click, so a measure there costs nothing you could notice. The difference shows up on a hot path. 10,000 measures took 16 ms, about as long as one frame lasts on a screen that shows 60 frames a second. The same 10,000 time stamps took about a quarter of that.
Recording adds to both. While a trace was recording, the same 10,000 calls took 47 ms as measures and 18.5 ms as time stamps. Time them in your own browser:
// Time 10,000 calls, then clean up the measures the first kind leaves behind.const time = (fn) => { const start = performance.now(); for (let call = 0; call < 10000; call++) fn(call); return performance.now() - start;};const detail = { devtools: { track: "Playground", trackGroup: "Your page", color: "primary" } }; const measureMs = time((call) => performance.measure(`measure ${call}`, { start: performance.now(), detail }));const kept = performance.getEntriesByType("measure").length;performance.clearMeasures();const timeStampMs = time((call) => console.timeStamp(`stamp ${call}`, performance.now(), performance.now(), "Playground", "Your page"));not yetnot yetnot yetEach button makes 10,000 calls of one API and times them. Afterwards, the measures are cleared, so they don't pile up in this page.
Here are the two ways side by side. Read each row across: one question, answered for each call.
| Question | performance.measure() | console.timeStamp() |
|---|---|---|
| Does it draw an entry on your own track? | Yes, when its detail holds a devtools object with a track. | Yes, when you pass a track name as the fourth argument. |
| Can the entry carry properties and a tooltip? | Yes. They show in the Summary tab. | No. It gets a name, a start, an end and a color. |
| Does it stay in the page afterwards? | Yes. Your code can read it until it clears it. | No. Only the Performance panel sees it. |
| Which one costs less? | It took about 4 times as long as console.timeStamp. | This one. The docs made it for code that runs very often. |
The store's timings.js
Now the whole file the course added to the store. It uses one of each, so you can compare them on one track:
// Put the store's own work on a custom track in the Performance panel.const original = { buildIndex, showTeas }; window.buildIndex = function () { const start = performance.now(); const prices = original.buildIndex(); performance.measure("buildIndex", { start, detail: { devtools: { track: "Store", trackGroup: "Leaf and Kettle", color: "primary", properties: [["Prices sorted", String(prices.length)]], tooltipText: "Sorts past order prices" } }, }); return prices;}; window.showTeas = function (howMany) { const start = performance.now(); original.showTeas(howMany); console.timeStamp(`showTeas(${howMany})`, start, performance.now(), "Store", "Leaf and Kettle", "secondary");}; document.addEventListener("click", (event) => { if (event.target.id !== "add") return; performance.mark("Add to cart", { detail: { devtools: { dataType: "marker", color: "tertiary", tooltipText: "A click on Add to cart" } } });}, true); - Line 2 keeps the store's own
buildIndex()andshowTeas(), so the new versions can still call them. - Lines 4 to 12 replace
buildIndex()with a version that times it. Line 7 is way one: a measure with adevtoolsobject. It sets a track, a group, a color, one property and a tooltip. - Lines 14 to 18 do the same for
showTeas(). Line 17 is way two:console.timeStamp(), the cheap call. - Lines 20 to 23 listen for clicks. Line 22 marks each click on Add to cart as a marker.
The store's own app.js didn't change at all. timings.js loads right after it and wraps its functions from the outside. That's where the four entries and the marker in the first playground came from.
One last step: DevTools only draws custom tracks when a setting is on. In the Performance panel, open Capture settings and tick Show custom tracks.
Annotating a trace
Your colleague will now see the store's own rows. But the trace still holds 18,896 events. Which part should they read first? You know. You need a way to say so, on the trace itself.
Before you hand the day's timetable to the new manager, you print it and add sticky notes. One goes on the head cook's 6:10 order: "This is the slow one." You highlight 6:20 to 6:21 and write: "The van blocked the door here." Then you draw an arrow from the oven timer set at 6:00 to the rush at 6:20: "This caused that."
- In real life: A sticky note on one bar: "This is the slow one"
- In DevTools: A label on one entry
- In real life: A highlighted stretch, with a note beside it
- In DevTools: A labelled time range
- In real life: An arrow drawn from one bar to another
- In DevTools: A link between two entries
- In real life: The notes stay on when you hand it over
- In DevTools: Annotations are saved in the trace file
DevTools calls these notes annotations. Its docs: once you've recorded and analyzed a trace, "you can annotate items (with Gemini), time ranges, and create connections." First, open the Annotations tab in the sidebar on the left of the Performance panel. Then:
- Label an item: double-click a bar, then type your note.
- Label a time range: hold Shift and drag from the start of the range to its end, then type your note.
- Connect two items: double-click the first bar, click the arrow that appears next to it, then click the second bar.
To change a note later, double-click it. To delete one, use the Delete button next to it in the Annotations tab. For a bar on the Main track, DevTools can also suggest a label with Gemini, Google's AI model. To do that, it sends the trace data it needs to Google.
The course's three notes
The course added one note of each kind to its recording of the store. Here they are on the recording's clock:
- The arrow starts at 484 ms, where the store's start-up code set a one-second timer. It ends at 1484.7 ms, where that timer fired. This is the promotion banner from Reading a performance trace.
- The time range runs from 1484.7 ms to 1491 ms: from the timer firing to the moment the page shifted. Its note says: "The promotion arrives and shifts the page."
- The label sits on the click's task, which ran for 252.1 ms. It says: "Add to cart: rebuilds the index and draws 1,800 teas."
Why it matters: each note answers a question before your colleague asks it. The arrow says what caused the shift. The range says where to zoom in. The label says which task is slow, and why.
How does the file remember them? The notes sit in the file's metadata: the part that describes the recording, apart from the events themselves. The course wrote its notes there the way the Performance panel saves them:
{ "metadata": { "modifications": { "annotations": { "entryLabels": [{ "entry": "r-13321", "label": "Add to cart: rebuilds the index and draws 1,800 teas" }], "labelledTimeRanges": [{ "bounds": { "min": 1154000465274, "max": 1154000471538, "range": 6264 }, "label": "The promotion arrives and shifts the page" }], "linksBetweenEntries": [{ "entryFrom": "r-7391", "entryTo": "r-11052" }] } } }}- Lines 1 to 4 lead into the notes: the file's metadata, then its modifications, then its annotations.
- Line 5 is the label. Its
entry,r-13321, says which bar it's stuck on. Thermeans a raw event, one exactly as the browser wrote it. 13321 is that event's place in the file's list of events, counting from 0. - Line 6 is the time range. Its
boundsare times in microseconds, the unit a trace uses. The range is 6264 µs long, about 6.3 ms. - Line 7 is the arrow: from event
r-7391, the timer being set, to eventr-11052, the timer firing.
The label and the arrow point at events by their place in the file, not by name or time. So when your colleague opens the same file, each note finds the same bar. The course made each key with DevTools' own code for naming events. Then it read the file back to check that each key found its event.
Download the annotated trace (389 kB). In your Performance panel, click Upload and pick the file. The notes come with it.
Which one?
Each job below is something you might want from a trace. For each one, ask three questions. Does it need details, or data your code reads later? Does it need to be as cheap as possible? Or is it a message for the person who opens the trace?
- Show which filter an image task used, in the entry's details
- Send the same timing to your analytics, too
- Time a function that runs thousands of times a second
- Leave the timing in production code
- Tell a colleague which task to look at
- Show that a timer caused a later layout shift
Sort each job by the tool that fits it best.
Common mix-ups
- "Custom tracks need a DevTools extension." No. They need a
devtoolsobject inside a measure'sdetail, or aconsole.timeStamp()call with a track name. There's nothing to install. - "Timing my code is free." No. With no recording running, 10,000 measures took 16 ms. And every measure stays in the page until your code clears it.
- "console.timeStamp and performance.measure do the same job." No. Both draw an entry on your track. Only a measure carries properties and a tooltip, and only a measure stays where your code can read it.
- "Annotations are lost when you save the trace." No. They're saved in the file's metadata, unless you untick Include annotations.
- "A trace holds only timings." No. It can hold screenshots of the page, every address the browser asked for, and every word in your custom details and notes.
Practice exercises
Answer first, then use the hints.
This is the first example again, with the click's buildIndex() instead of the load's. What does line 6 print now?
// buildIndex after the click, from the same recording, in milliseconds.
const mainTrack = [32.4, 104]; // its bars in the flame chart
const storeTrack = 138.2; // one entry on the store's own track
const total = mainTrack.reduce((sum, ms) => sum + ms, 0).toFixed(1);
console.log(`The Main track shows ${mainTrack.length} pieces, ${total} ms in all.`);
console.log(`The Store track shows one buildIndex entry, ${storeTrack} ms.`);The Main track shows 2 pieces, 136.4 ms in all. The flame chart drew the click's call as two bars. The Store track still shows it as one entry.
Line 5 of the file-size example now gives the gzipped size in kilobytes. What does it print?
// The course's annotated trace, saved without resource content or source maps.
const file = { events: 18896, bytes: 4737727, gzipBytes: 389261 };
console.log(`${file.events.toLocaleString("en-US")} events, ${(file.bytes / 1e6).toFixed(1)} MB as plain JSON.`);
console.log(`${Math.round(file.gzipBytes / 1000)} kB with gzip.`);389 kB with gzip. Math.round drops the .261, so 389.261 becomes 389.
This measure shows up on the Timings track, but never on a Store track. Which key is missing from its detail?
const start = performance.now();
// ... the store sorts its 200,000 prices here ...
performance.measure("buildIndex", {
start,
detail: { track: "Store", trackGroup: "Leaf and Kettle" },
});const start = performance.now();
// ... the store sorts its 200,000 prices here ...
performance.measure("buildIndex", {
start,
detail: { devtools: { track: "Store", trackGroup: "Leaf and Kettle" } },
});devtools. DevTools' trace engine looks for detail.devtools. Without it, this is an ordinary measure, so it only shows on the Timings track.
In the first playground, how many milliseconds did showTeas(1800), the redraw after the click, take on the Store track?
15.8 ms. Drawing all 1,800 teas was quick next to sorting the prices, which ran just before it.
Your checkout code fails now and then, and you want those entries to stand out. Which color value do DevTools' extensibility docs offer for that?
"error". The other nine names are shades of primary, secondary and tertiary.
You want to read a colleague's trace without their notes for a moment, but without deleting them. Which checkbox in the Annotations tab does that?
Hide annotations. The notes stay in the trace. They're only hidden from view until you untick it.
Check your understanding
For each question, ask what each call leaves behind, and what a saved trace carries.
Question 1 of 7What does the last line of the cost example print?
Read the code, then predict// 10,000 calls of each, timed in the page with no recording running, in ms. const measureMs = 16; const timeStampMs = 3.9; const perCall = (ms) => ((ms / 10000) * 1000).toFixed(2); console.log(`performance.measure: ${perCall(measureMs)} µs a call.`); console.log(`console.timeStamp: ${perCall(timeStampMs)} µs a call.`);Choose an answer to see the explanation.
Question 2 of 7What does the last line of the three-marks example print?
Read the code, then predictfor (let click = 1; click <= 3; click++) { performance.mark("Add to cart"); } console.log(`The page keeps ${performance.getEntriesByName("Add to cart").length} marks.`); performance.clearMarks("Add to cart"); console.log(`After clearMarks, it keeps ${performance.getEntriesByName("Add to cart").length}.`);Choose an answer to see the explanation.
Question 3 of 7Which call leaves no entry in the page's own performance timeline?
Choose an answer to see the explanation.
Question 4 of 7Why can the Main track show one call of buildIndex as several bars, while the Store track shows one?
Choose an answer to see the explanation.
Question 5 of 7Where does a performance.mark with dataType "marker" show up?
Choose an answer to see the explanation.
Question 6 of 7In a saved trace, what does an annotation's entry key, such as r-13321, point at?
Choose an answer to see the explanation.
Question 7 of 7A colleague asks for a trace of a bug in your logged-in account. What do you check before sending it?
Choose an answer to see the explanation.
Key takeaways
- A trace shows what the browser did. Your own entries add what your code meant.
performance.mark()writes down a moment, andperformance.measure()a stretch of time. Both stay in the page until you clear them.- A
devtoolsobject in a measure'sdetailputs it on your own track, with properties and a tooltip. console.timeStamp()is the cheap way, for hot paths. It leaves nothing in the page.- Turn on Show custom tracks to see your rows.
- Annotations label a bar, a time range, or a link from cause to effect. They're saved in the trace.
- A saved trace carries screenshots, addresses, your details and your notes. Your code goes in only if you choose it.
Remember the one-liner.
Name your work in the trace, note what you found, and read what you share.
Sources
The facts on this page come from these docs and source files:
- SamplesIntegrator.ts, devtools-frontend source.
- User timing, MDN.
- PerformanceMark, MDN.
- Performance: measure() method, MDN.
- Performance features reference: View custom timings, Kayce Basques and Sofia Emelianova, Chrome DevTools.
- Customize your performance data with extensibility API, Andrés Olivares and Sofia Emelianova, Chrome DevTools.
- ExtensionTraceDataHandler.ts, devtools-frontend source.
- Annotate and share your performance findings, Sofia Emelianova and Barry Pollard, Chrome DevTools.
- EventsSerializer.ts, devtools-frontend source.
- Save performance traces, Connor Clark, Chrome DevTools.
Coming next: Performance work with AI agents. You'll let AI assistance and coding agents record and read traces, and check every claim they make against the trace.