이 Codelab에서는 Google 애널리틱스와 User Timings API를 사용하여 웹사이트 또는 애플리케이션의 실제 성능을 측정하고 이를 최적화하여 사용자 환경을 개선하는 방법을 알아봅니다.
WebPagetest.org와 같은 도구는 성능 최적화를 시작하기에 좋지만, 사이트 성능의 진정한 테스트는 항상 실제 사용자의 실제 데이터가 됩니다.
웹사이트를 운영하는 경우 이미 Google 애널리틱스를 사용하여 트래픽과 기기 및 브라우저 사용량 등을 측정하고 있을 가능성이 높습니다. 코드를 조금만 추가하면 실적 측정항목을 추가할 수 있습니다.
학습할 내용
- User Timings API를 사용하여 성능 측정항목을 정확하고 효과적으로 측정하는 방법
- 보고서에 포함될 수 있도록 Google 애널리틱스로 데이터를 전송하는 방법
필요한 항목
- 개발자 콘솔이 있는 브라우저
- Chrome용 웹 서버 또는 원하는 웹 서버 사용
- 샘플 코드
- 텍스트 편집기
- (선택사항) Google 애널리틱스 계정
본 가이드를 어떻게 사용하실 계획인가요?
웹사이트 또는 애플리케이션 빌드 경험을 평가해 주세요.
컴퓨터에 모든 샘플 코드를 다운로드할 수 있습니다.
...또는 명령줄에서 GitHub 저장소를 클론합니다.
git clone https://github.com/googlecodelabs/performance-analytics.git
샘플 코드는 이 코드 랩의 번호가 지정된 각 단계에 해당하는 하위 디렉터리로 나뉩니다. 이를 사용하여 코드 랩을 쉽게 건너뛰거나 구현이 올바른지 확인할 수 있습니다.
차이점 비교 프로그램에 액세스할 수 있는 경우 이를 사용하여 단계별로 변경된 내용을 정확하게 확인할 수 있습니다.
이 코드 랩에서는 다음 애셋을 로드하는 단일 HTML 파일을 사용합니다.
- 웹 글꼴
- 스타일시트
- 이미지
- 자바스크립트
그리고 이러한 각 애셋 유형의 핵심 실적 측정항목을 측정하는 새 코드를 작성합니다.
애셋 실적 고려사항
성능 최적화에 대해 읽어본 적이 있다면 이러한 애셋 유형 각각에 다양한 특성이 있으며 전반적인 인식된 성능에 다양한 방식으로 영향을 미칠 수 있다는 것을 이미 알고 있을 것입니다.
CSS
예를 들어 스타일시트는 스타일시트 뒤에 오는 DOM의 모든 요소의 렌더링을 차단합니다. 즉, 브라우저가 스타일시트를 요청하고, 다운로드하고, 파싱해야 스타일시트 뒤에 오는 DOM의 콘텐츠를 렌더링할 수 있습니다. 따라서 일반적으로 스타일시트를 문서의 <head>에 배치하는 것이 가장 좋습니다. 또한 CSS의 차단 특성으로 인해 중요한 CSS만 <head>에 배치하고 중요하지 않은 CSS는 나중에 비동기식으로 로드하는 것이 좋습니다.
JavaScript
반면 JavaScript는 렌더링을 차단하지는 않지만 DOM의 파싱과 구성을 차단합니다. JavaScript는 DOM을 수정할 수 있으므로 브라우저가 <script> 태그 (비동기 스크립트 제외)를 볼 때마다 다음 태그로 계속 진행하기 전에 코드를 실행해야 합니다. <script> 태그가 외부 JavaScript 파일을 참조하는 경우 다음 단계로 이동하기 전에 코드를 다운로드하고 실행해야 합니다.
따라서 대부분의 DOM을 최대한 빨리 사용할 수 있도록 JavaScript를 닫는 </body> 태그 바로 전에 로드하는 것이 좋습니다.
웹 글꼴
웹 글꼴을 로드하는 경우 글꼴을 사용할 수 있을 때까지 문서 렌더링을 차단할 수도 있습니다. 이 경우 사용자에게 실제로 얼마나 걸리는지 파악하는 것이 중요합니다. 웹 글꼴은 나에게는 빠르게 로드되지만 내 사이트를 방문하는 대부분의 사용자에게는 매우 느리게 로드될 수 있습니다. 따라서 실제 데이터를 기반으로 측정하고 의사 결정을 내리는 것이 매우 중요합니다.
이미지
마지막으로 이미지는 사이트를 생생하게 만들 수 있지만 로드하는 데 가장 오래 걸리는 경우가 많습니다. 이것이 실제로 무엇을 의미하는지 이해하고 사용 패턴과 페이지 로드 시간 간의 상관관계를 파악하는 것은 최적화 방법을 이해하는 데 매우 중요합니다.
이 코드 실습의 첫 번째 단계는 성능 측정 코드를 추가하기 전에 데모 페이지가 어떻게 표시되는지 확인하는 것입니다.
데모를 보려면 새 폴더를 만들고 그 안에 index.html이라는 파일을 추가하세요. 그런 다음 아래 코드를 복사하여 index.html 파일에 붙여넣습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Performance Analytics Demo</title>
<!-- Start fonts -->
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
<!-- End fonts -->
<!-- Start CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
body { font-family: Roboto, sans-serif; margin: 1em; }
img { float: left; height: auto; width: 33.33%; }
.gallery { overflow: hidden; }
</style>
<!-- End CSS -->
</head>
<body>
<div class="container">
<!-- Start images -->
<div class="gallery">
<img src="http://lorempixel.com/380/200/animals/1/">
<img src="http://lorempixel.com/380/200/animals/2/">
<img src="http://lorempixel.com/380/200/animals/3/">
</div>
<!-- End images -->
<h1>Performance Analytics Demo</h1>
<p>Real performance data from real users.</p>
</div>
<!-- Start JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<!-- End JavaScript -->
</body>
</html>그런 다음 Chrome용 웹 서버를 열고 방금 만든 디렉터리에서 로컬 서버를 시작합니다. '자동으로 index.html 표시'가 선택되어 있는지 확인합니다.

