사용자 시간 API

웹 앱 이해

Alex Danilo

고성능 웹 애플리케이션은 우수한 사용자 환경에 매우 중요합니다. 웹 애플리케이션이 점점 더 복잡해짐에 따라 매력적인 환경을 만드는 데는 성능 영향을 이해하는 것이 중요합니다. 지난 몇 년 동안 네트워크 성능, 로드 시간 등을 분석하는 데 도움이 되는 다양한 API가 브라우저에 등장했지만, 이러한 API는 애플리케이션 속도를 저하시키는 요소를 찾을 만큼 충분히 유연하지는 않습니다. 웹 애플리케이션을 계측하여 애플리케이션에서 시간을 소비하는 위치를 식별하는 데 사용할 수 있는 메커니즘을 제공하는 User Timing API를 입력합니다. 이 도움말에서는 API와 API 사용 방법의 예를 설명합니다.

측정할 수 없는 사항을 최적화할 수 없습니다.

느린 웹 애플리케이션의 속도를 높이는 첫 번째 단계는 시간이 어디에 소요되는지 파악하는 것입니다. JavaScript 코드 영역의 시간에 미치는 영향을 측정하는 것은 핫스팟을 식별하는 가장 좋은 방법이며, 성능 개선 방법을 찾는 첫 번째 단계입니다. 다행히 User Timing API를 사용하면 자바스크립트의 여러 부분에 API 호출을 삽입한 다음 최적화에 사용할 수 있는 자세한 타이밍 데이터를 추출할 수 있습니다.

고해상도 시간 및 now()

정확한 시간 측정의 기본은 정밀도입니다. 예전에는 밀리초 측정을 기반으로 하는 타이밍을 사용했습니다. 이는 괜찮지만 버벅거림이 없는 60FPS 사이트를 빌드하려면 각 프레임을 16밀리초 내에 그려야 합니다. 따라서 밀리초의 정확성만 있을 때는 좋은 분석에 필요한 정밀도가 부족합니다. 최신 브라우저에 내장된 새로운 타이밍 유형인 고해상도 시간을 입력합니다. 고해상도 시간을 사용하면 마이크로초 단위의 해상도로 정확하게 측정할 수 있는 부동 소수점 타임스탬프를 사용할 수 있습니다. 이는 이전보다 1,000배 향상된 정확도입니다.

웹 애플리케이션에서 현재 시간을 가져오려면 Performance 인터페이스의 확장 프로그램을 형성하는 now() 메서드를 호출합니다. 다음 코드는 이 작업을 수행하는 방법을 보여줍니다.

var myTime = window.performance.now();

웹 애플리케이션이 로드되는 방식과 관련된 다양한 횟수를 제공하는 PerformanceTiming이라는 또 다른 인터페이스가 있습니다. now() 메서드는 PerformanceTimingnavigationStart 시간이 발생한 시점부터 경과된 시간을 반환합니다.

DOMHighResTimeStamp 유형

이전에는 웹 애플리케이션의 시간을 측정할 때 DOMTimeStamp를 반환하는 Date.now()와 같은 항목을 사용했습니다. DOMTimeStamp는 정수 밀리초 값을 반환합니다. 고해상도 시간에 필요한 더 높은 정확도를 제공하기 위해 DOMHighResTimeStamp라는 새로운 유형이 도입되었습니다. 이 유형은 밀리초 단위의 시간도 반환하는 부동 소수점 값입니다. 하지만 부동 소수점이므로 값이 밀리초의 소수를 나타낼 수 있으므로 밀리초의 1,000분의 1의 정확도를 얻을 수 있습니다.

사용자 시간 인터페이스

고해상도 타임스탬프가 생겼으므로 사용자 시간 인터페이스를 사용하여 시간 정보를 가져와 보겠습니다.

User Timing 인터페이스는 애플리케이션의 여러 위치에서 메서드를 호출할 수 있는 함수를 제공합니다. 이 함수는 한셀과 그레텔 스타일의 탐색경로를 제공하여 시간이 소비되는 위치를 추적할 수 있도록 합니다.

mark() 사용

mark() 메서드는 타이밍 분석 툴킷의 기본 도구입니다. mark()는 타임스탬프를 저장합니다. mark()의 매우 유용한 점은 타임스탬프 이름을 지정할 수 있고 API가 이름과 타임스탬프를 단일 단위로 기억한다는 것입니다.

애플리케이션의 여러 위치에서 mark()를 호출하면 웹 애플리케이션에서 '마크'를 눌렀을 때 걸린 시간을 계산할 수 있습니다.

사양에서는 mark_fully_loaded, mark_fully_visible, mark_above_the_fold와 같이 흥미롭고 설명이 거의 필요 없는 마커의 제안된 이름을 여러 개 언급합니다.

예를 들어, 다음 코드를 사용하여 애플리케이션이 완전히 로드되는 시점을 표시할 수 있습니다.

window.performance.mark('mark_fully_loaded');

웹 애플리케이션 전체에 이름이 지정된 마커를 설정하면 많은 타이밍 데이터를 수집하고 여유롭게 분석하여 애플리케이션이 무엇을 언제 실행하는지 파악할 수 있습니다.

measure()를 사용하여 측정값 계산

타이밍 마크를 여러 개 설정했다면 마크 간의 경과 시간을 확인해야 합니다. measure() 메서드를 사용하면 됩니다.

measure() 메서드는 표시 간의 경과 시간을 계산하며, PerformanceTiming 인터페이스의 유명한 이벤트 이름과 마크 사이의 시간도 측정할 수 있습니다.

