` |
50 |
51 | ## Advanced Usage
52 | In order to use the non-angular page load metrics, the Performance API is required in your entry point. As such, this directive is really indended for SPA's or applications with some base layout file that is included on every page. In short, collection of several attribute value 'times' are required throughout your entry point (usually index.html). Needed variables are set by using simple Performance. Now offsets and then passed through to the directive via the $window object. See the Page Load Demo for examples of how to use the Page Load metrics.
--------------------------------------------------------------------------------
/ngPerformance.html:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
ANGULAR STATS
26 |
27 |
28 |
29 | Total Scopes: |
30 | - |
31 |
32 |
33 | Total Watchers: |
34 | - |
35 |
36 |
37 | Dirty Checks: |
38 | - |
39 |
40 |
41 | Digest Cycles: |
42 | - |
43 |
44 |
45 | Digest Cycle (last): |
46 |
47 | - ms
48 | - FPS
49 | |
50 |
51 |
52 | Digest Cycle (avg): |
53 |
54 | - ms
55 | - FPS
56 | |
57 |
58 |
59 | Digest Cycle (max): |
60 |
61 | - ms
62 | - FPS
63 | |
64 |
65 |
66 |
67 |
68 |
PAGELOAD STATS
69 |
70 |
71 |
72 | Head Load: |
73 | - ms |
74 |
75 |
76 | Body Load: |
77 | - ms |
78 |
79 |
80 | Footer Load: |
81 | ms |
82 |
83 |
84 | Vendor Script Load: |
85 | - ms |
86 |
87 |
88 | App Load: |
89 | - ms |
90 |
91 |
92 | Metrics Load: |
93 | - ms |
94 |
95 |
96 | Time To End-of-Page: |
97 | - ms |
98 |
99 |
100 | Time To Angular: |
101 | - ms |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/ngPerformance.js:
--------------------------------------------------------------------------------
1 | (function (angular) {
2 |
3 | 'use strict';
4 |
5 | /**
6 | * @ngdoc directive
7 | * @name ngPerformanceApp.directive:ngPerformance
8 | * @description
9 | * # ngPerformance
10 | */
11 |
12 | var _el;
13 |
14 | function _setText(id, text) {
15 | if (_el[id]) {
16 | _el[id].textContent = text;
17 | }
18 | }
19 |
20 | var ngPerformanceModule = angular.module('blndspt.ngPerformance', []);
21 |
22 | ngPerformanceModule.directive('ngPerformance', [
23 | '$log',
24 | '$window',
25 | '$document',
26 | '$rootScope',
27 | function ($log, $window, $document, $rootScope) {
28 |
29 | //Initialize performance stats variables
30 | var ngStart = (performance != null) ? performance.now() : 0;
31 | var stats = ($window.perfStats) ? $window.perfStats :
32 | {
33 | TTLB: 0,
34 | appLoad: 0,
35 | bodyLoad: 0,
36 | footerLoad: 0,
37 | headLoad: 0,
38 | headStart: 0,
39 | metricsLoad: 0,
40 | timeToAngular: 0,
41 | vendorScriptLoad: 0
42 | };
43 |
44 | stats.timeToAngular = (ngStart - stats.headStart);
45 |
46 | //Count Scopes and Watchers
47 | var _countScopesWatchers = function () {
48 | // This logic is borrowed from $digest(). Keep it in sync!
49 | var next, current, target = $rootScope;
50 | var scopes = 0,
51 | watchers = 0;
52 |
53 | current = target;
54 | do {
55 | scopes += 1;
56 |
57 | if (current.$$watchers) {
58 | watchers += current.$$watchers.length;
59 | }
60 |
61 | // Insanity Warning: scope depth-first traversal
62 | // yes, this code is a bit crazy, but it works and we have tests to prove it!
63 | // this piece should be kept in sync with the traversal in $broadcast
64 | if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
65 | while (current !== target && !(next = current.$$nextSibling)) {
66 | current = current.$parent;
67 | }
68 | }
69 | } while ((current = next));
70 |
71 | return [scopes, watchers];
72 | };
73 |
74 | return {
75 | templateUrl: 'views/ngPerformance.html',
76 | restrict: 'EA',
77 | link: function (/*scope, element, attrs*/) {
78 |
79 | // Cache DOM elements
80 | _el = {
81 | '#scopes': $document[0].querySelector('#scopes'),
82 | '#watchers': $document[0].querySelector('#watchers'),
83 | '#dirty-checks': $document[0].querySelector('#dirty-checks'),
84 | '#digest-cycles': $document[0].querySelector('#digest-cycles'),
85 | '#digest-ms': $document[0].querySelector('#digest-ms'),
86 | '#digest-fps': $document[0].querySelector('#digest-fps'),
87 | '#avg-digest-ms': $document[0].querySelector('#avg-digest-ms'),
88 | '#avg-digest-fps': $document[0].querySelector('#avg-digest-fps'),
89 | '#max-digest-ms': $document[0].querySelector('#max-digest-ms'),
90 | '#max-digest-fps': $document[0].querySelector('#max-digest-fps'),
91 |
92 | '#head-load': $document[0].querySelector('#head-load'),
93 | '#body-load': $document[0].querySelector('#body-load'),
94 | '#footer-load': $document[0].querySelector('#footer-load'),
95 | '#vendor-load': $document[0].querySelector('#vendor-load'),
96 | '#app-load': $document[0].querySelector('#app-load'),
97 | '#metrics-load': $document[0].querySelector('#metrics-load'),
98 | '#time-to-eop': $document[0].querySelector('#time-to-eop'),
99 | '#time-to-ng': $document[0].querySelector('#time-to-ng'),
100 | };
101 |
102 | _setText('#head-load', stats.headLoad.toFixed(1));
103 | _setText('#body-load', stats.bodyLoad.toFixed(1));
104 | _setText('#footer-load', stats.footerLoad.toFixed(1));
105 | _setText('#vendor-load', stats.vendorScriptLoad.toFixed(1));
106 | _setText('#app-load', stats.appLoad.toFixed(1));
107 | _setText('#metrics-load', stats.metricsLoad.toFixed(1));
108 | _setText('#time-to-eop', stats.TTLB.toFixed(1));
109 | _setText('#time-to-ng', stats.timeToAngular.toFixed(1));
110 |
111 |
112 | // If the browser doesn't support Web Performance API
113 | // (I'm looking at you, Safari), don't even try.
114 | if (performance != null) {
115 | var digestCycles = 0,
116 | digestStart = 0,
117 | sumDigestMs = 0,
118 | maxDigestMs = 0,
119 | dirtyChecks = 0;
120 |
121 | // $digest loop uses a reverse while.
122 | // Pushing onto the end of $$watchers array makes this run first...
123 | $rootScope.$$watchers.push({
124 | eq: false,
125 | last: null,
126 | fn: function () {
127 | },
128 | exp: function () {
129 | },
130 | get: function () {
131 | dirtyChecks++;
132 |
133 | // Only update digestStart if not set. This allows for multiple
134 | // iterations inside the "dirty loop."
135 | //
136 | // NOTE: This technique for timing the $digest cycles
137 | // DOES NOT capture time spent processing the asyncQueue!
138 | if (digestStart === 0) {
139 | // $log.debug('$rootScope.$watch: digestStart');
140 | digestStart = performance.now();
141 | digestCycles++;
142 | }
143 |
144 | // Schedules a one-shot callback after digest loop is clean
145 | $rootScope.$$postDigest(function () {
146 | if (digestStart !== 0) {
147 | var digestEnd = performance.now();
148 | var digestMs = (digestEnd - digestStart);
149 | _setText('#digest-ms', digestMs.toFixed(1));
150 | _setText('#digest-fps', (1000 / digestMs).toFixed(0));
151 |
152 | maxDigestMs = Math.max(digestMs, maxDigestMs);
153 | _setText('#max-digest-ms', maxDigestMs.toFixed(1));
154 | _setText('#max-digest-fps', (1000 / maxDigestMs).toFixed(0));
155 |
156 | sumDigestMs += digestMs;
157 | if (digestCycles > 0) {
158 | var avgDigestMs = sumDigestMs / digestCycles;
159 | _setText('#avg-digest-ms', avgDigestMs.toFixed(1));
160 | _setText('#avg-digest-fps', (1000 / avgDigestMs).toFixed(0));
161 | }
162 |
163 | _setText('#dirty-checks', dirtyChecks);
164 | _setText('#digest-cycles', digestCycles);
165 |
166 | var count = _countScopesWatchers();
167 | var scopes = count[0],
168 | watchers = count[1];
169 |
170 | _setText('#scopes', scopes);
171 | _setText('#watchers', watchers);
172 |
173 | var log = 'NG-PERF: Digest Cycle #' + digestCycles + ': ' + digestMs.toFixed(1) + ' ms, ' +
174 | 'Scopes: ' + scopes + ', Watchers: ' + watchers +
175 | ' [Overhead: ' + (performance.now() - digestEnd).toPrecision(3) + ' ms]';
176 | $log.debug(log);
177 | if ($window.console.timeStamp) {
178 | $window.console.timeStamp(log);
179 | }
180 |
181 | // Register an async function to run first.
182 | //
183 | // NOTE: This technique for timing the $digest cycles
184 | // DOES capture time spent processing the asyncQueue!
185 | // $rootScope.$$asyncQueue.unshift({
186 | // scope: $rootScope,
187 | // expression: function (scope) {
188 | // // $log.debug('$rootScope.$evalAsync: digestStart');
189 | // digestStart = performance.now();
190 | // digestCycles++;
191 | // }
192 | // });
193 |
194 | // Clear digestStart for next "dirty loop."
195 | digestStart = 0;
196 | }
197 | });
198 |
199 | return null;
200 | }
201 | });
202 | }
203 |
204 | }
205 |
206 | };
207 | }]);
208 |
209 | })
210 |
211 | (angular);
212 |
--------------------------------------------------------------------------------