이제 브라우저에서 http://127.0.0.1:8887/로 이동하여 데모 파일을 볼 수 있습니다. 예를 들면 다음과 같습니다.

데모 페이지가 실행되면 잠시 시간을 내어 코드를 살펴보고 로드되는 다양한 애셋 유형을 확인하세요. 다음 단계에서는 이러한 애셋이 로드되고 사용자가 상호작용할 수 있는 시점을 측정하는 코드를 추가합니다.
앞서 애셋 성능 고려사항 섹션에서 언급했듯이 CSS는 DOM 요소의 렌더링과 DOM에서 CSS 뒤에 오는 스크립트의 실행을 차단합니다.
방금 만든 데모 파일에는 Bootstrap과 몇 가지 인라인 스타일을 참조하는 다음 CSS가 포함되어 있습니다.
<!-- Start CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
body { font-family: Roboto, sans-serif; margin: 1em; }
img { float: left; height: auto; width: 33.33%; }
.gallery { overflow: hidden; }
</style>
<!-- End CSS -->CSS는 DOM 요소의 렌더링과 스크립트 실행을 모두 차단하므로 현재 시간을 저장하는 CSS 바로 뒤에 <script> 태그를 추가하여 CSS가 차단을 완료한 시점을 확인할 수 있습니다.
변수를 만들고 new Date()를 할당하여 이 작업을 수행할 수 있지만 User Timings API 덕분에 훨씬 쉬운 방법이 있습니다. 바로 performance.mark 메서드입니다.
CSS가 렌더링과 스크립트 실행을 모두 차단하는 시점을 표시하려면 닫는 <!-- End CSS --> 주석 바로 앞에 다음 코드 줄을 추가합니다.
<script>performance.mark('css:unblock');</script>performance.mark 메서드는 이 시점에 고해상도 타임스탬프를 만들고 메서드에 전달된 이름과 연결합니다. 이 경우 마크의 이름은 'css:unblock'입니다.
performance.mark 메서드는 performance.measure 메서드와 함께 사용됩니다. performance.measure 메서드는 탐색 타이밍 API의 다양한 지점에 대해 브라우저가 자동으로 만드는 표시 외에도 사용자가 만드는 표시 사이의 시간 차이를 계산하는 데 사용됩니다.
다음 유틸리티 함수는 추가한 마크와 Navigation Timing API에서 생성한 responseEnd 마크 사이의 시간 길이를 측정하고 반환합니다.
function measureDuration(mark, opt_reference) {
var reference = opt_reference || 'responseEnd';
var name = reference + ':' + mark;
// Clears any existing measurements with the same name.
performance.clearMeasures(name);
// Creates a new measurement from the reference point to the specified mark.
// If more than one mark with this name exists, the most recent one is used.
performance.measure(name, reference, mark);
// Gets the value of the measurement just created.
var measure = performance.getEntriesByName(name)[0];
// Returns the measure duration.
return measure.duration;
}이 유틸리티 함수를 사용하려면 perf-analytics.js이라는 새 파일을 index.html 파일과 동일한 디렉터리에 만들고 위의 코드를 복사하여 붙여넣습니다.
이제 이 함수가 정의되었으므로 함수를 호출하고 'css:unblock' 마크 이름을 전달할 수 있습니다. 다른 리소스 로딩을 방해하지 않으려면 창의 로드 이벤트가 발생한 후까지 이러한 측정을 실행하지 않아야 합니다.
이 코드를 호출하는 함수를 작성하면 perf-analytics.js 파일이 다음과 같이 표시됩니다.
window.onload = function() {
measureCssUnblockTime();
};
/**
* Calculates the time duration between the responseEnd timing event and when
* the CSS stops blocking rendering, then logs that value to the console.
*/
function measureCssUnblockTime() {
console.log('CSS', 'unblock', measureDuration('css:unblock'));
}
/**
* Accepts a mark name and an optional reference point in the navigation timing
* API and returns the time duration between the reference point and the last
* mark (chronologically).
* @param {string} mark The mark name.
* @param {string=} opt_reference An optional reference point from the
* navigation timing API. Defaults to 'responseEnd'.
* @return {number} The time duration
*/
function measureDuration(mark, opt_reference) {
var reference = opt_reference || 'responseEnd';
var name = reference + ':' + mark;
// Clears any existing measurements with the same name.
performance.clearMeasures(name);
// Creates a new measurement from the reference point to the specified mark.
// If more than one mark with this name exists, the most recent one is used.
performance.measure(name, reference, mark);
// Gets the value of the measurement just created.
var measure = performance.getEntriesByName(name)[0];
// Returns the measure duration.
return measure.duration;
}마지막으로 index.html에서 perf-analytics.js 스크립트를 로드해야 합니다. 이렇게 하려면 기본 문서에 다음 스크립트 태그를 추가하세요. 다른 리소스의 로딩을 방해하지 않도록 마지막에 추가해야 합니다.
<!-- Start performance analytics -->
<script async src="perf-analytics.js"></script>
<!-- End performance analytics -->이 단계를 완료하면 코드가 Codelab 저장소의 01-css 디렉터리에 있는 것과 일치해야 합니다.
브라우저에서 페이지를 로드하고 개발자 콘솔을 열면 다음과 같은 출력이 표시됩니다.

