Введение в API производительности

Опубликовано: 2022-08-10

API производительности измеряет скорость отклика вашего работающего веб-приложения на реальных пользовательских устройствах и сетевых подключениях. Это может помочь выявить узкие места в коде на стороне клиента и на стороне сервера с помощью:

  • пользовательское время: пользовательское измерение производительности клиентской функции JavaScript.
  • время рисования: метрики рендеринга в браузере
  • время ресурсов: производительность загрузки ресурсов и вызовов Ajax
  • время навигации: показатели загрузки страницы, включая перенаправления, поиск DNS, готовность DOM и многое другое.

API решает несколько проблем, связанных с типичной оценкой производительности:

  1. Разработчики часто тестируют приложения на высокопроизводительных ПК, подключенных к быстрой сети. DevTools может эмулировать более медленные устройства, но не всегда выявляет реальные проблемы, когда большинство клиентов используют мобильные телефоны двухлетней давности, подключенные к Wi-Fi в аэропорту.
  2. Сторонние опции, такие как Google Analytics, часто блокируются, что приводит к искаженным результатам и предположениям. Вы также можете столкнуться с последствиями конфиденциальности в некоторых странах.
  3. API производительности может точнее измерять различные показатели лучше, чем такие методы, как Date() .

Хотите узнать больше об использовании Performance API? Начните здесь... Нажмите, чтобы твитнуть
В следующих разделах описаны способы использования Performance API. Рекомендуется иметь некоторое знание JavaScript и метрик загрузки страниц.

Доступность API производительности

Большинство современных браузеров поддерживают Performance API, включая IE10 и IE11 (даже IE9 имеет ограниченную поддержку). Вы можете обнаружить присутствие API, используя:

 if ('performance' in window) { // use Performance API }

Полное заполнение API невозможно, поэтому будьте осторожны с отсутствующими браузерами. Если 90 % ваших пользователей с удовольствием используют Internet Explorer 8, вы будете измерять только 10 % клиентов с более функциональными приложениями.

API можно использовать в веб-воркерах, которые позволяют выполнять сложные вычисления в фоновом потоке без остановки операций браузера.

Большинство методов API можно использовать в Node.js на стороне сервера со стандартным модулем perf_hooks:

 // Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );

Deno предоставляет стандартный API производительности:

 // Deno performance console.log( performance.now() );

Вам нужно будет запустить сценарии с разрешением --allow-hrtime , чтобы включить измерение времени с высоким разрешением:

 deno run --allow-hrtime index.js

Производительность на стороне сервера обычно легче оценить и управлять, поскольку она зависит от нагрузки, ЦП, ОЗУ, жестких дисков и ограничений облачных служб. Модернизация оборудования или варианты управления процессами, такие как PM2, кластеризация и Kubernetes, могут быть более эффективными, чем рефакторинг кода.

По этой причине в следующих разделах основное внимание уделяется производительности на стороне клиента.

Пользовательское измерение производительности

Performance API можно использовать для определения скорости выполнения функций вашего приложения. Возможно, вы использовали или сталкивались с функциями синхронизации, использующими Date() :

 const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

Performance API предлагает два основных преимущества:

  1. Лучшая точность: Date() измеряет с точностью до миллисекунды, но Performance API может измерять доли миллисекунды (в зависимости от браузера).
  2. Лучшая надежность: пользователь или ОС могут изменить системное время, поэтому метрики на основе Date() не всегда будут точными. Это означает, что ваши функции могут работать особенно медленно, когда часы идут вперед!

Эквивалентом Date() является performance.now() , которая возвращает отметку времени с высоким разрешением, которая устанавливается на ноль, когда начинается процесс, ответственный за создание документа (страница загружается):

 const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

Нестандартное свойство performance.timeOrigin также может возвращать отметку времени с 1 января 1970 года, хотя это недоступно в IE и Deno.

performance.now() становится непрактичным при выполнении нескольких измерений. Performance API предоставляет буфер, в котором вы можете записать событие для последующего анализа, передав имя метки в performance.mark() :

 performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');

Массив всех объектов меток в буфере производительности можно извлечь с помощью:

 const mark = performance.getEntriesByType('mark');

Пример результата:

 [ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]

Метод performance.measure() вычисляет время между двумя отметками и также сохраняет его в буфере производительности. Вы передаете имя новой меры, имя начальной метки (или null для измерения с загрузки страницы) и имя конечной метки (или null для измерения до текущего времени):

 performance.measure('init', 'start:init', 'end:init');

К буферу добавляется объект PerformanceMeasure с вычисленной продолжительностью времени. Чтобы получить это значение, вы можете либо запросить массив всех мер:

 const measure = performance.getEntriesByType('measure');

или запросите меру по ее имени:

 performance.getEntriesByName('init');

Пример результата:

 [ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]

Использование буфера производительности

Помимо меток и показателей, буфер производительности используется для автоматической записи времени навигации, времени ресурсов и времени рисования (которое мы обсудим позже). Вы можете получить массив всех записей в буфере:

 performance.getEntries();

По умолчанию большинство браузеров предоставляют буфер, в котором хранится до 150 метрик ресурсов. Этого должно быть достаточно для большинства оценок, но при необходимости вы можете увеличить или уменьшить лимит буфера:

 // record 500 metrics performance.setResourceTimingBufferSize(500);

Метки можно очистить по имени или указать пустое значение, чтобы очистить все метки:

 performance.clearMarks('start:init');

Точно так же меры можно очистить по имени или пустому значению, чтобы очистить все:

 performance.clearMeasures();

Мониторинг обновлений буфера производительности

PerformanceObserver может отслеживать изменения в буфере производительности и запускать функцию при возникновении определенных событий. Синтаксис будет вам знаком, если вы использовали MutationObserver для ответа на обновления DOM или IntersectionObserver для обнаружения прокрутки элементов в окне просмотра.

Вы должны определить функцию наблюдателя с двумя параметрами:

  1. массив записей наблюдателя, которые были обнаружены, и
  2. объект наблюдателя. При необходимости можно вызвать его метод disconnect() для остановки наблюдателя.
 function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }

