Network Waterfall Chart
The requests a shop's home page makes as it loads, the way the browser's network panel draws them: each request a row, split into its phases, with its DOMContentLoaded and Load moments marked. Throttle the network with Chrome's presets to see what a slow connection does.
Reading a waterfall
A network waterfall lines up every request on one clock, so the shape of the page load shows at once: a long first bar is a slow server, steps down the chart are requests that wait for each other, and DNS and connection time appear only where the page talks to a new server. Throttling stretches every round trip and download, and shows which requests stand between the visitor and a usable page.
Good for
- Page loads, API calls and other request timings
- Any process of phases per item: builds, pipelines, jobs
- Finding what waits on what
Think twice when
- Hundreds of requests: group or filter them first
- Totals per phase: a stacked bar chart is clearer
Get the code.
Built with amCharts 5, a JavaScript library. The code includes your theme and renderer (canvas or SVG), but not the changes you make with the controls.
npm install @amcharts/amcharts5. See Getting started.
The same chart as a JSON config. Chart Maker reads and writes this format, the library's JSON parser turns it into a chart, and the <am5-chart> element puts it on a page with no code.How it reads:
"type" is the class to create and "settings" what you would pass to its new() in code; "properties" fill in its parts, lists such as series and axes or templates such as columns.template; "refs" holds objects used in more than one place ("#series-0" stands for the one named series-0); a string starting with "@" takes a value from another object, such as "@series.get('fill')".
A complete page. Save it as an .html file and open it in a browser.
Paste this into an AI coding assistant to build the chart from scratch. More in amCharts & AI.
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
var responsive = am5themes_Responsive.new(root); // kept in a variable: a rule of its own is added below
root.setThemes([
am5themes_Animated.new(root),
responsive
]);
// ── Sample data: the requests a shop's home page makes as it loads, like the network panel of the browser's tools.
// Each request goes through phases: waiting in the queue, finding the server (DNS), connecting to it, waiting for its
// answer and downloading it. A server's first request pays for the DNS lookup and the connection; later ones to the
// same server reuse them ──
// The requests: file, type, server, size (KB), the server's own time to answer (ms), and what has to happen before
// the browser asks for it
var REQUESTS = [
{ url: "index.html", type: "Document", host: "shop.example.com", size: 32, server: 180, after: "start" },
{ url: "styles.css", type: "Stylesheet", host: "shop.example.com", size: 48, server: 25, after: "html" },
{ url: "vendor.js", type: "Script", host: "shop.example.com", size: 420, server: 20, after: "html" },
{ url: "app.js", type: "Script", host: "shop.example.com", size: 310, server: 20, after: "html" },
{ url: "fonts.css", type: "Stylesheet", host: "fonts.example.net", size: 2, server: 30, after: "html" },
{ url: "inter.woff2", type: "Font", host: "fonts.example.org", size: 38, server: 15, after: "fonts.css" },
{ url: "inter-bold.woff2", type: "Font", host: "fonts.example.org", size: 41, server: 15, after: "fonts.css" },
{ url: "logo.svg", type: "Image", host: "shop.example.com", size: 6, server: 10, after: "html" },
{ url: "hero.webp", type: "Image", host: "img.example-cdn.com", size: 180, server: 30, after: "html" },
{ url: "product-1.webp", type: "Image", host: "img.example-cdn.com", size: 64, server: 25, after: "html" },
{ url: "product-2.webp", type: "Image", host: "img.example-cdn.com", size: 58, server: 25, after: "html" },
{ url: "product-3.webp", type: "Image", host: "img.example-cdn.com", size: 71, server: 25, after: "html" },
{ url: "product-4.webp", type: "Image", host: "img.example-cdn.com", size: 49, server: 25, after: "html" },
{ url: "api/user", type: "Fetch", host: "api.example.com", size: 8, server: 90, after: "app.js" },
{ url: "api/products", type: "Fetch", host: "api.example.com", size: 24, server: 140, after: "app.js" },
{ url: "analytics.js", type: "Script", host: "stats.example.io", size: 45, server: 40, after: "dcl" },
{ url: "favicon.ico", type: "Image", host: "shop.example.com", size: 4, server: 10, after: "load" }
];
// Network speeds: a round trip's latency (ms) and the download speed (kilobits/s). Without throttling a fast office
// connection; the others are Chrome's own throttling presets
var PROFILES = {
none: { rtt: 20, kbps: 50000 },
fast4g: { rtt: 165, kbps: 8100 },
slow4g: { rtt: 562.5, kbps: 1440 },
slow3g: { rtt: 2000, kbps: 400 }
};
// A seeded random number generator (mulberry32): the same timings on every visit
function makeRandom(seed) {
return function() {
seed = seed + 0x6D2B79F5 | 0;
var t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// Each request's phases at a speed, and when the page's DOMContentLoaded and load events fire
function simulate(profile) {
var random = makeRandom(2026);
var kbPerMs = profile.kbps / 8 / 1000; // kilobytes a millisecond
var connectedAt = {}; // when the connection to each server was up
var done = {}; // when each request finished
var phases = [];
var events = { start: 0 };
REQUESTS.forEach(function(request) {
// when the browser can ask: at the start, once the HTML has started to arrive (the parser finds the tags), once
// a file it depends on is in, or after the page's own events
var ready = request.after === "html" ? events.html + 5 + 30 * random() : (events[request.after] !== undefined ? events[request.after] : done[request.after]);
var t = ready;
var parts = [];
function phase(name, ms) {
parts.push({ url: request.url, phase: name, start: Math.round(t * 10) / 10, end: Math.round((t + ms) * 10) / 10, duration: Math.round(ms) });
t += ms;
}
var queue = 1 + (request.type === "Image" ? 15 : 3) * random(); // images wait behind more urgent requests
// a request to a server that is still being connected to waits for that connection (the browser calls it stalled)
if (connectedAt[request.host] !== undefined) {
queue = Math.max(queue, connectedAt[request.host] - t);
}
phase("Queueing", queue);
if (connectedAt[request.host] === undefined) {
// the first request to a server finds it and connects to it; later ones reuse the connection
phase("DNS lookup", profile.rtt * (0.5 + 0.5 * random()));
phase("Connecting", profile.rtt * 2); // TCP, then TLS
connectedAt[request.host] = t;
}
phase("Waiting for server", profile.rtt + request.server);
// a bit under the line's speed: the other downloads share it
phase("Content download", Math.max(1, request.size / kbPerMs * (1.1 + 0.4 * random())));
parts.forEach(function(p) {
p.type = request.type;
p.host = request.host;
p.size = request.size;
phases.push(p);
});
done[request.url] = t;
// the page's milestones: the HTML's first bytes, then DOMContentLoaded once the HTML, the styles and the
// scripts are in and the scripts have run
if (request.url === "index.html") { events.html = t - (t - parts[parts.length - 1].start) * 0.7; }
if (request.url === "app.js") {
events.dcl = Math.max(done["index.html"], done["styles.css"], done["vendor.js"], done["app.js"]) + 90;
}
if (request.url === "analytics.js") {
// load fires once everything the page itself loads is in: its files and images, and a script added before load,
// like the analytics. Requests made with fetch() don't hold it
events.load = Math.max.apply(null, REQUESTS.filter(function(q) {
return q.type !== "Fetch" && done[q.url] !== undefined;
}).map(function(q) { return done[q.url]; })) + 15;
}
});
return { phases: phases, dcl: Math.round(events.dcl), load: Math.round(events.load), end: Math.max.apply(null, phases.map(function(p) { return p.end; })) };
}
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "none",
wheelY: "none",
paddingRight: 30, // room for the last time label, which is centered on the plot's edge
layout: root.verticalLayout
}));
// Add legend: the phases
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.children.unshift(am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50,
marginBottom: 10
}));
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
// The requests down the side, in the order the browser made them
var yRenderer = am5xy.AxisRendererY.new(root, {
inversed: true, // the first request at the top
minGridDistance: 8, // a label for every request
cellStartLocation: 0.2, // the bars thinner than the rows
cellEndLocation: 0.8
});
yRenderer.labels.template.setAll({
fontSize: "0.8em",
maxWidth: 130,
oversizedBehavior: "truncate"
});
yRenderer.grid.template.set("strokeOpacity", 0.05);
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "url",
renderer: yRenderer
}));
yAxis.data.setAll(REQUESTS);
// The time since the page was asked for, along the top
var xRenderer = am5xy.AxisRendererX.new(root, {
opposite: true,
minGridDistance: 70
});
xRenderer.grid.template.set("strokeOpacity", 0.06);
var xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
min: 0,
numberFormat: "#,###' ms'",
renderer: xRenderer
}));
// Create series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
// One series per phase, so the legend names them and can hide each: a phase is a floating bar on its request's row,
// from when it started to when it ended. The queue in a faint gray, the rest in the theme's colors
var gray = root.interfaceColors.get("alternativeBackground");
var series = ["Queueing", "DNS lookup", "Connecting", "Waiting for server", "Content download"].map(function(name) {
var settings = {
name: name,
xAxis: xAxis,
yAxis: yAxis,
openValueXField: "start",
valueXField: "end",
categoryYField: "url",
clustered: false // the phases share their request's row
};
if (name === "Queueing") {
settings.fill = gray;
settings.stroke = gray;
}
var s = chart.series.push(am5xy.ColumnSeries.new(root, settings));
s.columns.template.setAll({
strokeOpacity: 0,
fillOpacity: name === "Queueing" ? 0.25 : 1,
tooltipText: "[bold]{url}[/]\n" + name + ": {duration} ms\n{type}, {size} KB from {host}",
tooltipY: 0
});
legend.data.push(s);
return s;
});
// The page's two milestones as lines across the requests: DOMContentLoaded (the HTML read and its scripts run) and
// load (everything the page asked for is in)
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/axis-ranges/
function milestone(color, side) {
var range = xAxis.createAxisRange(xAxis.makeDataItem({ value: 0 }));
range.get("grid").setAll({
forceHidden: false,
stroke: color,
strokeOpacity: 1,
strokeWidth: 1.5,
strokeDasharray: [4, 3]
});
range.get("label").setAll({
forceHidden: false,
fill: color,
fontSize: "0.75em",
fontWeight: "600",
dy: 24, // down into the plot, below the time axis's own labels
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 4,
paddingRight: 4,
background: am5.RoundedRectangle.new(root, {
fill: root.interfaceColors.get("background"), // the chart's background, so the bars behind don't show through
fillOpacity: 0.9
}),
centerY: 0,
// DOMContentLoaded's name to the left of its line, Load's to the right, so the two never overlap
centerX: side < 0 ? am5.p100 : 0,
dx: 5 * side
});
return range;
}
var dclRange = milestone(root.interfaceColors.get("primaryButton"), -1);
var loadRange = milestone(root.interfaceColors.get("negative"), 1);
// On a narrow chart (a phone) the legend would take most of the height: it is hidden there, and so are the milestones' labels (their lines stay). The rule is
// undone on a wider chart
responsive.addRule({
relevant: am5themes_Responsive.widthM, // the chart is 600px wide or less
applying: function() {
legend.set("forceHidden", true);
dclRange.get("label").set("forceHidden", true); // the lines stay
loadRange.get("label").set("forceHidden", true);
},
removing: function() {
legend.set("forceHidden", false);
dclRange.get("label").set("forceHidden", false);
loadRange.get("label").set("forceHidden", false);
}
});
// Shows the page loading at a network speed: "none", "fast4g", "slow4g" or "slow3g"
function showRequests(name) {
var result = simulate(PROFILES[name]);
series.forEach(function(s) {
s.data.setAll(result.phases.filter(function(p) { return p.phase === s.get("name"); }));
});
dclRange.set("value", result.dcl);
dclRange.get("label").set("text", "DOMContentLoaded " + result.dcl.toLocaleString("en-US") + " ms");
loadRange.set("value", result.load);
loadRange.get("label").set("text", "Load " + result.load.toLocaleString("en-US") + " ms");
xAxis.set("max", Math.ceil(result.end * 1.04 / 50) * 50); // the last request, with a little room
}
showRequests("none");
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": false,
"panY": false,
"wheelX": "none",
"wheelY": "none",
"paddingRight": 30,
"layout": {
"type": "VerticalLayout"
}
},
"properties": {
"series": [
"#series-0",
"#series-1",
"#series-2",
"#series-3",
"#series-4"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
]
},
"children": [
{
"type": "Legend",
"settings": {
"centerX": {
"type": "Percent",
"value": 50
},
"x": {
"type": "Percent",
"value": 50
},
"marginBottom": 10
},
"properties": {
"data": [
"#series-0",
"#series-1",
"#series-2",
"#series-3",
"#series-4"
]
},
"index": 0
}
],
"refs": [
{
"data-0": [
{
"url": "index.html",
"phase": "Queueing",
"start": 0,
"end": 2.4,
"duration": 2,
"type": "Document",
"host": "shop.example.com",
"size": 32,
"__parse": false
},
{
"url": "styles.css",
"phase": "Queueing",
"start": 281.1,
"end": 282.6,
"duration": 1,
"type": "Stylesheet",
"host": "shop.example.com",
"size": 48,
"__parse": false
},
{
"url": "vendor.js",
"phase": "Queueing",
"start": 283.1,
"end": 286.5,
"duration": 3,
"type": "Script",
"host": "shop.example.com",
"size": 420,
"__parse": false
},
{
"url": "app.js",
"phase": "Queueing",
"start": 263.9,
"end": 265.1,
"duration": 1,
"type": "Script",
"host": "shop.example.com",
"size": 310,
"__parse": false
},
{
"url": "fonts.css",
"phase": "Queueing",
"start": 274.2,
"end": 275.6,
"duration": 1,
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"__parse": false
},
{
"url": "inter.woff2",
"phase": "Queueing",
"start": 378.4,
"end": 380.6,
"duration": 2,
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"__parse": false
},
{
"url": "inter-bold.woff2",
"phase": "Queueing",
"start": 378.4,
"end": 439.8,
"duration": 61,
"type": "Font",
"host": "fonts.example.org",
"size": 41,
"__parse": false
},
{
"url": "logo.svg",
"phase": "Queueing",
"start": 270.9,
"end": 283.5,
"duration": 13,
"type": "Image",
"host": "shop.example.com",
"size": 6,
"__parse": false
},
{
"url": "hero.webp",
"phase": "Queueing",
"start": 290.8,
"end": 303.3,
"duration": 12,
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"__parse": false
},
{
"url": "product-1.webp",
"phase": "Queueing",
"start": 275,
"end": 356.1,
"duration": 81,
"type": "Image",
"host": "img.example-cdn.com",
"size": 64,
"__parse": false
},
{
"url": "product-2.webp",
"phase": "Queueing",
"start": 287.9,
"end": 356.1,
"duration": 68,
"type": "Image",
"host": "img.example-cdn.com",
"size": 58,
"__parse": false
},
{
"url": "product-3.webp",
"phase": "Queueing",
"start": 265,
"end": 356.1,
"duration": 91,
"type": "Image",
"host": "img.example-cdn.com",
"size": 71,
"__parse": false
},
{
"url": "product-4.webp",
"phase": "Queueing",
"start": 285.8,
"end": 356.1,
"duration": 70,
"type": "Image",
"host": "img.example-cdn.com",
"size": 49,
"__parse": false
},
{
"url": "api/user",
"phase": "Queueing",
"start": 371.9,
"end": 375.8,
"duration": 4,
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"__parse": false
},
{
"url": "api/products",
"phase": "Queueing",
"start": 371.9,
"end": 432.6,
"duration": 61,
"type": "Fetch",
"host": "api.example.com",
"size": 24,
"__parse": false
},
{
"url": "analytics.js",
"phase": "Queueing",
"start": 498.3,
"end": 502.3,
"duration": 4,
"type": "Script",
"host": "stats.example.io",
"size": 45,
"__parse": false
},
{
"url": "favicon.ico",
"phase": "Queueing",
"start": 639.8,
"end": 653.3,
"duration": 13,
"type": "Image",
"host": "shop.example.com",
"size": 4,
"__parse": false
}
]
},
{
"data-1": [
{
"url": "index.html",
"phase": "DNS lookup",
"start": 2.4,
"end": 15.5,
"duration": 13,
"type": "Document",
"host": "shop.example.com",
"size": 32,
"__parse": false
},
{
"url": "fonts.css",
"phase": "DNS lookup",
"start": 275.6,
"end": 287.4,
"duration": 12,
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"__parse": false
},
{
"url": "inter.woff2",
"phase": "DNS lookup",
"start": 380.6,
"end": 399.8,
"duration": 19,
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"__parse": false
},
{
"url": "hero.webp",
"phase": "DNS lookup",
"start": 303.3,
"end": 316.1,
"duration": 13,
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"__parse": false
},
{
"url": "api/user",
"phase": "DNS lookup",
"start": 375.8,
"end": 392.6,
"duration": 17,
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"__parse": false
},
{
"url": "analytics.js",
"phase": "DNS lookup",
"start": 502.3,
"end": 514.1,
"duration": 12,
"type": "Script",
"host": "stats.example.io",
"size": 45,
"__parse": false
}
]
},
{
"data-2": [
{
"url": "index.html",
"phase": "Connecting",
"start": 15.5,
"end": 55.5,
"duration": 40,
"type": "Document",
"host": "shop.example.com",
"size": 32,
"__parse": false
},
{
"url": "fonts.css",
"phase": "Connecting",
"start": 287.4,
"end": 327.4,
"duration": 40,
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"__parse": false
},
{
"url": "inter.woff2",
"phase": "Connecting",
"start": 399.8,
"end": 439.8,
"duration": 40,
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"__parse": false
},
{
"url": "hero.webp",
"phase": "Connecting",
"start": 316.1,
"end": 356.1,
"duration": 40,
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"__parse": false
},
{
"url": "api/user",
"phase": "Connecting",
"start": 392.6,
"end": 432.6,
"duration": 40,
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"__parse": false
},
{
"url": "analytics.js",
"phase": "Connecting",
"start": 514.1,
"end": 554.1,
"duration": 40,
"type": "Script",
"host": "stats.example.io",
"size": 45,
"__parse": false
}
]
},
{
"data-3": [
{
"url": "index.html",
"phase": "Waiting for server",
"start": 55.5,
"end": 255.5,
"duration": 200,
"type": "Document",
"host": "shop.example.com",
"size": 32,
"__parse": false
},
{
"url": "styles.css",
"phase": "Waiting for server",
"start": 282.6,
"end": 327.6,
"duration": 45,
"type": "Stylesheet",
"host": "shop.example.com",
"size": 48,
"__parse": false
},
{
"url": "vendor.js",
"phase": "Waiting for server",
"start": 286.5,
"end": 326.5,
"duration": 40,
"type": "Script",
"host": "shop.example.com",
"size": 420,
"__parse": false
},
{
"url": "app.js",
"phase": "Waiting for server",
"start": 265.1,
"end": 305.1,
"duration": 40,
"type": "Script",
"host": "shop.example.com",
"size": 310,
"__parse": false
},
{
"url": "fonts.css",
"phase": "Waiting for server",
"start": 327.4,
"end": 377.4,
"duration": 50,
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"__parse": false
},
{
"url": "inter.woff2",
"phase": "Waiting for server",
"start": 439.8,
"end": 474.8,
"duration": 35,
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"__parse": false
},
{
"url": "inter-bold.woff2",
"phase": "Waiting for server",
"start": 439.8,
"end": 474.8,
"duration": 35,
"type": "Font",
"host": "fonts.example.org",
"size": 41,
"__parse": false
},
{
"url": "logo.svg",
"phase": "Waiting for server",
"start": 283.5,
"end": 313.5,
"duration": 30,
"type": "Image",
"host": "shop.example.com",
"size": 6,
"__parse": false
},
{
"url": "hero.webp",
"phase": "Waiting for server",
"start": 356.1,
"end": 406.1,
"duration": 50,
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"__parse": false
},
{
"url": "product-1.webp",
"phase": "Waiting for server",
"start": 356.1,
"end": 401.1,
"duration": 45,
"type": "Image",
"host": "img.example-cdn.com",
"size": 64,
"__parse": false
},
{
"url": "product-2.webp",
"phase": "Waiting for server",
"start": 356.1,
"end": 401.1,
"duration": 45,
"type": "Image",
"host": "img.example-cdn.com",
"size": 58,
"__parse": false
},
{
"url": "product-3.webp",
"phase": "Waiting for server",
"start": 356.1,
"end": 401.1,
"duration": 45,
"type": "Image",
"host": "img.example-cdn.com",
"size": 71,
"__parse": false
},
{
"url": "product-4.webp",
"phase": "Waiting for server",
"start": 356.1,
"end": 401.1,
"duration": 45,
"type": "Image",
"host": "img.example-cdn.com",
"size": 49,
"__parse": false
},
{
"url": "api/user",
"phase": "Waiting for server",
"start": 432.6,
"end": 542.6,
"duration": 110,
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"__parse": false
},
{
"url": "api/products",
"phase": "Waiting for server",
"start": 432.6,
"end": 592.6,
"duration": 160,
"type": "Fetch",
"host": "api.example.com",
"size": 24,
"__parse": false
},
{
"url": "analytics.js",
"phase": "Waiting for server",
"start": 554.1,
"end": 614.1,
"duration": 60,
"type": "Script",
"host": "stats.example.io",
"size": 45,
"__parse": false
},
{
"url": "favicon.ico",
"phase": "Waiting for server",
"start": 653.3,
"end": 683.3,
"duration": 30,
"type": "Image",
"host": "shop.example.com",
"size": 4,
"__parse": false
}
]
},
{
"data-4": [
{
"url": "index.html",
"phase": "Content download",
"start": 255.5,
"end": 262.4,
"duration": 7,
"type": "Document",
"host": "shop.example.com",
"size": 32,
"__parse": false
},
{
"url": "styles.css",
"phase": "Content download",
"start": 327.6,
"end": 336.7,
"duration": 9,
"type": "Stylesheet",
"host": "shop.example.com",
"size": 48,
"__parse": false
},
{
"url": "vendor.js",
"phase": "Content download",
"start": 326.5,
"end": 408.3,
"duration": 82,
"type": "Script",
"host": "shop.example.com",
"size": 420,
"__parse": false
},
{
"url": "app.js",
"phase": "Content download",
"start": 305.1,
"end": 371.9,
"duration": 67,
"type": "Script",
"host": "shop.example.com",
"size": 310,
"__parse": false
},
{
"url": "fonts.css",
"phase": "Content download",
"start": 377.4,
"end": 378.4,
"duration": 1,
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"__parse": false
},
{
"url": "inter.woff2",
"phase": "Content download",
"start": 474.8,
"end": 482.9,
"duration": 8,
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"__parse": false
},
{
"url": "inter-bold.woff2",
"phase": "Content download",
"start": 474.8,
"end": 482.6,
"duration": 8,
"type": "Font",
"host": "fonts.example.org",
"size": 41,
"__parse": false
},
{
"url": "logo.svg",
"phase": "Content download",
"start": 313.5,
"end": 315,
"duration": 1,
"type": "Image",
"host": "shop.example.com",
"size": 6,
"__parse": false
},
{
"url": "hero.webp",
"phase": "Content download",
"start": 406.1,
"end": 440.9,
"duration": 35,
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"__parse": false
},
{
"url": "product-1.webp",
"phase": "Content download",
"start": 401.1,
"end": 415.1,
"duration": 14,
"type": "Image",
"host": "img.example-cdn.com",
"size": 64,
"__parse": false
},
{
"url": "product-2.webp",
"phase": "Content download",
"start": 401.1,
"end": 414.7,
"duration": 14,
"type": "Image",
"host": "img.example-cdn.com",
"size": 58,
"__parse": false
},
{
"url": "product-3.webp",
"phase": "Content download",
"start": 401.1,
"end": 415.1,
"duration": 14,
"type": "Image",
"host": "img.example-cdn.com",
"size": 71,
"__parse": false
},
{
"url": "product-4.webp",
"phase": "Content download",
"start": 401.1,
"end": 411,
"duration": 10,
"type": "Image",
"host": "img.example-cdn.com",
"size": 49,
"__parse": false
},
{
"url": "api/user",
"phase": "Content download",
"start": 542.6,
"end": 544.3,
"duration": 2,
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"__parse": false
},
{
"url": "api/products",
"phase": "Content download",
"start": 592.6,
"end": 596.9,
"duration": 4,
"type": "Fetch",
"host": "api.example.com",
"size": 24,
"__parse": false
},
{
"url": "analytics.js",
"phase": "Content download",
"start": 614.1,
"end": 624.8,
"duration": 11,
"type": "Script",
"host": "stats.example.io",
"size": 45,
"__parse": false
},
{
"url": "favicon.ico",
"phase": "Content download",
"start": 683.3,
"end": 684.3,
"duration": 1,
"type": "Image",
"host": "shop.example.com",
"size": 4,
"__parse": false
}
]
},
{
"data-5": [
{
"url": "index.html",
"type": "Document",
"host": "shop.example.com",
"size": 32,
"server": 180,
"after": "start",
"__parse": false
},
{
"url": "styles.css",
"type": "Stylesheet",
"host": "shop.example.com",
"size": 48,
"server": 25,
"after": "html",
"__parse": false
},
{
"url": "vendor.js",
"type": "Script",
"host": "shop.example.com",
"size": 420,
"server": 20,
"after": "html",
"__parse": false
},
{
"url": "app.js",
"type": "Script",
"host": "shop.example.com",
"size": 310,
"server": 20,
"after": "html",
"__parse": false
},
{
"url": "fonts.css",
"type": "Stylesheet",
"host": "fonts.example.net",
"size": 2,
"server": 30,
"after": "html",
"__parse": false
},
{
"url": "inter.woff2",
"type": "Font",
"host": "fonts.example.org",
"size": 38,
"server": 15,
"after": "fonts.css",
"__parse": false
},
{
"url": "inter-bold.woff2",
"type": "Font",
"host": "fonts.example.org",
"size": 41,
"server": 15,
"after": "fonts.css",
"__parse": false
},
{
"url": "logo.svg",
"type": "Image",
"host": "shop.example.com",
"size": 6,
"server": 10,
"after": "html",
"__parse": false
},
{
"url": "hero.webp",
"type": "Image",
"host": "img.example-cdn.com",
"size": 180,
"server": 30,
"after": "html",
"__parse": false
},
{
"url": "product-1.webp",
"type": "Image",
"host": "img.example-cdn.com",
"size": 64,
"server": 25,
"after": "html",
"__parse": false
},
{
"url": "product-2.webp",
"type": "Image",
"host": "img.example-cdn.com",
"size": 58,
"server": 25,
"after": "html",
"__parse": false
},
{
"url": "product-3.webp",
"type": "Image",
"host": "img.example-cdn.com",
"size": 71,
"server": 25,
"after": "html",
"__parse": false
},
{
"url": "product-4.webp",
"type": "Image",
"host": "img.example-cdn.com",
"size": 49,
"server": 25,
"after": "html",
"__parse": false
},
{
"url": "api/user",
"type": "Fetch",
"host": "api.example.com",
"size": 8,
"server": 90,
"after": "app.js",
"__parse": false
},
{
"url": "api/products",
"type": "Fetch",
"host": "api.example.com",
"size": 24,
"server": 140,
"after": "app.js",
"__parse": false
},
{
"url": "analytics.js",
"type": "Script",
"host": "stats.example.io",
"size": 45,
"server": 40,
"after": "dcl",
"__parse": false
},
{
"url": "favicon.ico",
"type": "Image",
"host": "shop.example.com",
"size": 4,
"server": 10,
"after": "load",
"__parse": false
}
]
},
{
"axis-0": {
"type": "ValueAxis",
"settings": {
"min": 0,
"numberFormat": "##,###' ms'",
"renderer": {
"type": "AxisRendererX",
"settings": {
"opposite": true,
"minGridDistance": 70
},
"properties": {
"grid": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0.06
}
}
}
}
}
},
"max": 750
}
}
},
{
"axis-1": {
"type": "CategoryAxis",
"settings": {
"categoryField": "url",
"renderer": {
"type": "AxisRendererY",
"settings": {
"inversed": true,
"minGridDistance": 8,
"cellStartLocation": 0.2,
"cellEndLocation": 0.8
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"fontSize": "0.8em",
"maxWidth": 130,
"oversizedBehavior": "truncate"
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0.05
}
}
}
}
}
}
},
"properties": {
"data": "#data-5"
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"name": "Queueing",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"openValueXField": "start",
"valueXField": "end",
"categoryYField": "url",
"clustered": false,
"fill": "@root.interfaceColors.get('alternativeBackground')",
"stroke": "@root.interfaceColors.get('alternativeBackground')"
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0,
"fillOpacity": 0.25,
"tooltipText": "[bold]{url}[/]\nQueueing: {duration} ms\n{type}, {size} KB from {host}",
"tooltipY": 0
}
}
}
}
}
}
},
{
"series-1": {
"type": "ColumnSeries",
"settings": {
"name": "DNS lookup",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"openValueXField": "start",
"valueXField": "end",
"categoryYField": "url",
"clustered": false
},
"properties": {
"data": "#data-1",
"columns": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0,
"fillOpacity": 1,
"tooltipText": "[bold]{url}[/]\nDNS lookup: {duration} ms\n{type}, {size} KB from {host}",
"tooltipY": 0
}
}
}
}
}
}
},
{
"series-2": {
"type": "ColumnSeries",
"settings": {
"name": "Connecting",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"openValueXField": "start",
"valueXField": "end",
"categoryYField": "url",
"clustered": false
},
"properties": {
"data": "#data-2",
"columns": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0,
"fillOpacity": 1,
"tooltipText": "[bold]{url}[/]\nConnecting: {duration} ms\n{type}, {size} KB from {host}",
"tooltipY": 0
}
}
}
}
}
}
},
{
"series-3": {
"type": "ColumnSeries",
"settings": {
"name": "Waiting for server",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"openValueXField": "start",
"valueXField": "end",
"categoryYField": "url",
"clustered": false
},
"properties": {
"data": "#data-3",
"columns": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0,
"fillOpacity": 1,
"tooltipText": "[bold]{url}[/]\nWaiting for server: {duration} ms\n{type}, {size} KB from {host}",
"tooltipY": 0
}
}
}
}
}
}
},
{
"series-4": {
"type": "ColumnSeries",
"settings": {
"name": "Content download",
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"openValueXField": "start",
"valueXField": "end",
"categoryYField": "url",
"clustered": false
},
"properties": {
"data": "#data-4",
"columns": {
"properties": {
"template": {
"settings": {
"strokeOpacity": 0,
"fillOpacity": 1,
"tooltipText": "[bold]{url}[/]\nContent download: {duration} ms\n{type}, {size} KB from {host}",
"tooltipY": 0
}
}
}
}
}
}
},
{
"axisRange-0": {
"axis": "#axis-0",
"settings": {
"value": 498,
"label": {
"type": "AxisLabel",
"settings": {
"centerY": 0,
"centerX": {
"type": "Percent",
"value": 100
},
"paddingTop": 2,
"paddingBottom": 2,
"paddingLeft": 4,
"paddingRight": 4,
"fontSize": "0.75em",
"fill": "@root.interfaceColors.get('primaryButton')",
"forceHidden": false,
"fontWeight": "600",
"dy": 24,
"background": {
"type": "RoundedRectangle",
"settings": {
"fill": "@root.interfaceColors.get('background')",
"fillOpacity": 0.9
}
},
"dx": -5,
"text": "DOMContentLoaded 498 ms"
}
},
"grid": {
"type": "Grid",
"settings": {
"strokeOpacity": 1,
"stroke": "@root.interfaceColors.get('primaryButton')",
"strokeWidth": 1.5,
"forceHidden": false,
"strokeDasharray": [4, 3]
}
}
},
"__parseLogic": "axisRange"
}
},
{
"axisRange-1": {
"axis": "#axis-0",
"settings": {
"value": 640,
"label": {
"type": "AxisLabel",
"settings": {
"centerY": 0,
"centerX": 0,
"paddingTop": 2,
"paddingBottom": 2,
"paddingLeft": 4,
"paddingRight": 4,
"fontSize": "0.75em",
"fill": "@root.interfaceColors.get('negative')",
"forceHidden": false,
"fontWeight": "600",
"dy": 24,
"background": {
"type": "RoundedRectangle",
"settings": {
"fill": "@root.interfaceColors.get('background')",
"fillOpacity": 0.9
}
},
"dx": 5,
"text": "Load 640 ms"
}
},
"grid": {
"type": "Grid",
"settings": {
"strokeOpacity": 1,
"stroke": "@root.interfaceColors.get('negative')",
"strokeWidth": 1.5,
"forceHidden": false,
"strokeDasharray": [4, 3]
}
}
},
"__parseLogic": "axisRange"
}
}
]
}