웹 글꼴은 일반적으로 초기 데모 파일에서 볼 수 있듯이 외부 스타일시트를 통해 로드됩니다.
<!-- Start fonts -->
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
<!-- End fonts -->CSS 파일에 대한 <link> 태그이므로 글꼴이 로드되어 사용할 준비가 되었을 때를 확인하는 것은 1단계와 마찬가지로 <link> 바로 뒤에 <script> 태그 내에 표시를 추가하는 것만큼 간단해 보일 수 있습니다.
하지만 그렇게 간단하지 않습니다.
스타일시트의 콘텐츠는 CSSOM을 구성하는 데 사용되므로 스타일시트는 JavaScript의 실행을 차단합니다. 로드되는 JavaScript가 CSSOM에 액세스해야 할 수 있으므로 CSSOM이 완전히 구성될 때까지 실행이 지연되어야 합니다.
문제는 브라우저가 실제로 글꼴을 다운로드하지 않고 CSSOM을 구성할 수 있다는 것입니다. 즉, 글꼴의 스타일시트 <link> 태그 바로 뒤에 인라인 스크립트 태그를 통해 DOM에 표시를 추가하면 글꼴이 완전히 로드되기 전에 표시가 발생할 가능성이 높습니다.
브라우저에서 글꼴 로드 이벤트를 사용할 수 있을 때까지는 JavaScript를 사용하여 글꼴이 실제로 활성화되어 페이지에서 사용할 준비가 되었는지 확인해야 합니다. 다행히도 JavaScript를 통해 글꼴을 로드하면 CSS 파일에 대한 추가 차단 요청이 필요하지 않으므로 성능도 향상됩니다.
Google과 Typekit이 공동 개발한 webfont.js 스크립트를 통해 Google 글꼴, Typekit, font.com 글꼴을 비롯한 대부분의 웹 글꼴을 로드할 수 있습니다.
webfont.js를 사용하여 글꼴을 로드하도록 기본 문서를 업데이트하려면(<link> 태그 대신) 코드의 글꼴 섹션을 다음으로 대체하세요.
<!-- Start fonts -->
<script>
window.WebFontConfig = {
google: {families: ['Roboto:400,700,400italic']},
timeout: 10000,
active: function() {
performance.mark('fonts:active');
}
};
</script>
<script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<!-- End fonts -->위 코드에는 두 가지 중요한 사항이 있습니다.
- 활성 콜백에 'fonts:active' 표시를 만들어 나중에 글꼴이 로드되는 데 걸린 시간을 측정할 수 있습니다.
- webfonts.js를 로드하는
<script>태그에는async속성이 포함되어 있으므로 문서의 나머지 부분의 파싱이나 렌더링을 차단하지 않습니다 (<link>태그에는 해당되지 않음).
위 코드는 'fonts:active' 표시를 만들지만 이 표시를 측정하고 콘솔에 로깅하는 것은 'css:unblock' 표시의 경우만큼 간단하지 않습니다. 글꼴 로딩이 이제 비동기적으로 발생하므로 window.onload 핸들러에서 'fonts:active' 표시를 측정하려고 하면('css:unblock'에서와 같이) 글꼴이 아직 로드되지 않았을 수 있습니다.
이 문제를 해결하려면 글꼴이 로드되면 해결되는 프로미스를 만들면 됩니다. 다음 함수가 이 작업을 실행합니다. perf-analytics.js 파일에 복사하여 붙여넣습니다.
/**
* Creates a promise that is resolved once the web fonts are fully load or
* is reject if the fonts fail to load. The resolved callback calculates the
* time duration between the responseEnd timing event and when the web fonts
* are downloaded and active. If an error occurs loading the font, this fact
* is logged to the console.
*/
function measureWebfontPerfAndFailures() {
new Promise(function(resolve, reject) {
// The classes `wf-active` or `wf-inactive` are added to the <html>
// element once the fonts are loaded (or error).
var loaded = /wf-(in)?active/.exec(document.documentElement.className);
var success = loaded && !loaded[1]; // No "in" in the capture group.
// If the fonts are already done loading, resolve immediately.
// Otherwise resolve/reject in the active/inactive callbacks, respectively.
if (loaded) {
success ? resolve() : reject();
}
else {
var originalAciveCallback = WebFontConfig.active;
WebFontConfig.inactive = reject;
WebFontConfig.active = function() {
originalAciveCallback();
resolve();
};
// In case the webfont.js script fails to load, always reject the
// promise after the timeout amount.
setTimeout(reject, WebFontConfig.timeout);
}
})
.then(function() {
console.log('Fonts', 'active', measureDuration('fonts:active'));
})
.catch(function() {
console.error('Error loading web fonts')
});
}이 새 함수를 호출하도록 window.onload 핸들러도 업데이트합니다.
window.onload = function() {
measureCssUnblockTime();
measureWebfontPerfAndFailures();
};이 단계를 완료하면 코드가 Codelab 저장소의 02-fonts 디렉터리에 있는 것과 일치해야 합니다.
브라우저에서 페이지를 로드하고 개발자 콘솔을 열면 다음과 같은 출력이 표시됩니다.

