이 Codelab에서는 Google 애널리틱스 및 User Timings API를 사용하여 웹사이트 또는 애플리케이션의 실제 성능을 측정하고 최적화하여 사용자 환경을 개선하는 방법을 알아봅니다.
WebPagetest.org와 같은 도구는 성능 최적화를 위한 훌륭한 출발점이지만, 사이트 성능의 실제 테스트는 항상 실제 사용자의 실제 데이터가 됩니다.
웹사이트를 운영 중이라면 이미 Google 애널리틱스를 사용하여 트래픽과 기기 사용량 등을 측정하는 데 사용하고 있을 가능성이 높습니다. 약간의 코드만으로도 실적 측정항목을 추가할 수 있습니다.
학습할 내용
- User Timings API를 사용하여 성능 측정항목을 정확하고 효과적으로 측정하는 방법
- 해당 데이터를 보고서에 통합하여 Google 애널리틱스에 전송하는 방법
필요한 항목
- 개발자 콘솔이 있는 브라우저
- Chrome용 웹 서버 또는 원하는 웹 서버 사용
- 샘플 코드
- 텍스트 편집기
- (선택사항) Google 애널리틱스 계정
본 가이드를 어떻게 사용하실 계획인가요?
웹사이트 또는 애플리케이션 빌드 관련 사전지식 수준을 평가해 주세요.
컴퓨터에 모든 샘플 코드를 다운로드할 수 있습니다.
...또는 명령줄에서 GitHub 저장소를 클론합니다.
git clone https://github.com/googlecodelabs/performance-analytics.git
샘플 코드는 이 Codelab에서 번호가 지정된 각 단계에 해당하는 하위 디렉터리로 나뉩니다. 이를 통해 Codelab을 쉽게 건너뛰거나 구현이 올바른지 확인할 수 있습니다.
디핑 프로그램에 액세스할 수 있다면 이 프로그램을 사용하여 단계마다 무엇이 변했는지 확인할 수 있습니다.
이 Codelab에서는 다음 애셋을 로드하는 단일 HTML 파일을 사용합니다.
- 웹 글꼴
- 스타일시트
- 이미지
- 자바스크립트
이제 각 애셋 유형의 핵심 실적 측정항목을 측정하는 새 코드를 작성하겠습니다.
애셋 성능 고려사항
실적 최적화에 대해 읽어본 적이 있다면 이러한 애셋 유형별로 각각 고유한 특성이 있으며 다양한 방식으로 인식되는 전반적인 실적에 영향을 줄 수 있습니다.
CSS
예를 들어 스타일시트는 그 뒤에 오는 DOM의 모든 요소의 렌더링을 차단합니다. 즉, 브라우저는 스타일시트를 요청하고 다운로드하고 그 뒤에 오는 DOM의 콘텐츠를 렌더링하기 전에 파싱해야 합니다. 따라서 일반적으로 스타일시트를 문서의 <lt;head>에 배치하는 것이 좋습니다. CSS의 차단 특성으로 인해 중요한 CSS를 <head>에만 배치하고 중요하지 않은 CSS는 나중에 비동기식으로 로드하는 것이 좋습니다.
자바스크립트
반면 자바스크립트는 렌더링을 차단하지 않지만 DOM의 파싱 및 구성을 차단합니다. 이는 자바스크립트가 DOM을 수정할 수 있기 때문에 필요합니다. 즉, 브라우저에서 <script> 태그를 볼 때마다 (비동기 스크립트 제외) 다음 태그로 진행하기 전에 코드를 실행해야 합니다. <script> 태그가 외부 자바스크립트 파일을 참조하는 경우 계속 진행하기 전에 코드를 다운로드하고 실행해야 합니다.
따라서 대부분의 DOM을 최대한 빨리 사용할 수 있도록 종료 </body> 태그 바로 앞에 자바스크립트를 로드하는 것이 좋습니다.
웹 글꼴
웹 글꼴을 로드하는 경우 글꼴을 사용할 수 있을 때까지 문서의 렌더링을 차단하도록 선택할 수도 있습니다. 이 경우 사용자에게 실제 소요되는 시간을 파악하는 것이 중요합니다. 웹 글꼴은 빠르게 로드되지만 사이트를 방문하는 대부분의 사용자에게는 매우 느리게 로드됩니다. 따라서 실제 데이터를 측정하고 결정을 내리는 것이 매우 중요합니다.
이미지
마지막으로, 이미지를 통해 사이트의 생동감을 더할 수 있지만 로드 시간이 가장 오래 걸리는 경우도 많습니다. 실제 의미와 사용 패턴 및 페이지 로드 시간 사이의 상관관계를 이해하는 것이 최적화 방법을 이해하는 데 중요합니다.
이 Codelab의 첫 단계는 성능 측정 코드를 추가하기 전에 데모 페이지의 모양을 확인하는 것입니다.
데모를 보려면 새 폴더를 만들고 여기에 폴더 안에 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에서 그 뒤에 오는 스크립트 실행을 차단합니다.
방금 만든 데모 파일에는 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
메서드와 함께 사용합니다. 또한, Navigation Timing 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;
}
이 유틸리티 함수를 사용하려면 index.html
파일과 같은 디렉터리에 perf-analytics.js
라는 새 파일을 만들고 위의 코드를 복사하여 붙여넣습니다.
이제 이 함수가 정의되었으므로 전화를 걸어 "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의 구성에 사용되기 때문에 자바스크립트의 실행이 차단되며, 로드 중인 자바스크립트는 CSSOM에 액세스해야 할 수 있으므로 CSSOM이 완전히 구성될 때까지 실행은 지연되어야 합니다.
중요한 점은 브라우저에서 실제로 글꼴을 다운로드하지 않고도 CSSOM을 구성할 수 있다는 것입니다. 즉, 글꼴의 글꼴 시트(StyleSheet) <링크> 태그 바로 뒤에 인라인 스크립트 태그를 통해 표시를 추가하면 글꼴이 완전히 로드되기 전에 이러한 상황이 발생할 가능성이 있습니다.
브라우저에서 글꼴 로드 이벤트를 사용할 수 있을 때까지 자바스크립트는 글꼴이 실제로 활성 상태이고 페이지에서 사용할 수 있는 시점을 파악해야 합니다. 다행히 자바스크립트를 통해 글꼴을 로드하는 것은 CSS 파일에 추가적인 차단 요청이 필요하지 않기 때문에 성능 측면에서도 좋은 일입니다.
Google 웹, Typekit에서 공동 개발한 webfont.js 스크립트를 통해 대부분의 웹 글꼴 (Google 글꼴, typekit, font.com 글꼴 포함)을 로드할 수 있습니다.
<link> 태그가 아닌 webfont.js를 사용하여 글꼴을 로드하도록 기본 문서를 업데이트하려면 코드의 글꼴 섹션을 다음으로 바꿉니다.
<!-- 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" 표시만큼 간단하지 않습니다. 이제 글꼴 로드가 비동기식으로 표시되므로 "css:unblock"와 마찬가지로 window.onload
핸들러에서 &;t를 측정하려고 하면 글꼴이 아직 로드되지 않을 수 있습니다.
이 문제를 해결하려면 글꼴이 로드되면 해결되는 프라미스를 만들면 됩니다. 다음 함수를 사용하면 됩니다. 이를 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 파싱을 차단하므로 DOM에서 마지막 동기 <script>
바로 뒤에 인라인 스크립트 태그에 표시를 추가하여 모든 스크립트의 실행이 완료된 지점을 결정할 수 있습니다.
이 경우 onload
핸들러는 스크립트가 로드된 후 스크립트를 실행해야 하고 시간이 걸리므로 작동하지 않습니다. 빠르게 로드되지만 실행 속도가 느린 스크립트는 느린 로드 스크립트만큼이나 나빠질 수 있습니다.
모든 문서에서 자바스크립트가 로드되고 실행되는 시점을 추적하려면 다음 코드를 사용하여 index.html
의 자바스크립트 섹션을 업데이트합니다.
<!-- 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
디렉터리에 있는 항목과 일치해야 합니다.
브라우저에서 페이지를 로드하고 개발자 콘솔을 열면 다음과 같은 출력이 표시됩니다.
모든 브라우저가 자바스크립트 프로미스 또는 User Timing API를 지원하지는 않으며, 지금까지 작성한 코드를 이 언어 중 하나를 지원하지 않고 브라우저에서 실행하면 오류가 발생합니다.
이 문제를 해결하려면 기능 감지를 사용하면 됩니다. 글꼴 섹션 바로 앞에 다음 코드를 추가합니다. 자바스크립트 줄은 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) {
// ...
}
}
마지막으로 모든 브라우저가 자바스크립트 프로미스를 지원하지 않으므로 measureWebfontPerfAndFailures
함수도 조건부로 래핑해야 합니다.
function measureWebfontPerfAndFailures() {
if (window.Promise) {
// ...
}
}
이제 문제없이 모든 브라우저에서 코드를 실행할 수 있습니다.
이 단계를 완료하면 코드가 Codelab의 05-feature-detects
디렉터리에 있는 항목과 일치해야 합니다.
이 Codelab의 마지막 단계는 콘솔에 기록되는 데이터를 대신 Google 애널리틱스로 보내는 것입니다. Google 애널리틱스로 데이터를 보내려면 먼저 analytics.js 라이브러리와 기본 추적 스니펫을 페이지에 추가해야 합니다.
기본 자바스크립트 블록 뒤, perf-analytics.js
스크립트가 로드되기 전에 index.html
에 다음 코드를 추가합니다.
<!-- 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 애널리틱스를 추가한 경우 Google 애널리틱스에서 새 속성을 만들 때 받은 'UA-XXXXX-Y&placeholder' 자리표시자를 추적 ID로 교체해야 합니다.
analytics.js 추적 스니펫은 4가지 주요 작업을 수행합니다.
- analytics.js 자바스크립트 라이브러리를 다운로드하는 비동기
<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 함수의 반환 문을 업데이트하여 반환 값을 반올림합니다.
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 글꼴을 사용하여 실제 웹사이트에 게재된 글꼴 로드 시간의 예입니다.
수고하셨습니다. 이 Codelab을 완료했습니다. 더 심층적으로 분석하려고 하는 경우 다음 섹션에서 더 많은 통계를 얻기 위해 이 코드 위에 빌드하는 방법을 제안합니다.
이 Codelab에서 다룬 성능 측정항목은 실제 사용자를 위해 사이트가 로드되는 방식을 측정하는 데 필수이지만, 이는 시작에 불과합니다. 실적 분석에 대해 더 자세히 알고 싶다면 간단히 더 많은 측정항목을 추적하는 것이 좋습니다.
이 Codelab에서는 사용자가 리소스를 사용할 수 있는 시점과 관련된 측정항목을 추적했습니다. 원한다면 크게 세분화할 수 있습니다. 예를 들어 자바스크립트의 실행이 끝나는 시점만 측정하는 것이 아니라 로드 시작 시점, 로드 완료 시점, 실행 시작 시점, 실행 완료 시점을 측정할 수 있습니다. 이러한 측정항목을 통해 둘 중 하나만 알 수 없는 문제를 발견할 수 있습니다.
세부적으로 분류하는 것 외에도 일반적인 실적 분석 전략에 대해 더 포괄적으로 생각해야 합니다. 목표가 무엇인가요? 성공이란 무엇일까요?
여러 분석 유형의 경우 먼저 일종의 질문으로 시작한 다음 데이터를 사용하여 이 질문에 답변하는 방법을 찾아야 합니다.
예를 들어 다음 질문 목록을 살펴보고 이 Codelab에서 얻은 지식을 사용해 분석 내 질문에 답변하는 방법을 생각해 보세요.
- 추적 중인 측정항목의 값이 시간이 지남에 따라 감소하거나 증가하나요?
- 서비스 워커 또는 로컬 스토리지를 통한 오프라인 캐싱 사용은 사이트의 전반적인 성능에 어떤 영향을 주나요?
- 리소스가 최적으로 로드되고 있나요? 즉, 리소스가 다운로드되는 시점과 이 리소스를 사용할 수 있는 시점 사이에 큰 차이가 있나요?
- 실적 및 기타 측정항목 (예: 가입률, 사이트에 머문 시간, 구매)과 상관관계가 있나요?
마지막으로 웹 실적 및 Google 애널리틱스에 대해 자세히 알아보고 싶다면 시작하는 데 도움이 되는 다음 리소스를 참조하세요.
- 고성능 웹사이트를 구축하는 데 도움이 되는 도구와 정보
https://developers.google.com/speed/ - Google 애널리틱스 플랫폼을 활용할 수 있는 개발자용 도구 및 API
https://developers.google.com/analytics/ - Google 애널리틱스 제품 자체 사용 방법을 알려주는 온라인 과정입니다. Google 애널리틱스 자체 직원도 학습합니다.
https://analyticsacademy.withgoogle.com/