В сентябре этого года у меня была возможность присоединиться к Forest Admin, компании, которая выполняет всю тяжёлую работу по созданию панели администратора любого веб-приложения и предоставляет платформу на основе API, чтобы реализовать все ваши конкретные бизнес-процессы. Моей первой задачей было реализовать мониторинг времени загрузки приложения, а также мониторинг времени запросов, сделанных нашими клиентами в их административном бэкенде, который я разработал.

Цель такой функции – иметь возможность ориентироваться на проекты, работая с которыми пользователи сталкиваются с длительной загрузкой, чтобы оптимизировать конфигурацию своего интерфейса. Это делает навигацию и взаимодействие с приложением плавнее и, таким образом, улучшает взаимодействие с пользователем. Для этого первым делом нужно выяснить, как мы собираемся реализовать такую функцию.




В зависимости от используемой среды разработки надстройки могут уже быть установлены. Если вы разработчик React, например, react-addons-per. должен позволить вам отслеживать то, что вы хотите. В Forest Admin мы используем Ember.js, и нет никакой надстройки, подобной React. Поэтому воспользуемся Performance API, который, согласно документации, должен позволить нам сделать именно то, что мы хотим сделать:

Стандарт High Resolution Time определяет интерфейс производительности, который поддерживает измерения задержки на стороне клиента в приложениях. Интерфейсы Performance считаются интерфейсами с высоким разрешением, поскольку имеют точность до тысячных долей миллисекунды (в зависимости от аппаратных или программных ограничений).

II. API Performance


Performance API использует множество разнообразных методов. Нам понадобится всего 5:

  • mark;
  • measure;
  • getEntriesByType;
  • clearMarks/clearMeasures.

1. mark


Метод mark позволяет разместить маркер времени. Чтобы сослаться на маркер, требуется только один аргумент (строка), этот метод ничего не возвращает. Позже он позволит рассчитать время.

performance.mark('start');

2. measure


Метод measure позволяет измерить разницу во времени между двумя маркерами. Он принимает 3 аргумента: имя созданного измерителя (строка), маркер начала (строка), маркер конца (строка). Этот метод возвращает объект со свойством duration, которое вычисляет разницу между двумя маркерами.

async function timeDuration() {
    performance.mark('start');
    await new Promise(resolve => setTimeout(resolve, 100))
    performance.mark('end');
  
    return performance.measure('time', 'start', 'end').duration;
  }
  
  timeDuration().then((result) => console.log(result));
  // output: 100

3. getEntriesByType


Метод getEntriesByType позволяет получить доступ ко всем созданным объектам определённого типа. Он возвращает массив объектов и принимает в качестве аргумента тип входных значений (строку) из следующих: frame, navigation, resource, mark, measure, paint, longtask. Не волнуйтесь, нам понадобятся только mark и measure.
performance.getEntriesByType('measure');
    // output: return an Array of Object containing all the measure


4. clearMarks/clearMeasures


Методы clearMarks/clearMeasures используются для удаления ранее добавленных маркеров и измерителей из кеша браузера. Эти методы ничего не возвращают и не принимают никаких аргументов.

III. Давайте углубимся в код





Теперь, когда мы знаем методы реализации функциональности, нам нужно интегрировать её в код. У нас есть два варианта: создать маркеры и проводить измерения прямо в нужных местах кода или создать сервис (time-tracker.js) и вставить его в код. Для наглядности выберем второй вариант.

performance.mark('start');
    performance.mark('end');
    performance.measure('time_duration', 'start', 'end');
    
    console.log(performance.getEntriesByType('mark').length);
    // output: 2
    console.log(performance.getEntriesByType('measure').length);
    // output: 1
    
    performance.clearMarks();
    performance.clearMeasures();
    console.log(performance.getEntriesByType('mark').length);
    // output: 0
    console.log(performance.getEntriesByType('measure').length);
    // output: 0

Всё просто, правда? Теперь нам нужно вызвать сервисные функции в тех местах кода, которые позволят нам отслеживать время загрузки запроса и интерфейса. Для интерфейса маркер timinginterfacestart должен вызываться в начале загрузки страницы. Первым вызывается метод model маршрута, который разместим здесь. Также возможно использовать методы в жизненных циклах компонента или любой другой функции, вызванной изначально. Однако следует соблюдать осторожность при размещении маркера. Если маркер не поставить в нужное место, последующее измерение будет неточным.

import { inject as service } from '@ember/service';

    export default class RouteExample extends Route {
      @service timeTracker;
      
      model() {
        this.timeTracker.startInterface();
        // do something
      }
    }

Для маркера timinginterfacestop он должен вызываться, когда закончен рендеринг приложения. Что касается рендеринга нескольких компонентов, логичным способом является использование методов в жизненных циклах этих компонентов. Метод компонента didRender из ember кажется хорошим кандидатом. Затем идёт измерение общего времени загрузки интерфейса. Его можно разместить после маркера timinginterfacestop или в любом другом желаемом месте.

import { inject as service } from '@ember/service';

    export default class RenderingTracker extends Component {
      @service timeTracker;
    
      didRender() {
        this.timeTracker.stopInterface();
      }
      stopInterfaceTracking() {
        return this.timeTracker.measureTimingInterface()
      }
    }

Для расчёта времени запроса на сервере пользователя достаточно окружить функцию, отвечающую за запрос сервера пользователя, маркерами timingrequeststart и Timingrequeststop. Затем можно измерить время запроса, вызвав метод measureTimingRequest() из нашего сервиса.

import { inject as service } from '@ember/service';

    export default class RouteExample extends Route {
      @service timeTracker;
      
      model() {
        this.timeTracker.startInterface();
        // do something
      }
      
      async function fetchData(params) {
        //do something
      
        this.timeTracker.startRequest();
        const records = await fetchRecords(params);
        this.timingTracker.stopRequest();
        
        const timingRequest = this.timingTracker.measureTimingRequest()
        // do something else
    
      }
    }

Заключение


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


image