이미지가 표시되는 시점을 파악하는 것은 생각보다 간단하지 않습니다. 이전 단계에서 스타일시트와 동기식 <script> 태그가 렌더링, 파싱, 스크립트 실행을 차단할 수 있다는 것을 알고 있습니다. 두 가지 모두 브라우저의 미리 로드 스캐너를 차단하지 않는다는 사실을 모를 수도 있습니다.
사전 로드 스캐너는 차단 애셋이 많은 성능에 관심이 없는 사이트에서도 성능을 개선하기 위한 여러 시도 중 하나로 모든 최신 브라우저에서 구현하는 것입니다. 일부 애셋은 파싱이나 렌더링, 스크립트 실행을 차단할 수 있지만 다운로드를 차단할 필요는 없습니다. 따라서 브라우저는 DOM을 구성하기 전에 HTML 파일을 스캔하고 즉시 다운로드를 시작할 수 있는 애셋을 찾습니다.
이미지의 경우 이미지가 DOM에 추가될 때 이미 다운로드되었을 가능성이 꽤 높습니다. 또한 이미지가 다운로드되는 시점이 반드시 좋은 실적 측정항목은 아닙니다. 관심을 가져야 하는 성능 측정항목은 이미지가 사용자에게 표시되는 시점입니다.
이미지가 DOM에 추가되기 전에 다운로드되면 이미지가 표시되는 시점은 DOM에 있는 시점입니다. 반면 이미지가 DOM에 추가되기 전에 다운로드되지 않으면 이미지가 표시되는 시점은 onload 핸들러가 실행될 때입니다.
따라서 이미지가 표시되는 시점을 알기 위해서는 두 가지 경우를 모두 처리해야 합니다.
각 이미지의 onload 핸들러와 DOM의 마지막 이미지 바로 뒤에 있는 인라인 <script> 태그에 표시를 추가하면 됩니다. 마지막에 발생하는 표시가 모든 이미지가 표시되는 시점을 나타내는 표시가 됩니다.
이미지가 로드될 때와 렌더링될 때 모두에 마크를 추가하려면 index.html에서 이미지 코드를 다음과 같이 업데이트합니다.
<!-- Start images -->
<div class="gallery">
<img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
<img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
<img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>performance.mark('img:visible')</script>
<!-- End images -->특정 마크 이름의 performance.measure 메서드는 항상 마지막 마크를 사용하므로 (이름이 동일한 마크가 여러 개 있는 경우) perf-analytics.js 파일의 measureDuration 유틸리티 함수를 추가 수정 없이 사용할 수 있습니다.
/**
* Calculates the time duration between the responseEnd timing event and when
* all images are loaded and visible on the page, then logs that value to the
* console.
*/
function measureImagesVisibleTime() {
console.log('Images', 'visible', measureDuration('img:visible'));
}위 함수를 perf-analytics.js 파일에 추가한 다음 이를 호출하도록 window.onload 핸들러를 업데이트합니다.
window.onload = function() {
measureCssBlockTime();
measureWebfontPerfAndFailures();
measureImagesVisibleTime();
};이 단계를 완료하면 코드가 Codelab 저장소의 03-images 디렉터리에 있는 것과 일치해야 합니다.
브라우저에서 페이지를 로드하고 개발자 콘솔을 열면 다음과 같은 출력이 표시됩니다.

