Зачем мониторить
Используя (верхние) домохозяйства (Император), чтобы сказать, почему эта страница такая медленная, кого-то это все еще волнует? !
Проще говоря, есть три причины:
- Сосредоточение внимания на производительности — это природа + обязанность инженеров;
- Производительность страницы имеет решающее значение для взаимодействия с пользователем. Неубедительно полагаться только на тестовые данные оборудования разработки инженера для улучшения производительности страницы каждый раз при рефакторинге, а для проверки требуется большой объем реальных данных;
- При зависании ресурса или нештатной загрузке нельзя всегда полагаться на жалобы пользователей, чтобы узнать об этом позже, и нужно проявлять инициативу, чтобы сообщить в полицию.
Реконструкция производительности, при условии гигабитной скорости сети и оборудования стоимостью 10 000 юаней, улучшение времени загрузки страницы может составлять всего 0,1%, но такие данные (земля) данные (хао) не являются репрезентативными. Сетевые среды и аппаратные устройства сильно различаются.Для недорогих устройств субъективный опыт повышения производительности более очевиден, а соответствующие изменения данных более репрезентативны.
Многие проекты загружают ресурсы в CDN. Когда возникает проблема с какими-то узлами CDN, то вообще невозможно точно сообщить «Такой-то, ваш ресурс xx не работает», поэтому нам нужно активно следить за этим.
По данным Google, когда страница загружается более 10 секунд, пользователи впадают в отчаяние, обычно покидают текущую страницу и, скорее всего, никогда не возвращаются.
что контролировать
Что касается показателей производительности внешнего интерфейса, W3C определяет мощныйPerformance
API, который, в свою очередь, включаетHigh Resolution Time
,Frame Timing
,Navigation Timing
,Performance Timeline
,Resource Timing
,User Timing
и многие другие специальные стандарты.
В этой статье в основном рассматриваютсяNavigation Timing
а такжеResource Timing
. По состоянию на середину 2018 года все основные браузеры завершили базовую реализацию.
Performance
API имеет множество функций, одна из которых — запись производительности (сведения о времени) самой страницы и каждого ресурса на странице. И все, что нам нужно сделать, это запросить и использовать.
Читатели могут печатать прямо в консоли браузера.
performance
, см. соответствующие API.
Далее мы будем использоватьwindow.performance
объект (Performance
конкретная реализация API) для реализации простого внешнего инструмента мониторинга производительности.
Интерфейсный инструмент для мониторинга производительности за 5 минут
первая строка кода
Назовите инструмент какpMonitor
, означающий, чтоperformance monitor
.
const pMonitor = {}
Какие показатели отслеживать
Поскольку это серия «5 минут, чтобы достичь ххх», то должен быть компромисс. Поэтому в данной статье выбраны только два наиболее важных показателя для мониторинга:
- время загрузки страницы
- время запроса ресурса
Глядя на время, прошло 4 минуты Редактор сказал, что настроение у него стабильное, колебаний нет.
загрузка страницы
Показатели производительности для загрузки страницы можно найти по адресуNavigation Timing
найти в.Navigation Timing
Он включает в себя сведения о времени каждой ссылки с момента запроса страницы до ее загрузки.
можно получить поNavigation Timing
Конкретное содержание:
const navTimes = performance.getEntriesByType('navigation')
getEntriesByType
это способ для нас получить данные о производительности.performance
также обеспечиваетgetEntries
а такжеgetEntriesByName
В прочем, из-за "нехватки времени" конкретные отличия здесь повторяться не будут, можно перейти к этому:Woohoo. Я 3.org/TR/perform A….
Результатом является массив, структура элементов которого показана ниже:
{
"connectEnd": 64.15495765894057,
"connectStart": 64.15495765894057,
"domainLookupEnd": 64.15495765894057,
"domainLookupStart": 64.15495765894057,
"domComplete": 2002.5385066728431,
"domContentLoadedEventEnd": 2001.7384263440083,
"domContentLoadedEventStart": 2001.2386167400286,
"domInteractive": 1988.638474368076,
"domLoading": 271.75174283737226,
"duration": 2002.9385468372606,
"entryType": "navigation",
"fetchStart": 64.15495765894057,
"loadEventEnd": 2002.9385468372606,
"loadEventStart": 2002.7383663540235,
"name": "document",
"navigationStart": 0,
"redirectCount": 0,
"redirectEnd": 0,
"redirectStart": 0,
"requestStart": 65.28225608537441,
"responseEnd": 1988.283025689508,
"responseStart": 271.75174283737226,
"startTime": 0,
"type": "navigate",
"unloadEventEnd": 0,
"unloadEventStart": 0,
"workerStart": 0.9636893776343863
}
О временных значениях каждого поля,Navigation Timing Level 2Даны подробные инструкции:
Нетрудно заметить, что деталей полно. Таким образом, содержимое, которое можно рассчитать, очень разнообразно, например, время запроса DNS, время рукопожатия TLS и т. д. Можно сказать, что есть только невообразимое, нет невозможного~
Поскольку нас интересует загрузка страницы, естественно прочитатьdomComplete
:
const [{ domComplete }] = performance.getEntriesByType('navigation')
определить метод полученияdomComplete
:
pMonitor.getLoadTime = () => {
const [{ domComplete }] = performance.getEntriesByType('navigation')
return domComplete
}
На данный момент у нас есть точное время загрузки страницы.
загрузка ресурсов
Так как на странице есть соответствующийNavigation Timing
, есть ли соответствующий статический ресурс?Timing
Шерстяная ткань?
Ответ - да, его зовутResource Timing
. Он содержит сведения о времени каждой ссылки каждого ресурса на странице с момента отправки запроса до завершения загрузки иNavigation Timing
очень похожий.
Ключевое слово для получения времени загрузки ресурса'resource'
, конкретный метод заключается в следующем:
performance.getEntriesByType('resource')
Нетрудно представить, что возвращаемый результат обычно представляет собой очень длинный массив, поскольку он содержит информацию о загрузке всех ресурсов на странице.
Конкретная структура каждого сообщения:
{
"connectEnd": 462.95008929525244,
"connectStart": 462.95008929525244,
"domainLookupEnd": 462.95008929525244,
"domainLookupStart": 462.95008929525244,
"duration": 0.9620853673520173,
"entryType": "resource",
"fetchStart": 462.95008929525244,
"initiatorType": "img",
"name": "https://cn.bing.com/sa/simg/SharedSpriteDesktopRewards_022118.png",
"nextHopProtocol": "",
"redirectEnd": 0,
"redirectStart": 0,
"requestStart": 463.91217466260445,
"responseEnd": 463.91217466260445,
"responseStart": 463.91217466260445,
"startTime": 462.95008929525244,
"workerStart": 0
}
Вышеупомянутое 7 июля 2018, вcn.bing.comискать вниз
test
час,performance.getEntriesByType("resource")
Вернулся второй результат.
Нас беспокоит трудоемкость загрузки ресурсов, которую можно получить в следующем виде:
const [{ startTime, responseEnd }] = performance.getEntriesByType('resource')
const loadTime = responseEnd - startTime
такой жеNavigation Timing
похожий наstartTime
,fetchStart
,connectStart
иrequestStart
разница,Resource Timing Level 2Даны подробные инструкции:
Не все время загрузки ресурсов требует внимания, основное внимание уделяется частям, которые загружаются слишком медленно.
Для простоты определите 10 секунд в качестве лимита тайм-аута, тогда метод получения ресурса тайм-аута будет следующим:
const SEC = 1000
const TIMEOUT = 10 * SEC
const setTime = (limit = TIMEOUT) => time => time >= limit
const isTimeout = setTime()
const getLoadTime = ({ startTime, responseEnd }) => responseEnd - startTime
const getName = ({ name }) => name
const resourceTimes = performance.getEntriesByType('resource')
const getTimeoutRes = resourceTimes
.filter(item => isTimeout(getLoadTime(item)))
.map(getName)
Таким образом, мы получаем список всех ресурсов, время ожидания которых истекло.
Просто инкапсулируйте:
const SEC = 1000
const TIMEOUT = 10 * SEC
const setTime = (limit = TIMEOUT) => time => time >= limit
const getLoadTime = ({ requestStart, responseEnd }) =>
responseEnd - requestStart
const getName = ({ name }) => name
pMonitor.getTimeoutRes = (limit = TIMEOUT) => {
const isTimeout = setTime(limit)
const resourceTimes = performance.getEntriesByType('resource')
return resourceTimes.filter(item => isTimeout(getLoadTime(item))).map(getName)
}
данные отчета
После получения данных необходимо сообщить об этом на сервер:
// 生成表单数据
const convert2FormData = (data = {}) =>
Object.entries(data).reduce((last, [key, value]) => {
if (Array.isArray(value)) {
return value.reduce((lastResult, item) => {
lastResult.append(`${key}[]`, item)
return lastResult
}, last)
}
last.append(key, value)
return last
}, new FormData())
// 拼接 GET 时的url
const makeItStr = (data = {}) =>
Object.entries(data)
.map(([k, v]) => `${k}=${v}`)
.join('&')
// 上报数据
pMonitor.log = (url, data = {}, type = 'POST') => {
const method = type.toLowerCase()
const urlToUse = method === 'get' ? `${url}?${makeItStr(data)}` : url
const body = method === 'get' ? {} : { body: convert2FormData(data) }
const option = {
method,
...body
}
fetch(urlToUse, option).catch(e => console.log(e))
}
вернуться и инициализировать
Детали URL-адреса загрузки данных, времени ожидания и т. д. варьируются от проекта к проекту, поэтому необходимо указать метод инициализации:
// 缓存配置
let config = {}
/**
* @param {object} option
* @param {string} option.url 页面加载数据的上报地址
* @param {string} option.timeoutUrl 页面资源超时的上报地址
* @param {string=} [option.method='POST'] 请求方式
* @param {number=} [option.timeout=10000]
*/
pMonitor.init = option => {
const { url, timeoutUrl, method = 'POST', timeout = 10000 } = option
config = {
url,
timeoutUrl,
method,
timeout
}
// 绑定事件 用于触发上报数据
pMonitor.bindEvent()
}
когда запускать
Мониторинг производительности является лишь вспомогательной функцией и не должен блокировать загрузку страницы, поэтому сбор данных и отчетность мы выполняем только после загрузки страницы (на самом деле необходимую информацию нельзя получить до загрузки страницы):
// 封装一个上报两项核心数据的方法
pMonitor.logPackage = () => {
const { url, timeoutUrl, method } = config
const domComplete = pMonitor.getLoadTime()
const timeoutRes = pMonitor.getTimeoutRes(config.timeout)
// 上报页面加载时间
pMonitor.log(url, { domeComplete }, method)
if (timeoutRes.length) {
pMonitor.log(
timeoutUrl,
{
timeoutRes
},
method
)
}
}
// 事件绑定
pMonitor.bindEvent = () => {
const oldOnload = window.onload
window.onload = e => {
if (oldOnload && typeof oldOnload === 'function') {
oldOnload(e)
}
// 尽量不影响页面主线程
if (window.requestIdleCallback) {
window.requestIdleCallback(pMonitor.logPackage)
} else {
setTimeout(pMonitor.logPackage)
}
}
}
Резюме
На данный момент завершен полный инструмент мониторинга производительности внешнего интерфейса ~ весь код выглядит следующим образом:
const base = {
log() {},
logPackage() {},
getLoadTime() {},
getTimeoutRes() {},
bindEvent() {},
init() {}
}
const pm = (function() {
// 向前兼容
if (!window.performance) return base
const pMonitor = { ...base }
let config = {}
const SEC = 1000
const TIMEOUT = 10 * SEC
const setTime = (limit = TIMEOUT) => time => time >= limit
const getLoadTime = ({ startTime, responseEnd }) => responseEnd - startTime
const getName = ({ name }) => name
// 生成表单数据
const convert2FormData = (data = {}) =>
Object.entries(data).reduce((last, [key, value]) => {
if (Array.isArray(value)) {
return value.reduce((lastResult, item) => {
lastResult.append(`${key}[]`, item)
return lastResult
}, last)
}
last.append(key, value)
return last
}, new FormData())
// 拼接 GET 时的url
const makeItStr = (data = {}) =>
Object.entries(data)
.map(([k, v]) => `${k}=${v}`)
.join('&')
pMonitor.getLoadTime = () => {
const [{ domComplete }] = performance.getEntriesByType('navigation')
return domComplete
}
pMonitor.getTimeoutRes = (limit = TIMEOUT) => {
const isTimeout = setTime(limit)
const resourceTimes = performance.getEntriesByType('resource')
return resourceTimes
.filter(item => isTimeout(getLoadTime(item)))
.map(getName)
}
// 上报数据
pMonitor.log = (url, data = {}, type = 'POST') => {
const method = type.toLowerCase()
const urlToUse = method === 'get' ? `${url}?${makeItStr(data)}` : url
const body = method === 'get' ? {} : { body: convert2FormData(data) }
const init = {
method,
...body
}
fetch(urlToUse, init).catch(e => console.log(e))
}
// 封装一个上报两项核心数据的方法
pMonitor.logPackage = () => {
const { url, timeoutUrl, method } = config
const domComplete = pMonitor.getLoadTime()
const timeoutRes = pMonitor.getTimeoutRes(config.timeout)
// 上报页面加载时间
pMonitor.log(url, { domeComplete }, method)
if (timeoutRes.length) {
pMonitor.log(
timeoutUrl,
{
timeoutRes
},
method
)
}
}
// 事件绑定
pMonitor.bindEvent = () => {
const oldOnload = window.onload
window.onload = e => {
if (oldOnload && typeof oldOnload === 'function') {
oldOnload(e)
}
// 尽量不影响页面主线程
if (window.requestIdleCallback) {
window.requestIdleCallback(pMonitor.logPackage)
} else {
setTimeout(pMonitor.logPackage)
}
}
}
/**
* @param {object} option
* @param {string} option.url 页面加载数据的上报地址
* @param {string} option.timeoutUrl 页面资源超时的上报地址
* @param {string=} [option.method='POST'] 请求方式
* @param {number=} [option.timeout=10000]
*/
pMonitor.init = option => {
const { url, timeoutUrl, method = 'POST', timeout = 10000 } = option
config = {
url,
timeoutUrl,
method,
timeout
}
// 绑定事件 用于触发上报数据
pMonitor.bindEvent()
}
return pMonitor
})()
export default pm
как? Разве это не сложно? Это даже немного просто
Я снова посмотрел на время, 5 минут или что-то в этом роде, плевать на эти детали или
Дополнительные инструкции
перечислить
Если вы хотите преследовать (взорвать) и искать (мао) крайность (искать) и вызывать (дефект), инструмент мониторинга не должен занимать время парсинга JavaScript основного потока при загрузке страницы. Поэтому лучше запускать на страницеonload
После события используйте метод асинхронной загрузки:
// 在项目的入口文件的底部
const log = async () => {
const pMonitor = await import('/path/to/pMonitor.js')
pMonitor.init({ url: 'xxx', timeoutUrl: 'xxxx' })
pMonitor.logPackage()
// 可以进一步将 bindEvent 方法从源码中删除
}
const oldOnload = window.onload
window.onload = e => {
if (oldOnload && typeof oldOnload === 'string') {
oldOnload(e)
}
// 尽量不影响页面主线程
if (window.requestIdleCallback) {
window.requestIdleCallback(log)
} else {
setTimeout(log)
}
}
Междоменные и другие проблемы с запросами
Когда инструмент сообщает данные, он не учитывает междоменные проблемы и неGET
иPOST
одновременное существование.
Какой велосипед за 5 минут!
При необходимости вы можете покрыть его самостоятельноpMonitor.logPackage
метод, который создается динамически<form/>
и<iframe/>
, или используйте более распространенный метод расстановки точек~
Как насчет вызова полиции? Просто сообщить или нет? !
Это по-прежнему требует сотрудничества с сервером [серьезное лицо.jpg].
Либо каждому проекту соответствует отдельный URL-адрес для отчетов, либо это может быть унифицированный набор URL-адресов, и проекту присваивается уникальный идентификатор в качестве отличия.
Когда количество тайм-аутов превысит согласованный порог в течение указанного времени, разработчик будет уведомлен об этом по электронной почте/SMS.
мелкозернистый
Теперь для тайм-аута создается только простая статистика, но никаких конкретных причин тайм-аута (DNS? TCP? запрос? ответ?) не сообщается.
Следующий шаг
В этой статье представлен мониторинг производительности загрузки страницы, кроме того, парсинг+выполнение кода JavaScript также является важным фактором, ограничивающим скорость рендеринга первого экрана страницы (особенно для одностраничных приложений). В следующей главе редактор приведет вас к дальнейшему изучениюPerformance Timeline Level 2
, чтобы улучшить мониторинг производительности среды выполнения JavaScript, так что следите за обновлениями~
использованная литература
- Я 3 из .GitHub.IO/navigation-…
- Woohoo. Я 3.org/TR/resource…
- Woohoo. Я 3.org/TR/perform A…
- Developers.Google.com/Web/Женщины большие…
О Еженедельнике Циву
«Qiwu Weekly» — это сообщество передовых технологий, управляемое профессиональной командой «Qiwu Troupe» компании 360. Подписавшись на официальный аккаунт, вы можете отправить нам статью, отправив ссылку прямо на фон.