Skip to content

Commit 91f4be9

Browse files
committed
Add a test to measure the overhead of bypassing requests to SW
As discussion[1] shows, handling a request via SW having no fetch handler causes degradation of performance comparing to bypassing SW. This new test is created to track this issue (related to an issue[2]). [1]: w3c/ServiceWorker#718 [2]: http://crbug.com/605844
1 parent 0cc35c2 commit 91f4be9

File tree

4 files changed

+187
-0
lines changed

4 files changed

+187
-0
lines changed

blank.html

Whitespace-only changes.

empty-worker.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// empty

nofetch.html

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<!DOCTYPE html>
2+
<title>Service Worker Performance Tests</title>
3+
<script>
4+
var FetchLatencyTester = function(options) {
5+
var nofityResultCallback = function() {};
6+
var targetNumRequests = options.numRequests || 400;
7+
var targetNumConcurrentRequests = options.numConcurrentRequests || 1;
8+
var numTimedRequestsSent = 0;
9+
var numTimedRequestsFinished = 0;
10+
var responseTimes = [];
11+
var targetUrl = options.targetUrl;
12+
var testPrefix = options.testPrefix || targetUrl;
13+
14+
function sendXhr(xhr) {
15+
xhr.timeout = 500;
16+
xhr.open('GET', targetUrl, true);
17+
xhr.setRequestHeader('Pragma', 'no-cache');
18+
xhr.setRequestHeader('Cache-Control', 'no-cache');
19+
xhr.send();
20+
}
21+
22+
// Used when ramping the load up and down.
23+
function rampRequest() {
24+
var xhr = new XMLHttpRequest();
25+
xhr.addEventListener('loadend', next, false);
26+
sendXhr(xhr);
27+
}
28+
29+
function timedRequest() {
30+
var xhr = new XMLHttpRequest();
31+
32+
xhr.addEventListener('load', timedRequestCompleted, false);
33+
xhr.addEventListener('error', timedRequestFailed, false);
34+
xhr.addEventListener('abort', timedRequestFailed, false);
35+
xhr.addEventListener('timeout', timedRequestFailed, false);
36+
37+
xhr.startTime = performance.now();
38+
sendXhr(xhr);
39+
numTimedRequestsSent++;
40+
}
41+
42+
function timedRequestCompleted(event) {
43+
var endTime = performance.now();
44+
responseTimes.push(endTime - event.target.startTime);
45+
46+
numTimedRequestsFinished++;
47+
next();
48+
}
49+
50+
function timedRequestFailed(event) {
51+
numTimedRequestsFinished++;
52+
next();
53+
}
54+
55+
function next() {
56+
// Ramp-down requests finishing.
57+
58+
// All timed requests have finished.
59+
if (numTimedRequestsFinished == targetNumRequests) {
60+
notifyResultCallback(computeResults());
61+
return;
62+
}
63+
64+
// All timed requests have started, ramping down.
65+
if (numTimedRequestsSent == targetNumRequests) {
66+
rampRequest();
67+
return;
68+
}
69+
70+
// Still generating timed requests.
71+
timedRequest();
72+
}
73+
74+
function computeResults() {
75+
var results = {};
76+
results['nofetch_' + testPrefix + '_' + targetNumConcurrentRequests + '_failed_requests'] = {
77+
value: numTimedRequestsSent - responseTimes.length,
78+
units: 'requests'
79+
};
80+
responseTimes.sort();
81+
reportPercentile(50);
82+
reportPercentile(90);
83+
reportPercentile(99);
84+
return results
85+
86+
// Gets the n-th percentile response time, or ten seconds if that response did
87+
// not succeed.
88+
function percentile(n) {
89+
var i = Math.floor(n * numTimedRequestsSent / 100);
90+
if (i >= responseTimes.length) {
91+
return 10 * 1000;
92+
}
93+
return responseTimes[i];
94+
}
95+
96+
function reportPercentile(n) {
97+
results['nofetch_' + testPrefix + '_' + targetNumConcurrentRequests + '_response_' + n + '_percentile'] = {
98+
value: percentile(n),
99+
units: 'ms'
100+
};
101+
}
102+
}
103+
104+
this.run = function() {
105+
return new Promise(function(resolve) {
106+
notifyResultCallback = resolve;
107+
for (var i = 0; i < targetNumConcurrentRequests - 1; i++) {
108+
rampRequest();
109+
}
110+
timedRequest();
111+
});
112+
};
113+
};
114+
115+
</script>
116+
<body>
117+
<script>
118+
function waitUntilActive(registration) {
119+
return new Promise(function(resolve, reject) {
120+
var serviceWorker;
121+
if (registration.active) {
122+
resolve(registration.active);
123+
} else if (registration.waiting) {
124+
serviceWorker = registration.waiting;
125+
serviceWorker.onstatechange = checkState;
126+
} else {
127+
serviceWorker = registration.installing;
128+
serviceWorker.onstatechange = checkState;
129+
}
130+
131+
function checkState() {
132+
if (serviceWorker.state == 'activated') {
133+
serviceWorker.removeEventListener('statechange', checkState);
134+
resolve(serviceWorker);
135+
}
136+
}
137+
});
138+
}
139+
140+
function addResults(partial) {
141+
for (key in partial) {
142+
if (partial.hasOwnProperty(key)) {
143+
results[key] = partial[key];
144+
}
145+
}
146+
}
147+
148+
function cleanUp() {
149+
return navigator.serviceWorker.getRegistration(workerOptions['scope'])
150+
.then(function(r) {
151+
return r.unregister();
152+
});
153+
}
154+
155+
function complete() {
156+
console.log('complete');
157+
console.log(results);
158+
done = true;
159+
}
160+
161+
done = false;
162+
results = { };
163+
164+
var workerScriptPath = 'empty-worker.js';
165+
var workerOptions = {scope: '/nofetch/'};
166+
var outOfScopeTester = new FetchLatencyTester({
167+
numRequests: 400,
168+
numConcurrentRequests: 1,
169+
targetUrl: 'blank.html',
170+
testPrefix: 'outofscope'});
171+
var inScopeTester = new FetchLatencyTester({
172+
numRequests: 400,
173+
numConcurrentRequests: 1,
174+
targetUrl: 'nofetch/blank.html',
175+
testPrefix: 'inscope'});
176+
177+
178+
navigator.serviceWorker.register(workerScriptPath, workerOptions)
179+
.then(waitUntilActive)
180+
.then(outOfScopeTester.run)
181+
.then(addResults)
182+
.then(inScopeTester.run)
183+
.then(addResults)
184+
.then(cleanUp)
185+
.then(complete);
186+
</script>

nofetch/blank.html

Whitespace-only changes.

0 commit comments

Comments
 (0)