async 속성이 없는 <script> 태그는 다운로드되고 실행될 때까지 DOM 파싱을 차단하므로 DOM의 마지막 동기 <script> 바로 뒤에 인라인 스크립트 태그에 표시를 추가하여 모든 스크립트가 실행을 완료한 시점을 확인할 수 있습니다.
브라우저가 스크립트를 로드한 후 실행해야 하므로 시간이 걸리므로 이 경우 onload 핸들러를 사용하면 작동하지 않습니다. 로드 속도는 빠르지만 실행 속도가 느린 스크립트는 로드 속도가 느린 스크립트만큼 나쁠 수 있습니다.
기본 문서에서 모든 JavaScript가 로드되고 실행되는 시점을 추적하려면 index.html의 JavaScript 섹션을 다음 코드로 업데이트합니다.
<!-- Start JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>performance.mark('js:execute');</script>
<!-- End JavaScript -->이렇게 하면 jQuery 및 Bootstrap 플러그인용 스크립트가 다운로드되고 실행이 완료된 직후에 'js:execute'라는 표시가 추가됩니다.
이 작업에 걸리는 시간을 측정하려면 perf-analytics.js에 다음 함수를 추가하세요.
/**
* Calculates the time duration between the responseEnd timing event and when
* all synchronous JavaScript files have been downloaded and executed, then
* logs that value to the console.
*/
function measureJavaSciptExecutionTime() {
console.log('JavaScript', 'execute', measureDuration('js:execute'));
}그런 다음 window.onload 핸들러에서 호출합니다.
window.onload = function() {
measureCssBlockTime();
measureWebfontPerfAndFailures();
measureImagesVisibleTime();
measureJavaSciptExecutionTime();
};이 단계를 완료하면 코드가 Codelab 저장소의 04-javascript 디렉터리에 있는 것과 일치해야 합니다.
브라우저에서 페이지를 로드하고 개발자 콘솔을 열면 다음과 같은 출력이 표시됩니다.