Функция передается новому объекту PerformanceObserver. Его observe() передается массив типов записей буфера производительности для наблюдения:

 let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });

В этом примере при добавлении новой метки или показателя запускается функция performanceCallback() . Хотя здесь он регистрирует только сообщения, его можно использовать для запуска загрузки данных или выполнения дальнейших расчетов.

Измерение характеристик краски

Paint Timing API доступен только в клиентском JavaScript и автоматически записывает две метрики, важные для Core Web Vitals:

  1. first-paint: Браузер начал рисовать страницу.
  2. first-contentful-paint: Браузер нарисовал первый значимый элемент содержимого DOM, такой как заголовок или изображение.

Их можно извлечь из буфера производительности в массив:

 const paintTimes = performance.getEntriesByType('paint');

Будьте осторожны при запуске этого до полной загрузки страницы; значения не будут готовы. Либо дождитесь события window.load , либо используйте PerformanceObserver для мониторинга типов записей paint .

Боретесь с простоями и проблемами WordPress? Kinsta — это решение для хостинга, предназначенное для экономии вашего времени! Ознакомьтесь с нашими функциями

Пример результата:

 [ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]

Медленная первая отрисовка часто вызвана блокировкой рендеринга CSS или JavaScript. Промежуток до first-contentful-paint может быть большим, если браузеру нужно загрузить большое изображение или отобразить сложные элементы.

Измерение производительности ресурсов

Сетевые тайминги для таких ресурсов, как изображения, таблицы стилей и файлы JavaScript, автоматически записываются в буфер производительности. Хотя вы мало что можете сделать для решения проблем со скоростью сети (кроме уменьшения размера файлов), это может помочь выявить проблемы с большими ресурсами, медленными ответами Ajax или плохо работающими сторонними скриптами.

Массив метрик PerformanceResourceTiming можно извлечь из буфера, используя:

 const resources = performance.getEntriesByType('resource');

Кроме того, вы можете получить метрики для актива, передав его полный URL-адрес:

 const resource = performance.getEntriesByName('https://test.com/script.js');

Пример результата:

 [ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]

Можно исследовать следующие свойства:

  • имя : URL-адрес ресурса
  • entryType : «ресурс»
  • initiatorType : как был инициирован ресурс, например «скрипт» или «ссылка».
  • serverTiming : массив объектов PerformanceServerTiming , передаваемых сервером в заголовке HTTP Server-Timing (ваше серверное приложение может отправлять метрики клиенту для дальнейшего анализа).
  • startTime : Отметка времени начала выборки
  • nextHopProtocol : Используемый сетевой протокол
  • workerStart : метка времени перед запуском работника службы Progressive Web App (0, если запрос не перехвачен работником службы).
  • redirectStart : Отметка времени начала перенаправления
  • redirectEnd : Отметка времени после последнего байта последнего ответа перенаправления
  • fetchStart : Отметка времени перед выборкой ресурса
  • domainLookupStart : Отметка времени перед поиском DNS
  • domainLookupEnd : Отметка времени после поиска DNS
  • connectStart : Отметка времени перед установлением соединения с сервером
  • connectEnd : Отметка времени после установления соединения с сервером
  • secureConnectionStart : Отметка времени перед рукопожатием SSL
  • requestStart : Отметка времени перед тем, как браузер запросит ресурс
  • responseStart : Отметка времени, когда браузер получает первый байт данных
  • responseEnd : Отметка времени после получения последнего байта или закрытия соединения
  • продолжительность : разница между startTime и responseEnd
  • transferSize : размер ресурса в байтах, включая заголовок и сжатое тело.
  • encodedBodySize : тело ресурса в байтах перед распаковкой.
  • decodedBodySize : тело ресурса в байтах после распаковки