예를 들어 다음과 같은 코드를 사용하여 DOM이 완료된 시점부터 애플리케이션 상태가 완전히 로드될 때까지의 시간을 계산할 수 있습니다.

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

measure()를 호출하면 설정한 마크와 관계없이 결과가 저장되므로 나중에 가져올 수 있습니다. 애플리케이션이 실행될 때 시간을 저장하면 애플리케이션의 응답성이 유지되며, 애플리케이션이 일부 작업을 완료한 후 나중에 분석할 수 있도록 모든 데이터를 덤프할 수 있습니다.

clearMarks()로 마크 삭제

설정한 마크를 한꺼번에 삭제할 수 있는 기능이 유용할 때가 있습니다. 예를 들어 웹 애플리케이션에서 일괄 실행을 실행할 수 있으므로 실행할 때마다 새로 시작하려고 할 수 있습니다.

clearMarks()를 호출하여 설정한 마커를 쉽게 삭제할 수 있습니다.

따라서 아래 예시 코드는 기존 마크를 모두 제거하므로 원하는 경우 타이밍 실행을 다시 설정할 수 있습니다.

window.performance.clearMarks();

물론 모든 마크를 지우지 않는 것이 좋을 때도 있습니다. 따라서 특정 마크를 삭제하려면 삭제하려는 마크의 이름을 전달하면 됩니다. 예를 들어 아래 코드는 다음과 같습니다.

window.performance.clearMarks('mark_fully_loaded');

첫 번째 예에서 설정한 마크를 삭제하고 다른 마크는 변경되지 않은 상태로 둡니다.

취한 조치도 삭제할 수 있습니다. 이를 위한 메서드는 clearMeasures()입니다. clearMarks()와 완전히 동일하게 작동하지만, 이전에 측정한 내용을 토대로 작동합니다. 예를 들면 다음과 같습니다.

window.performance.clearMeasures('measure_load_from_dom');

는 위의 measure() 예에서 만든 측정항목을 삭제합니다. 모든 측정항목을 삭제하려면 인수 없이 clearMeasures()를 호출하기만 하면 되므로 clearMarks()와 동일하게 작동합니다.

타이밍 데이터 가져오기

마크를 설정하고 간격을 측정하는 것은 좋지만, 특정 시점에 분석을 수행하기 위해 타이밍 데이터를 가져와야 할 때가 있습니다. 이 작업도 매우 간단합니다. PerformanceTimeline 인터페이스를 사용하기만 하면 됩니다.

예를 들어 getEntriesByType() 메서드를 사용하면 모든 마크 시간 또는 모든 측정 시간을 목록으로 가져와서 반복하고 데이터를 소화할 수 있습니다. 목록이 시간순으로 반환되므로 웹 애플리케이션에서 마커가 발생한 순서대로 확인할 수 있습니다.

아래 코드:

var items = window.performance.getEntriesByType('mark');

는 웹 애플리케이션에서 적중된 모든 마크의 목록을 반환합니다. 반면 코드는 다음과 같습니다.

var items = window.performance.getEntriesByType('measure');

취한 모든 조치의 목록을 반환합니다.

지정한 특정 이름을 사용하여 항목 목록을 가져올 수도 있습니다. 예를 들어 다음 코드가 있습니다.

var items = window.performance.getEntriesByName('mark_fully_loaded');

그러면 startTime 속성에 'mark_full_loaded' 타임스탬프가 포함된 항목 하나가 포함된 목록이 반환됩니다.

XHR 요청 타이밍 (예)

이제 User Timing API에 대해 충분히 이해했으므로 이를 사용하여 웹 애플리케이션에서 모든 XMLHttpRequests가 걸리는 시간을 분석할 수 있습니다.

먼저 모든 send() 요청을 수정하여 표시를 설정하는 함수 호출을 실행하는 동시에 다른 표시를 설정한 다음 요청에 걸린 시간에 대한 측정값을 생성하는 함수 호출로 성공 콜백을 변경합니다.

따라서 일반적으로 XMLHttpRequest는 다음과 같이 표시됩니다.

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

이 예에서는 요청 수를 추적하고 전역 카운터를 사용하여 각 요청에 대한 측정항목을 저장하는 전역 카운터를 추가합니다. 이를 수행하는 코드는 다음과 같습니다.

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

위의 코드는 전송하는 모든 XMLHttpRequest에 고유한 이름 값이 있는 측정항목을 생성합니다. 여기서는 요청이 순서대로 실행된다고 가정합니다. 즉, 병렬 요청의 코드가 순서에 맞지 않게 반환되는 요청을 처리하기 위해서는 조금 더 복잡해야 하므로 독자를 위한 연습으로 남겨 두겠습니다.

웹 애플리케이션이 여러 요청을 실행한 후 아래 코드를 사용하여 모두 콘솔에 덤프할 수 있습니다.

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

결론

User Timing API는 웹 애플리케이션의 모든 측면에 적용할 수 있는 다양한 유용한 도구를 제공합니다. 웹 애플리케이션 전체에 API 호출을 뿌리고 생성된 타이밍 데이터를 후처리하여 시간이 소요되는 위치를 명확하게 파악하면 애플리케이션의 핫스팟을 쉽게 좁힐 수 있습니다. 하지만 브라우저가 이 API를 지원하지 않으면 어떻게 해야 할까요? 괜찮습니다. API를 매우 잘 에뮬레이션하고 webpagetest.org와도 잘 작동하는 훌륭한 폴리필을 확인할 수 있습니다. 무엇을 망설이고 계신가요? 지금 애플리케이션에서 User Timing API를 사용해 보세요. 속도를 높이는 방법을 찾을 수 있고 사용자는 훨씬 더 나은 환경을 제공해 주셔서 감사할 것입니다.