일부 브라우저에서는 JavaScript 프로미스 또는 사용자 타이밍 API를 지원하지 않으며, 이러한 기능 중 하나를 지원하지 않는 브라우저에서 지금까지 작성한 코드를 실행하면 오류가 발생합니다.
이 문제를 해결하려면 기능 감지를 사용하면 됩니다. 글꼴 섹션 바로 앞에 다음 코드 비트를 추가합니다. 이 JavaScript 줄은 performance.mark 메서드 지원을 감지하므로 이 메서드를 사용하기 전에 페이지에 추가해야 합니다.
<!-- Start feature detects -->
<script>window.__perf = window.performance && performance.mark;</script>
<!-- End feature detects -->그런 다음 performance.mark을 호출하는 index.html의 아무 곳에서나 기능 감지를 접두사로 사용합니다. 다음은 이미지 블록의 코드를 업데이트하는 예시입니다. 다른 섹션도 업데이트해야 합니다.
<!-- Start images -->
<div class="gallery">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>__perf && performance.mark('img:visible')</script>
<!-- End images -->이제 __perf 변수가 window에 설정되었으므로 perf-analytics.js에서도 이를 사용하여 현재 브라우저에서 지원되지 않는 메서드를 호출하지 않도록 할 수 있습니다.
다음 조건을 추가하도록 measureDuration 함수를 업데이트해야 합니다.
function measureDuration(mark, opt_reference) {
if (window.__perf) {
// ...
}
}마지막으로 모든 브라우저가 JavaScript 프로미스를 지원하지는 않으므로 measureWebfontPerfAndFailures 함수도 조건으로 래핑해야 합니다.
function measureWebfontPerfAndFailures() {
if (window.Promise) {
// ...
}
}이제 문제없이 모든 브라우저에서 코드를 실행할 수 있습니다.
이 단계를 완료하면 코드가 Codelab 저장소의 05-feature-detects 디렉터리에 있는 것과 일치해야 합니다.
이 Codelab의 마지막 단계는 콘솔에 로깅되는 데이터를 가져와서 Google 애널리틱스로 전송하는 것입니다. Google 애널리틱스로 데이터를 전송하려면 먼저 analytics.js 라이브러리와 기본 추적 스니펫을 페이지에 추가해야 합니다.
기본 JavaScript 블록 뒤에 index.html에 다음 코드를 추가합니다(perf-analytics.js 스크립트가 로드되기 전).
<!-- Start analytics tracking snippet -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics_debug.js"></script>
<!-- End analytics tracking snippet -->이전에 웹사이트에 Google 애널리틱스를 추가한 적이 있다면 'UA-XXXXX-Y' 자리표시자를 Google 애널리틱스에서 새 속성을 만들 때 받은 추적 ID로 바꿔야 한다는 것을 알고 있을 것입니다.
analytics.js 추적 스니펫은 다음 네 가지 주요 작업을 실행합니다.
- analytics.js JavaScript 라이브러리를 다운로드하는 비동기
<script>요소를 만듭니다. - analytics.js 라이브러리가 로드되고 사용할 준비가 되면 실행할 명령어를 예약할 수 있는 전역
ga()함수 (ga() 명령어 큐를 호출함)를 초기화합니다. ga()명령어 큐에 명령어를 추가하여'UA-XXXXX-Y' 매개변수를 통해 지정된 속성의 새 추적기 객체를 만듭니다.ga()명령어 큐에 다른 명령어를 추가하여 현재 페이지의 페이지 조회를 Google 애널리틱스로 전송합니다.
페이지 조회수에서 수집된 데이터는 유용하지만 전체 상황을 파악하기에는 부족합니다. 사용자가 사이트 또는 애플리케이션을 어떻게 경험하는지 더 잘 파악하려면 Google 애널리틱스로 추가 상호작용 데이터를 전송해야 합니다.
Google 애널리틱스는 페이지 조회수, 이벤트, 소셜 상호작용, 예외, 사용자 타이밍 등 여러 유형의 상호작용 데이터를 지원합니다. Google 애널리틱스로 사용자 시간 데이터를 전송하려면 다음 명령어 서명을 사용하면 됩니다.
ga('send', 'timing', timingCategory, timingVar, timingValue);여기서 timingCategory은 타이밍 조회수를 논리적 그룹으로 정리할 수 있는 문자열이고, timingVar은 측정하는 변수이며, timingValue은 실제 시간(밀리초)입니다.
실제로 작동하는 방식을 확인하려면 measureCssUnblockTime 함수의 console.log 문을 다음과 같이 업데이트하면 됩니다.
ga('send', 'timing', 'CSS', 'unblock', measureDuration('css:unblock'));위 코드는 일부 상황에서 작동하지만 다음과 같은 두 가지 중요한 주의사항이 있습니다.
- 이전 단계에서는 브라우저가 User Timings API를 지원하는 경우에만 실행되도록
measureDuration함수를 업데이트했습니다. 즉,undefined가 반환되는 경우가 있습니다. 정의되지 않은 데이터를 Google 애널리틱스로 전송할 이유가 없으므로 (경우에 따라 보고서가 엉망이 될 수도 있음)measureDuration가 값을 반환하는 경우에만 이 타이밍 조회를 전송해야 합니다. measureDuration가 값을 반환하는 경우 밀리초보다 정밀도가 높은DOMHighResTimeStamp입니다. Google 애널리틱스의timingValue는 정수여야 하므로measureDuration에서 반환된 값을 반올림해야 합니다.
이러한 주의사항을 고려하여 measureDuration 함수의 return 문을 업데이트하여 반환 값을 반올림합니다.
function measureDuration(mark, opt_reference) {
if (window.__perf) {
// ...
return Math.round(measure.duration);
}
}그리고 해당 측정항목에 값이 있는 경우에만 실행되도록 타이밍 명령어를 업데이트합니다. 예를 들어 measureCssUnblockTime 함수는 다음과 같이 업데이트해야 합니다.
function measureCssUnblockTime() {
var cssUnblockTime = measureDuration('css:unblock');
if (cssUnblockTime) {
ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
}
}다른 모든 측정 함수도 유사하게 업데이트해야 합니다. 완료되면 최종 perf-analytics.js 파일은 다음과 같이 표시됩니다.
window.onload = function() {
measureCssUnblockTime();
measureWebfontPerfAndFailures();
measureImagesVisibleTime();
measureJavaSciptExecutionTime();
};
/**
* Calculates the time duration between the responseEnd timing event and when
* the CSS stops blocking rendering, then sends this measurement to Google
* Analytics via a timing hit.
*/
function measureCssUnblockTime() {
var cssUnblockTime = measureDuration('css:unblock');
if (cssUnblockTime) {
ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
}
}
/**
* Calculates the time duration between the responseEnd timing event and when
* the web fonts are downloaded and active, then sends this measurement to
* Google Analytics via a timing hit. If an error occurs loading the font, an
* error event is sent to Google Analytics.
*/
function measureWebfontPerfAndFailures() {
if (window.Promise) {
new Promise(function(resolve, reject) {
var loaded = /wf-(in)?active/.exec(document.documentElement.className);
var success = loaded && !loaded[1]; // No "in" in the capture group.
if (loaded) {
success ? resolve() : reject();
}
else {
var originalAciveCallback = WebFontConfig.active;
WebFontConfig.inactive = reject;
WebFontConfig.active = function() {
originalAciveCallback();
resolve();
};
// In case the webfont.js script failed to load.
setTimeout(reject, WebFontConfig.timeout);
}
})
.then(function() {
var fontsActiveTime = measureDuration('fonts:active');
if (fontsActiveTime) {
ga('send', 'timing', 'Fonts', 'active', fontsActiveTime);
}
})
.catch(function() {
ga('send', 'event', 'Fonts', 'error');
});
}
}
/**
* Calculates the time duration between the responseEnd timing event and when
* all images are loaded and visible on the page, then sends this measurement
* to Google Analytics via a timing hit.
*/
function measureImagesVisibleTime() {
var imgVisibleTime = measureDuration('img:visible');
if (imgVisibleTime) {
ga('send', 'timing', 'Images', 'visible', imgVisibleTime);
}
}
/**
* Calculates the time duration between the responseEnd timing event and when
* all synchronous JavaScript files are downloaded and executed, then sends
* this measurement to Google Analytics via a timing hit.
*/
function measureJavaSciptExecutionTime() {
var jsExecuteTime = measureDuration('js:execute');
if (jsExecuteTime) {
ga('send', 'timing', 'JavaScript', 'execute', jsExecuteTime);
}
}
/**
* Accepts a mark name and an optional reference point in the navigation timing
* API and returns the time duration between the reference point and the last
* mark (chronologically). The return value is rounded to the nearest whole
* number to be compatible with Google Analytics.
* @param {string} mark The mark name.
* @param {string=} opt_reference An optional reference point from the
* navigation timing API. Defaults to 'responseEnd'.
* @return {?number} The time duration as an integer or undefined if no
* matching marks can be found.
*/
function measureDuration(mark, opt_reference) {
if (window.__perf) {
var reference = opt_reference || 'responseEnd';
var name = reference + ':' + mark;
// Clears any existing measurements with the same name.
performance.clearMeasures(name);
// Creates a new measurement from the reference point to the specified mark.
// If more than one mark with this name exists, the most recent one is used.
performance.measure(name, reference, mark);
// Gets the value of the measurement just created.
var measure = performance.getEntriesByName(name)[0];
// Returns the measure duration.
return Math.round(measure.duration);
}
}최종 index.html 파일은 다음과 같습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Performance Analytics Demo</title>
<!-- Start navigation timing feature detect -->
<script>window.__perf = window.performance && performance.mark;</script>
<!-- End navigation timing feature detect -->
<!-- Start fonts -->
<script>
window.WebFontConfig = {
google: {families: ['Roboto:400,700,400italic']},
timeout: 10000,
active: function() {
__perf && performance.mark('fonts:active');
}
};
</script>
<script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<!-- End fonts -->
<!-- Start CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
body { font-family: Roboto, sans-serif; margin: 1em; }
img { float: left; height: auto; width: 33.33%; }
.gallery { overflow: hidden; }
</style>
<script>__perf && performance.mark('css:unblock');</script>
<!-- End CSS -->
</head>
<body>
<div class="container">
<!-- Start images -->
<div class="gallery">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
<img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>__perf && performance.mark('img:visible')</script>
<!-- End images -->
<h1>Performance Analytics Demo</h1>
<p>Real performance data from real users.</p>
</div>
<!-- Start JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>__perf && performance.mark('js:execute');</script>
<!-- End JavaScript -->
<!-- Start analytics tracking snippet -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
<!-- End analytics tracking snippet -->
<!-- Start performance analytics -->
<script async src="perf-analytics.js"></script>
<!-- End performance analytics -->
</body>
</html>이 페이지를 로드하고 네트워크 패널에서 요청을 살펴보면 다음과 같은 내용이 표시됩니다.

