Created
November 14, 2019 06:04
-
-
Save nodaguti/0ad99b83d9b7a5addbb99e5260f762ec to your computer and use it in GitHub Desktop.
Resource Loading Performance Analyser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(() => { | |
// Based on: | |
// https://github.com/micmro/performance-bookmarklet | |
// MIT License Copyright (c) 2014 Michael Mrowetz | |
// https://github.com/micmro/performance-bookmarklet/blob/master/LICENSE | |
//extract a resources file type | |
const getFileType = (fileExtension, initiatorType) => { | |
if(fileExtension){ | |
switch(fileExtension){ | |
case "jpg" : | |
case "jpeg" : | |
case "png" : | |
case "gif" : | |
case "webp" : | |
case "svg" : | |
case "ico" : | |
return "image"; | |
case "js" : | |
return "js" | |
case "css": | |
return "css" | |
case "html": | |
return "html" | |
case "woff": | |
case "woff2": | |
case "ttf": | |
case "eot": | |
case "otf": | |
return "font" | |
case "swf": | |
return "flash" | |
case "map": | |
return "source-map" | |
} | |
} | |
if(initiatorType){ | |
switch(initiatorType){ | |
case "xmlhttprequest" : | |
return "ajax" | |
case "img" : | |
return "image" | |
case "script" : | |
return "js" | |
case "internal" : | |
case "iframe" : | |
return "html" //actual page | |
default : | |
return "other" | |
} | |
} | |
return initiatorType; | |
}; | |
const map = (arr, predicate) => { | |
const result = {}; | |
arr.forEach((item) => { | |
const key = predicate(item); | |
if (!result[key]) { | |
result[key] = []; | |
} | |
result[key].push(item); | |
}); | |
return result; | |
} | |
// -------------------------------------------------- | |
const resources = window.performance.getEntriesByType("resource"); | |
const marks = window.performance.getEntriesByType("mark"); | |
marks.reduce((prev, curr) => { | |
if (!prev) return curr; | |
window.performance.measure(`${prev.name} - ${curr.name}`, prev.name, curr.name); | |
return curr; | |
}, null); | |
const measures = window.performance.getEntriesByType("measure"); | |
const perfTiming = window.performance.timing; | |
const injectMetadataToEntry = (entry) => { | |
//crunch the resources data into something easier to work with | |
const isRequest = entry.name.startsWith("http"); | |
let urlFragments, maybeFileName, fileExtension; | |
if(isRequest){ | |
urlFragments = entry.name.match(/:\/\/(.[^/]+)([^?]*)\??(.*)/); | |
maybeFileName = urlFragments[2].split("/").pop(); | |
fileExtension = maybeFileName.substr((Math.max(0, maybeFileName.lastIndexOf(".")) || Infinity) + 1); | |
}else{ | |
urlFragments = ["", location.host]; | |
fileExtension = entry.name.split(":")[0]; | |
} | |
const currRes = { | |
name : entry.name, | |
domain : urlFragments[1], | |
initiatorType : entry.initiatorType || fileExtension || "SourceMap or Not Defined", | |
fileExtension : fileExtension || "XHR or Not Defined", | |
loadtime : entry.duration, | |
fileType : getFileType(fileExtension, entry.initiatorType), | |
isRequestToHost : urlFragments[1] === location.host | |
}; | |
for(let attr in entry){ | |
if(typeof entry[attr] !== "function") { | |
currRes[attr] = entry[attr]; | |
} | |
} | |
if(entry.requestStart){ | |
currRes.requestStartDelay = entry.requestStart - entry.startTime; | |
currRes.dns = entry.domainLookupEnd - entry.domainLookupStart; | |
currRes.tcp = entry.connectEnd - entry.connectStart; | |
currRes.ttfb = entry.responseStart - entry.startTime; | |
currRes.requestDuration = entry.responseStart - entry.requestStart; | |
} | |
if(entry.secureConnectionStart){ | |
currRes.ssl = entry.connectEnd - entry.secureConnectionStart; | |
} | |
return currRes; | |
}; | |
const getDurationParallelAndTotal = (entries) => { | |
let lastResponseEnd = 0; | |
let parallel = 0; | |
let total = 0; | |
entries.forEach((entry) => { | |
if (lastResponseEnd <= entry.startTime) { | |
parallel += entry.duration; | |
} else if (lastResponseEnd < entry.responseEnd) { | |
parallel += entry.responseEnd - lastResponseEnd; | |
} | |
total += entry.duration; | |
lastResponseEnd = entry.responseEnd; | |
}); | |
return { | |
parallel, | |
total, | |
}; | |
} | |
const getNearestMarkName = (entry) => { | |
const mark = marks.find((m) => m.startTime >= entry.startTime); | |
return mark ? mark.name : `After ${marks[marks.length - 1].name}`; | |
} | |
const resourcesWithMetadata = resources | |
//remove this bookmarklet from the result | |
.filter((currR) => !currR.name.match(/http[s]?\:\/\/(micmro|nurun).github.io\/performance-bookmarklet\/.*/)) | |
.map(injectMetadataToEntry); | |
const requests = resourcesWithMetadata.filter((currR) => { | |
return currR.name.startsWith("http") && !currR.name.match(/js.map$/); | |
}); | |
const requestsByMark = map(requests, getNearestMarkName); | |
let csv = []; | |
csv.push('mark, domain, parallel, total'); | |
Object.entries(requestsByMark) | |
.forEach(([mark, entries]) => { | |
const requestsByDomain = map(entries, (e) => e.domain); | |
Object.entries(requestsByDomain) | |
.forEach(([domain, entries2]) => { | |
const { total, parallel } = getDurationParallelAndTotal(entries2); | |
csv.push(`${mark}, ${domain}, ${parallel}, ${total}`); | |
}); | |
}); | |
console.log(csv.join('\n')); | |
csv.push('mark, domain, url, duration'); | |
csv.length = 0; | |
Object.entries(requestsByMark) | |
.forEach(([mark, entries]) => { | |
const requestsByDomain = map(entries, (e) => e.domain); | |
Object.entries(requestsByDomain) | |
.forEach(([domain, entries2]) => { | |
entries2.forEach((entry) => { | |
csv.push(`${mark}, ${domain}, ${entry.name}, ${entry.duration}`); | |
}); | |
}); | |
}); | |
console.log(csv.join('\n')); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment