커스텀 측정항목

모든 웹사이트에서 측정할 수 있는 보편적인 사용자 중심 측정항목을 갖추면 사용자의 웹 경험을 이해하고 내 사이트를 경쟁업체와 비교하는 데 매우 유용할 수 있습니다. 그러나 특정 사이트의 전체 경험을 포착하려면 보편적인 측정항목 이상의 것을 측정해야 하는 경우가 많습니다.

맞춤 측정항목을 사용하면 내 사이트에만 적용되는 사이트 환경의 다음과 같은 측면을 측정할 수 있습니다.

  • 단일 페이지 앱 (SPA)이 한 '페이지'에서 다른 '페이지'로 전환하는 데 걸리는 시간입니다.
  • 로그인한 사용자의 데이터베이스에서 가져온 데이터를 페이지에 표시하는 데 걸리는 시간입니다.
  • 서버 측 렌더링 (SSR) 앱이 하이드레이션하는 데 걸리는 시간
  • 재방문자가 로드한 리소스의 캐시 적중률입니다.
  • 게임에서 발생한 클릭 또는 키보드 이벤트의 이벤트 지연 시간입니다.

커스텀 측정항목 측정을 위한 API

지금까지 웹 개발자는 성능 측정을 위한 하위 수준 API를 많이 사용하지 않았기 때문에 사이트의 성능이 우수한지 측정하기 위해 해킹 방법을 활용해야 했습니다. 예를 들어 requestAnimationFrame 루프를 실행하고 각 프레임 간의 델타를 계산하여 장기 실행 자바스크립트 작업으로 기본 스레드가 차단되는지 확인할 수 있습니다. 델타가 디스플레이의 프레임 속도보다 훨씬 길면 긴 작업으로 보고할 수 있습니다.

하지만 이와 같은 해킹은 기기의 배터리를 소모하는 등 사이트 성능에 영향을 줄 수 있습니다. 성능 측정 기법이 성능 문제를 일으키는 경우 이러한 기술에서 얻은 데이터가 정확하지 않을 수 있습니다. 따라서 커스텀 측정항목을 만들려면 다음 API 중 하나를 사용하는 것이 좋습니다.

Performance Observer API

브라우저 지원

  • 52
  • 79
  • 57
  • 11

소스

Performance Observer API는 이 페이지에서 설명하는 다른 모든 성능 API에서 데이터를 수집하고 표시하는 메커니즘입니다. 좋은 데이터를 얻기 위해서는 그것을 이해하는 것이 중요합니다.

PerformanceObserver를 사용하여 성능 관련 이벤트를 수동적으로 구독할 수 있습니다. 이렇게 하면 유휴 기간에 API 콜백이 실행될 수 있으므로 일반적으로 페이지 성능을 방해하지 않습니다.

PerformanceObserver를 만들 때 새 성능 항목이 전달될 때마다 실행되는 콜백을 전달하세요. 그런 다음 observe() 메서드를 사용하여 다음과 같이 리슨할 항목 유형을 관찰자에게 알립니다.

// Catch errors that some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });

  po.observe({type: 'some-entry-type'});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

다음 섹션에는 관찰할 수 있는 모든 항목 유형이 나열되어 있습니다. 최신 브라우저에서는 정적 PerformanceObserver.supportedEntryTypes 속성을 사용하여 사용 가능한 항목 유형을 검사할 수도 있습니다.

이미 발생한 항목 관찰

기본적으로 PerformanceObserver 객체는 발생하는 항목만 관찰할 수 있습니다. 따라서 우선순위가 높은 리소스를 차단하지 않도록 성능 분석 코드를 지연 로드하려는 경우 문제가 발생할 수 있습니다.

이전 항목을 가져오려면 buffered 플래그를 true로 설정하여 observe를 호출합니다. 그러면 PerformanceObserver 콜백이 처음 호출될 때 브라우저에 성능 항목 버퍼의 이전 항목이 포함됩니다.

po.observe({
  type: 'some-entry-type',
  buffered: true,
});

피해야 할 기존 성능 API

Performance Observer API 이전에는 개발자가 performance 객체에 정의된 다음 메서드를 사용하여 성능 항목에 액세스할 수 있었습니다. 이러한 메서드는 새 항목을 수신 대기할 수 없으므로 사용하지 않는 것이 좋습니다.

또한 많은 새로운 API (예: Long Tasks)가 performance 객체에서 노출되지 않고 PerformanceObserver에 의해서만 노출됩니다. 따라서 특별히 Internet Explorer 호환성이 필요한 경우가 아니라면 코드에서 이러한 메서드를 피하고 앞으로 PerformanceObserver를 사용하는 것이 가장 좋습니다.

사용자 시간 API

