Как измерить длительность запросов (XHR) в AngularJS?

Я пытаюсь реализовать перехватчик для AngularJS (1.5.x), который может измерять каждый вызов API и отправлять продолжительность каждого из них в Google Analytics.

Я начал с фиктивной реализации этого, используя только new Date().getTime():

(function (angular) {
  'use strict';

  angular.module('MODULE')
    .factory('HttpLoadingInterceptor', HttpLoadingInterceptor)
    .config(HttpLoadingInterceptorConfig);

  HttpLoadingInterceptor.$inject = ['$injector', '$q', 'AnalyticsService', '_'];

  HttpLoadingInterceptorConfig.$inject = ['$httpProvider'];

  function HttpLoadingInterceptor ($injector, $q, AnalyticsService, _) {
    var REQUEST_GA_EVENT_NAME = 'REQUEST';

    return {
      request: request,
      requestError: requestError,
      response: response,
      responseError: responseError
    };

    function request (config) {
      config.requestTimestamp = now();
      return config || $q.when(config);
    }

    function requestError (rejection) {
      rejection.config.responseTimestamp = now();
      trackRequest(rejection);
      return $q.reject(rejection);
    }

    function response (response) {
      response.config.responseTimestamp = now();
      trackRequest(response);
      return response || $q.when(response);
    }

    function responseError (rejection) {
      rejection.config.responseTimestamp = now();
      trackRequest(rejection);
      return $q.reject(rejection);
    }

    function trackRequest (response) {
      if (!_.startsWith(response.config.url, 'api')) {
        return;
      }
      AnalyticsService.trackEvent(
        REQUEST_GA_EVENT_NAME,
        response.config.url,
        response.status,
        response.config.responseTimestamp - response.config.requestTimestamp
      );
    }

    function now () {
      return new Date().getTime();
    }
  }

  function HttpLoadingInterceptorConfig ($httpProvider) {
    $httpProvider.interceptors.push('HttpLoadingInterceptor');
  }
})(window.angular);

На первый взгляд, это выглядит неплохо, но, сравнивая длительность запросов от тех, которые были собраны на вкладке "Сети" в Chrome, я заметил, что те, что собраны в моем коде, всегда выше (иногда намного!) От тех, которые собирает Chrome.

Еще одна идея, которая пришла мне в голову, это использование API пользовательской навигации, поэтому я изменил свой код на:

(function (angular, performance) {
  'use strict';

  angular.module('MODULE')
    .factory('HttpLoadingInterceptor', HttpLoadingInterceptor)
    .config(HttpLoadingInterceptorConfig);

  HttpLoadingInterceptor.$inject = ['$injector', '$q', 'AnalyticsService', '_'];

  HttpLoadingInterceptorConfig.$inject = ['$httpProvider'];

  function HttpLoadingInterceptor ($injector, $q, AnalyticsService, _) {
    var REQUEST_GA_EVENT_NAME = 'REQUEST';
    var measureReqCnt = 1;

    return {
      request: request,
      requestError: requestError,
      response: response,
      responseError: responseError
    };

    function request (config) {
      if (shouldMeasure(config.url)) {
        measureRequest(config);
      }
      return config || $q.when(config);
    }

    function requestError (rejection) {
      if (shouldMeasure(rejection.config.url)) {
        trackRequest(rejection);
      }
      return $q.reject(rejection);
    }

    function response (response) {
      if (shouldMeasure(response.config.url)) {
        trackRequest(response);
      }
      return response || $q.when(response);
    }

    function responseError (rejection) {
      if (shouldMeasure(rejection.config.url)) {
        trackRequest(rejection);
      }
      return $q.reject(rejection);
    }

    function shouldMeasure (url) {
      return performance && _.startsWith(url, 'api');
    }

    function measureRequest (config) {
      config.measureName = [config.url, measureReqCnt++].join('_');
      config.markStartName = [config.measureName, 'start'].join('_');
      config.markEndName = [config.measureName, 'end'].join('_');
      performance.mark(config.markStartName);
    }

    function trackRequest (response) {
      performance.mark(response.config.markEndName);
      performance.measure(response.config.measureName, response.config.markStartName, response.config.markEndName);
      var entries = performance.getEntriesByName(response.config.measureName, 'measure');
      if (entries && entries.length) {
        AnalyticsService.trackEvent(
          REQUEST_GA_EVENT_NAME,
          response.config.url,
          response.status,
          entries[0].duration
        );
      }
    }
  }

  function HttpLoadingInterceptorConfig ($httpProvider) {
    $httpProvider.interceptors.push('HttpLoadingInterceptor');
  }
})(window.angular, window.performance);

... но я снова получил результаты, отличные от тех, которые были получены Chrome, и даже отличные от тех, которые были получены с помощью new Date().getTime()

Что я делаю неправильно? Как я должен сделать это правильно? Может быть, Resource Timing API? AngularJS конечно навязывает немного.

Я не могу использовать метод API навигации пользователя - window.performacne.getEntries() чтобы найти продолжительность моих запросов, потому что я не могу распознать конкретный запрос. Параметры каждого запроса там недоступны, и у меня много одинаковых вызовов API только с разными параметрами.

Должен ли я украсить собственный запрос XHR, который используется AngularJS?

1 ответ

А как насчет эффективности performance.now ()? ( https://developer.mozilla.org/en-US/docs/Web/API/Performance/now), который предназначен для высокоточного отслеживания производительности по сравнению только с Date()?

Смотрите: performance.now () против Date.now()

Кроме того, вы заботитесь об абсолютных числах? Для отслеживания производительности иногда лучше использовать относительное значение, так как сравнение яблок с апельсинами редко дает реальное назначение данных.

Я предполагаю, что есть только несоответствие между тем, какой таймер используется, и точками, когда время занято. Инструменты Chrome Dev, вероятно, гораздо ближе "к проводу", нежели код приложения. Если это помогает, у нас также есть инструмент для анализа и отладки API (www.moesif.com/features), но полное раскрытие того, что я являюсь генеральным директором.

Другие вопросы по тегам