19 мар. 2020 г.

Профилирование скорости server-side рендеринга (SSR) компонентов на React + TypeScript

Подготовка

Для запуска ts tsx файлов в Node.js устанавливаем пакет ts-node:

npm i ts-node

Чтобы при запуске игнорировать импорт стилей в файле компонента (который может выглядеть примерно так: import './MyComponent.scss';), устанавливаем пакет ignore-styles:

npm i ignore-styles

Создание файла с бенчмарком

Создаём tsx файл, в котором импортируем наш компонент MyComponent, задаём props и вызываем renderToString():

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};
console.log(renderToString(<MyComponent {...props} />));

Запускаем его, чтобы убедиться, что всё работает без ошибок, компонент рендерится и в консоль выводится правильная разметка компонента:

NODE_ENV=production node -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если не хватит памяти, надо добавить параметр --max_old_space_size=4096

NODE_ENV=production node --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Если памяти всё равно не хватит - надо поставить вместо 4096 число побольше. Запоминаем подобранные параметры.

Убедившись, что всё работает, в файле бенчмарка убираем вывод в консоль и дописываем вызов renderToString() в цикле:

import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { MyComponent } from './MyComponent';

const props = {/* ... */};

for (let i = 0; i < 1000; i++) {
    renderToString(<MyComponent {...props} />);
}

Запуск бенчмарка

Запускаем бенчмарк (в подобранную ранее командную строку добавляется параметр --prof):

NODE_ENV=production node --prof -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

или

NODE_ENV=production node --prof --max_old_space_size=4096 -r ts-node/register -r ignore-styles MyComponent.perf-test.tsx

Первый запуск после изменения файла будет долгим и даст совсем неправильные результаты, т.к. под капотом компилируется TypeScript, и процесс компиляции тоже попадёт в собранный профиль, что нам не нужно. Поэтому после каждого редактирования запускаем бенчмарк по два раза, второй раз он выполнится быстрее.

После каждого запуска должны создаваться файлы isolate*.log.

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

После этого удаляем все накопившиеся файлы isolate*.log, запускаем бенчмарк ещё раз - мы должны получить ровно один файл isolate*.log.

Обработка и анализ собранного профиля

Полученный файл isolate*.log надо обработать, чтобы получить на выходе читабельный профиль выполнения:

node --prof-process isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

Вместо isolate-0x104000800-v8 надо подставить своё имя файла. Также для обработки лога можно использовать отдельный пакет https://www.npmjs.com/package/tick-processor, часто он лучше обрабатывает лог и не теряет данные, в обличие от встроенного в саму ноду процессора логов:

tick-processor isolate-0x104000800-v8.log >isolate-0x104000800-v8.txt

В текстовом файле мы видим, какие именно методы v8 и нативного C++ кода вызывались и сколько времени заняли. Подробное описание содержимого и методов его анализа - тема, достойная отдельной большой статьи.