이는 유용하지만 이 데이터를 URL로 인코딩된 요청으로 확인하는 것은 번거로울 수 있습니다. 어떤 이유로든 이러한 요청이 표시되지 않으면 실패가 발생한 위치를 추적하기가 매우 어렵습니다.
로컬에서 개발할 때는 각 analytics.js 명령어가 실행될 때 유용한 디버깅 정보를 콘솔에 기록하는 analytics.js의 디버그 버전을 사용하는 것이 좋습니다. 만약
index.html의 analytics.js URL을 analytics_debug.js로 업데이트하고 브라우저 콘솔을 열면 다음과 같은 문이 출력됩니다.

이제 이 데모 페이지의 성능 측정을 구현하는 방법을 이해했으므로 자체 사이트에 추가하여 실제 사용자 데이터를 Google 애널리틱스로 전송해 보세요.
수집한 데이터에 대한 보고
며칠 동안 성능 데이터를 수집한 후에는 해당 데이터를 보고하여 실제 사용자에 대해 사이트와 리소스가 실제로 얼마나 빠르게 로드되는지에 관한 실행 가능한 통계를 얻을 수 있습니다.
Google 애널리틱스에서 사용자 타이밍 보고서로 이동하려면 상단의 보고 탭을 클릭하고 사이드바 탐색에서 '행동 > 사이트 속도 > 사용자 타이밍'을 선택합니다 (또는 고객센터의 안내에 따라 사용자 타이밍 보고서를 확인합니다).
Google 애널리틱스에서 사용자 타이밍 보고서를 로드하면 전송한 데이터에 해당하는 타이밍 카테고리가 표시됩니다. 이러한 항목을 클릭하면 타이밍 데이터의 세부 시각화를 확인할 수 있습니다. 다음 이미지는 지난 24시간 동안 Google Fonts를 사용하는 실제 웹사이트의 글꼴 로드 시간의 예입니다.

