Интерфейсный инструмент для мониторинга производительности за 5 минут

внешний интерфейс JavaScript Google DNS монитор

Зачем мониторить

Используя (верхние) домохозяйства (Император), чтобы сказать, почему эта страница такая медленная, кого-то это все еще волнует? !

Проще говоря, есть три причины:

  • Сосредоточение внимания на производительности — это природа + обязанность инженеров;
  • Производительность страницы имеет решающее значение для взаимодействия с пользователем. Неубедительно полагаться только на тестовые данные оборудования разработки инженера для улучшения производительности страницы каждый раз при рефакторинге, а для проверки требуется большой объем реальных данных;
  • При зависании ресурса или нештатной загрузке нельзя всегда полагаться на жалобы пользователей, чтобы узнать об этом позже, и нужно проявлять инициативу, чтобы сообщить в полицию.

Реконструкция производительности, при условии гигабитной скорости сети и оборудования стоимостью 10 000 юаней, улучшение времени загрузки страницы может составлять всего 0,1%, но такие данные (земля) данные (хао) не являются репрезентативными. Сетевые среды и аппаратные устройства сильно различаются.Для недорогих устройств субъективный опыт повышения производительности более очевиден, а соответствующие изменения данных более репрезентативны.

Многие проекты загружают ресурсы в CDN. Когда возникает проблема с какими-то узлами CDN, то вообще невозможно точно сообщить «Такой-то, ваш ресурс xx не работает», поэтому нам нужно активно следить за этим.

По данным Google, когда страница загружается более 10 секунд, пользователи впадают в отчаяние, обычно покидают текущую страницу и, скорее всего, никогда не возвращаются.

что контролировать

Что касается показателей производительности внешнего интерфейса, W3C определяет мощныйPerformanceAPI, который, в свою очередь, включаетHigh Resolution Time,Frame Timing,Navigation Timing,Performance Timeline,Resource Timing,User Timingи многие другие специальные стандарты.

В этой статье в основном рассматриваютсяNavigation Timingа такжеResource Timing. По состоянию на середину 2018 года все основные браузеры завершили базовую реализацию.

Navigation Timing Support

Resource Timing Support

PerformanceAPI имеет множество функций, одна из которых — запись производительности (сведения о времени) самой страницы и каждого ресурса на странице. И все, что нам нужно сделать, это запросить и использовать.

Читатели могут печатать прямо в консоли браузера.performance, см. соответствующие API.

Далее мы будем использоватьwindow.performanceобъект (Performanceконкретная реализация API) для реализации простого внешнего инструмента мониторинга производительности.

Интерфейсный инструмент для мониторинга производительности за 5 минут

первая строка кода

Назовите инструмент какpMonitor, означающий, чтоperformance monitor.

const pMonitor = {}

Какие показатели отслеживать

Поскольку это серия «5 минут, чтобы достичь ххх», то должен быть компромисс. Поэтому в данной статье выбраны только два наиболее важных показателя для мониторинга:

  • время загрузки страницы
  • время запроса ресурса

Глядя на время, прошло 4 минуты Редактор сказал, что настроение у него стабильное, колебаний нет.

Everything is fine

загрузка страницы

Показатели производительности для загрузки страницы можно найти по адресу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Даны подробные инструкции:

Navigation Timing attributes

Нетрудно заметить, что деталей полно. Таким образом, содержимое, которое можно рассчитать, очень разнообразно, например, время запроса 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Даны подробные инструкции:

Resource Timing attributes

Не все время загрузки ресурсов требует внимания, основное внимание уделяется частям, которые загружаются слишком медленно.

Для простоты определите 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 минут или что-то в этом роде, плевать на эти детали или

It doesn't matter

Дополнительные инструкции

перечислить

Если вы хотите преследовать (взорвать) и искать (мао) крайность (искать) и вызывать (дефект), инструмент мониторинга не должен занимать время парсинга 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, так что следите за обновлениями~

использованная литература

О Еженедельнике Циву

«Qiwu Weekly» — это сообщество передовых технологий, управляемое профессиональной командой «Qiwu Troupe» компании 360. Подписавшись на официальный аккаунт, вы можете отправить нам статью, отправив ссылку прямо на фон.