User Timing API는 시간 기반 측정항목의 범용 측정 API입니다. 이를 통해 시점을 임의로 표시한 후 나중에 이 표시 사이의 지속 시간을 측정할 수 있습니다.

// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');

// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');

Date.now() 또는 performance.now()와 같은 API가 비슷한 기능을 제공하지만, User Timing API는 성능 도구와 더 잘 통합됩니다. 예를 들어 Chrome DevTools는 성능 패널의 사용자 시간 측정을 시각화하며, 많은 분석 서비스 제공업체는 자동으로 개발자가 측정하는 측정을 추적하고 기간 데이터를 분석 백엔드로 전송합니다.

User Timing 측정값을 보고하려면 PerformanceObserver를 등록하여 measure 유형의 항목을 관찰하세요.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });
  // Start listening for `measure` entries.
  po.observe({type: 'measure', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

장기 작업 API

브라우저 지원

  • 58
  • 79
  • x
  • x

소스

Long Tasks API는 브라우저의 기본 스레드가 프레임 속도나 입력 지연 시간에 영향을 미칠 만큼 충분히 오랫동안 차단된 경우를 확인하는 데 유용합니다. API는 50밀리초 (ms) 넘게 실행된 모든 작업을 보고합니다.

비용이 많이 드는 코드를 실행하거나 대용량 스크립트를 로드하고 실행해야 할 때마다 코드가 기본 스레드를 차단하는지 추적하는 것이 유용합니다. 실제로 많은 상위 수준 측정항목이 Long Tasks API 자체를 기반으로 빌드됩니다 (예: 상호작용 시간 (TTI)총 차단 시간(TBT)).

장기 작업이 발생하는 시점을 확인하려면 PerformanceObserver를 등록하여 longtask 유형의 항목을 관찰합니다.

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });
  // Start listening for `longtask` entries to be dispatched.
  po.observe({type: 'longtask', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Element Timing API

브라우저 지원

  • 77
  • 79
  • x
  • x

소스

콘텐츠가 포함된 최대 페인트 (LCP) 측정항목은 페이지에서 가장 큰 이미지나 텍스트 블록이 화면에 표시되는 시점을 파악하는 데 유용하지만, 다른 요소의 렌더링 시간을 측정해야 하는 경우도 있습니다.

이러한 경우에는 Element Timing API를 사용하세요. LCP API는 실제로 Element Timing API를 기반으로 빌드되었으며 가장 큰 콘텐츠 요소의 자동 보고를 추가합니다. 하지만 elementtiming 속성을 명시적으로 추가하고 PerformanceObserver를 등록하여 element 항목 유형을 관찰함으로써 다른 요소에 관해 보고할 수도 있습니다.

<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
...
<script>
// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      // Log the entry and all associated details.
      console.log(entry.toJSON());
    }
  });
  // Start listening for `element` entries to be dispatched.
  po.observe({type: 'element', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}
</script>

Event Timing API

최초 입력 반응 시간 (FID) 측정항목은 사용자가 페이지와 처음 상호작용한 시점부터 브라우저가 상호작용에 응답하여 이벤트 핸들러 처리를 시작할 수 있는 시점까지의 시간을 측정합니다. 하지만 경우에 따라 이벤트 처리 시간 자체를 측정하는 것도 유용할 수 있습니다.

이는 FID 측정 외에도 다음과 같은 이벤트 수명 주기의 여러 타임스탬프를 노출하는 Event Timing API를 사용하면 가능합니다.

  • startTime: 브라우저가 이벤트를 수신하는 시간입니다.
  • processingStart: 브라우저가 이벤트의 이벤트 핸들러 처리를 시작할 수 있는 시간입니다.
  • processingEnd: 브라우저가 이 이벤트의 이벤트 핸들러에서 시작된 모든 동기 코드 실행을 완료한 시간입니다.
  • duration: 브라우저가 이벤트를 수신한 시점부터 이벤트 핸들러에서 시작된 모든 동기 코드의 실행을 완료한 후 다음 프레임을 그릴 수 있을 때까지의 시간 (보안상의 이유로 8ms로 반올림)입니다.

다음 예는 이러한 값을 사용하여 맞춤 측정을 만드는 방법을 보여줍니다.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  const po = new PerformanceObserver((entryList) => {
    const firstInput = entryList.getEntries()[0];

    // Measure First Input Delay (FID).
    const firstInputDelay = firstInput.processingStart - firstInput.startTime;

    // Measure the time it takes to run all event handlers
    // Doesn't include work scheduled asynchronously using methods like
    // `requestAnimationFrame()` or `setTimeout()`.
    const firstInputProcessingTime = firstInput.processingEnd - firstInput.processingStart;

    // Measure the entire duration of the event, from when input is received by
    // the browser until the next frame can be painted after processing all
    // event handlers.
    // Doesn't include work scheduled asynchronously using
    // `requestAnimationFrame()` or `setTimeout()`.
    // For security reasons, this value is rounded to the nearest 8 ms.
    const firstInputDuration = firstInput.duration;

    // Log these values to the console.
    console.log({
      firstInputDelay,
      firstInputProcessingTime,
      firstInputDuration,
    });
  });

  po.observe({type: 'first-input', buffered: true});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

Resource Timing API

개발자는 Resource Timing API를 사용하여 특정 페이지의 리소스가 로드된 방식을 자세히 파악할 수 있습니다. API의 이름에도 불구하고 API가 제공하는 정보는 타이밍 데이터에 국한되지 않습니다 (많은 정보 있음). 액세스할 수 있는 기타 데이터는 다음과 같습니다.

  • initiatorType: 리소스를 가져온 방법입니다(예: <script> 또는 <link> 태그 또는 fetch()).
  • nextHopProtocol: 리소스를 가져오는 데 사용되는 프로토콜입니다(예: h2 또는 quic).
  • encodedBodySizedecodedBodySize]: 각각 인코딩 또는 디코딩된 형태의 리소스 크기입니다.
  • transferSize: 네트워크를 통해 실제로 전송된 리소스의 크기입니다. 리소스가 캐시를 사용하여 처리될 때 이 값은 encodedBodySize보다 훨씬 작을 수 있으며, 캐시 재검증이 필요하지 않은 경우에는 0일 수 있습니다.