축하합니다. 이 코드 실습을 완료했습니다. 더 자세히 알아보려면 다음 섹션에서 이 코드를 기반으로 더 많은 통계를 얻는 방법을 몇 가지 제안해 드립니다.
이 Codelab에서 다루는 성능 측정항목은 실제 사용자를 위해 사이트가 로드되는 방식을 측정하는 데 중요하지만 시작에 불과합니다. 실적 분석을 자세히 살펴보려면 더 많은 측정항목을 추적하면 됩니다.
이 코드 랩에서는 리소스를 사용자가 사용할 수 있는 시점과 관련된 측정항목을 추적했습니다. 원하는 경우 이러한 항목을 더 세분화할 수 있습니다. 예를 들어 JavaScript 실행이 완료된 시점만 측정하는 대신 로드가 시작된 시점, 로드가 완료된 시점, 실행이 시작된 시점, 마지막으로 실행이 완료된 시점을 측정할 수 있습니다. 이러한 각 측정항목은 하나만으로는 알 수 없는 문제를 발견할 수 있습니다.
더 세분화된 분석을 수행하는 것 외에도 일반적인 실적 분석 전략을 더 전체적으로 고려해야 합니다. 플랫폼의 목표는 무엇인가요? 성공이란 무엇인가요?
모든 종류의 분석을 할 때는 일반적으로 질문으로 시작한 다음 데이터를 사용하여 질문에 답하는 방법을 파악합니다.
예를 들어 다음 질문 목록과 이 코드 랩에서 얻은 지식을 사용하여 분석을 통해 질문에 답하는 방법을 생각해 보세요.
- 추적하는 측정항목의 값이 시간이 지남에 따라 감소하거나 증가하고 있나요?
- 서비스 워커 또는 로컬 스토리지를 통한 오프라인 캐싱 사용이 사이트의 전반적인 성능에 어떤 영향을 미치나요?
- 리소스가 최적으로 로드되고 있나요? 즉, 리소스가 다운로드된 시점과 사용 가능한 시점 사이에 큰 차이가 있나요?
- 실적과 추적 중인 다른 측정항목 (예: 가입률, 사이트에 머문 시간, 구매 등) 간에 상관관계가 있나요?
마지막으로 웹 성능 또는 Google 애널리틱스에 대해 자세히 알아보려면 다음 리소스를 참고하세요.
- 고성능 웹사이트를 구축하는 데 도움이 되는 도구 및 정보
https://developers.google.com/speed/ - 개발자가 Google 애널리틱스 플랫폼을 활용할 수 있는 도구 및 API
https://developers.google.com/analytics/ - Google 애널리틱스 제품 자체를 사용하는 방법을 알려주는 온라인 과정 (Google 애널리틱스에서 근무하는 사람이 강의함)
https://analyticsacademy.withgoogle.com/