Этот пример сценария извлекает все запросы Ajax, инициированные Fetch API, и возвращает общий размер и продолжительность передачи:

 const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );

Измерение производительности навигации

Сетевые тайминги для выгрузки предыдущей страницы и загрузки текущей страницы автоматически записываются в буфер производительности как один объект PerformanceNavigationTiming .

Извлеките его в массив, используя:

 const pageTime = performance.getEntriesByType('navigation');

…или передав URL-адрес страницы в .getEntriesByName() :

 const pageTiming = performance.getEntriesByName(window.location);

Показатели идентичны показателям для ресурсов, но также включают значения для конкретных страниц:

  • entryType : например, «навигация»
  • type : «навигация», «перезагрузка», «назад_вперед» или «пререндеринг».
  • redirectCount : количество перенаправлений
  • unloadEventStart : Отметка времени перед событием выгрузки предыдущего документа
  • unloadEventEnd : Отметка времени после события выгрузки предыдущего документа
  • domInteractive : Отметка времени, когда браузер проанализировал HTML и построил DOM.
  • domContentLoadedEventStart : Отметка времени перед срабатыванием события DOMContentLoaded документа.
  • domContentLoadedEventEnd : Отметка времени после завершения события DOMContentLoaded документа
  • domComplete : Отметка времени после завершения построения DOM и событий DOMContentLoaded.
  • loadEventStart : Отметка времени перед запуском события загрузки страницы.
  • loadEventEnd : Отметка времени после события загрузки страницы и доступности всех ресурсов.

Типичные проблемы включают в себя:

  • Большая задержка между unloadEventEnd и domInteractive . Это может указывать на медленный отклик сервера.
  • Большая задержка между domContentLoadedEventStart и domComplete . Это может указывать на то, что сценарии запуска страницы работают слишком медленно.
  • Большая задержка между domComplete и loadEventEnd . Это может означать, что на странице слишком много ресурсов или некоторые из них загружаются слишком долго.

Запись и анализ производительности

Performance API позволяет сопоставлять данные об использовании в реальном мире и загружать их на сервер для дальнейшего анализа. Вы можете использовать стороннюю службу, такую ​​как Google Analytics, для хранения данных, но есть риск, что сторонний скрипт может быть заблокирован или вызвать новые проблемы с производительностью. Ваше собственное решение можно настроить в соответствии с вашими требованиями, чтобы мониторинг не влиял на другие функции.

Остерегайтесь ситуаций, в которых невозможно определить статистику — возможно, из-за того, что пользователи используют старые браузеры, блокируют JavaScript или используют корпоративный прокси. Понимание того, каких данных не хватает, может быть более плодотворным, чем делать предположения на основе неполной информации.

В идеале ваши сценарии анализа не будут отрицательно влиять на производительность, выполняя сложные вычисления или загружая большие объемы данных. Рассмотрите возможность использования веб-воркеров и сведите к минимуму использование синхронных вызовов localStorage. Позже всегда можно выполнить пакетную обработку необработанных данных.

Наконец, будьте осторожны с выбросами, такими как очень быстрые или очень медленные устройства и соединения, которые отрицательно влияют на статистику. Например, если девять пользователей загружают страницу за две секунды, а десятый загружает ее за 60 секунд, средняя задержка составит почти 8 секунд. Более реалистичной метрикой является медиана (2 секунды) или 90-й процентиль (9 из каждых 10 пользователей испытывают время загрузки 2 секунды или меньше).

Резюме

Веб-производительность остается критическим фактором для разработчиков. Пользователи ожидают, что сайты и приложения будут реагировать на большинство устройств. На поисковую оптимизацию также может повлиять понижение рейтинга более медленных сайтов в Google.
Все, что вам нужно знать, чтобы начать работу с Performance API, находится прямо здесь. Нажмите, чтобы твитнуть
Существует множество инструментов для мониторинга производительности, но большинство из них оценивают скорость выполнения на стороне сервера или используют ограниченное количество подходящих клиентов для оценки рендеринга в браузере. Performance API предоставляет способ сопоставления реальных пользовательских показателей, которые невозможно рассчитать каким-либо другим способом.