리소스 시간 항목의 transferSize 속성을 사용하여 캐시 적중률 측정항목 또는 캐시된 총 리소스 크기 측정항목을 측정할 수 있습니다. 이는 리소스 캐싱 전략이 반복 방문자의 성능에 미치는 영향을 이해하는 데 유용할 수 있습니다.

다음 예에서는 페이지에서 요청한 모든 리소스를 기록하고 각 리소스가 캐시를 사용하여 처리되었는지 여부를 나타냅니다.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log(entry.name, entry.transferSize === 0);
    }
  });
  // Start listening for `resource` entries to be dispatched.
  po.observe({type: 'resource', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

브라우저 지원

  • 57
  • 12
  • 58
  • 15

소스

Navigation Timing API는 Resource Timing API와 비슷하지만 탐색 요청만 보고합니다. navigation 항목 유형도 resource 항목 유형과 비슷하지만 탐색 요청 (예: DOMContentLoadedload 이벤트 실행 시)에만 관련된 몇 가지 추가 정보가 포함되어 있습니다.

많은 개발자가 서버 응답 시간을 이해하기 위해 추적하는 측정항목 중 하나인 첫 바이트 소요 시간 (TTFB)은 Navigation Timing API의 responseStart 타임스탬프를 통해 사용할 수 있습니다.

// Catch errors since  browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled using the cache.
      console.log('Time to first byte', entry.responseStart);
    }
  });
  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

서비스 워커를 사용하는 개발자들이 관심을 가질 만한 또 다른 측정항목은 탐색 요청의 서비스 워커 시작 시간입니다. 이 시간은 브라우저에서 가져오기 이벤트 가로채기를 시작하기 전에 서비스 워커 스레드를 시작하는 데 걸리는 시간입니다.

지정된 탐색 요청의 서비스 워커 시작 시간은 다음과 같이 entry.responseStartentry.workerStart 사이의 델타에서 결정될 수 있습니다.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.log('Service Worker startup time:',
          entry.responseStart - entry.workerStart);
    }
  });
  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Server Timing API

Server Timing API를 사용하면 응답 헤더를 사용하여 서버에서 브라우저로 요청별 타이밍 데이터를 전달할 수 있습니다. 예를 들어 특정 요청에 대해 데이터베이스에서 데이터를 조회하는 데 걸린 시간을 표시할 수 있습니다. 이는 서버의 속도 저하로 인해 발생한 성능 문제를 디버깅하는 데 유용할 수 있습니다.

서드 파티 분석 제공업체를 사용하는 개발자의 경우 Server Timing API는 서버 성능 데이터를 이러한 분석 도구에서 측정하는 다른 비즈니스 측정항목과 연결할 수 있는 유일한 방법입니다.

응답에 서버 타이밍 데이터를 지정하려면 Server-Timing 응답 헤더를 사용합니다. 예를 들면 다음과 같습니다.

HTTP/1.1 200 OK

Server-Timing: miss, db;dur=53, app;dur=47.2

그런 다음 페이지에서 Resource Timing 및 Navigation Timing API의 resource 또는 navigation 항목 모두에서 이 데이터를 읽을 수 있습니다.

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // Logs all server timing data for this response
      console.log('Server Timing', entry.serverTiming);
    }
  });
  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}