surfnperf 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c4c7403d39855f590d1dd5e2c2345237274bf602
4
+ data.tar.gz: 6e855ec75d1e8b0635c9e4ab5b6597518609da26
5
+ SHA512:
6
+ metadata.gz: 52b75f11f6a00ba94a4a5e2d246d26860038dd0d9beac2371468a9059394620c794e562c2ff804813897589bd4fb9b9312d300eaa128408b9f61ce54161cb807
7
+ data.tar.gz: a9aba799e9f43869e3a45d0eb82acc8f63c17640db5e6f2c7309f0834d9608a299a40165e5c11e6a2b29370e2da3002c596f288a11e5e77fcec1fb7e557da3bf
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ Surf-N-Perf
2
+ ==============
3
+
4
+ Micro-library for gathering frontend web page performance data.
5
+
6
+ [![Build Status](https://travis-ci.org/Comcast/Surf-N-Perf.svg?branch=master)](https://travis-ci.org/Comcast/Surf-N-Perf)
7
+
8
+ ## Usage
9
+
10
+ ### Including the code in your project
11
+
12
+ There are 2 pieces of code that need to be included in your webpage:
13
+
14
+ **1.** The following code must be included as high up in the source code of your base HTML document as possible, ideally right after the opening ```<head>``` tag:
15
+
16
+ ```html
17
+ <script>
18
+ var SURF_N_PERF = {
19
+ marks: {},
20
+ highResMarks: {}
21
+ };
22
+
23
+ SURF_N_PERF.marks.pageStart = (new Date()).getTime();
24
+
25
+ if(window.performance) {
26
+ if(window.performance.now) {
27
+ SURF_N_PERF.highResMarks.pageStart = window.performance.now();
28
+ }
29
+ if(window.performance.mark) {
30
+ window.performance.mark('pageStart');
31
+ }
32
+ }
33
+
34
+ SURF_N_PERF.setPageLoad = function() {
35
+ SURF_N_PERF.marks.loadEventEnd = (new Date()).getTime();
36
+
37
+ if(window.performance && window.performance.now) {
38
+ SURF_N_PERF.highResMarks.loadEventEnd = window.performance.now();
39
+ }
40
+ };
41
+
42
+ if(window.addEventListener) {
43
+ window.addEventListener('load', SURF_N_PERF.setPageLoad, false);
44
+ } else {
45
+ window.attachEvent('onload', SURF_N_PERF.setPageLoad);
46
+ }
47
+ </script>
48
+ ```
49
+
50
+ That provides support for the following:
51
+ - A "pageStart" mark for browsers that do not support [Navigation Timing](http://www.w3.org/TR/navigation-timing/) which can be used to compute durations from when the page first started loading (specifically, this mark falls between the [domLoading](http://www.w3.org/TR/navigation-timing/#dom-performancetiming-domloading) and [domInteractive](http://www.w3.org/TR/navigation-timing/#dom-performancetiming-dominteractive) attributes of Navigation Timing)
52
+ - "pageStart" marks for browsers that support [High Resolution Time](http://www.w3.org/TR/hr-time/) and/or [User Timing](http://www.w3.org/TR/user-timing/) so that "pageStart" can be used as a consistent starting point for duration calculations across all browsers regardless of their supported features
53
+ - A "loadEventEnd" mark for browsers that do not support [Navigation Timing](http://www.w3.org/TR/navigation-timing/) which can be used to compute durations from when the load event of the document is completed ([loadEventEnd](http://www.w3.org/TR/navigation-timing/#dom-performancetiming-loadend))
54
+ - A "loadEventEnd" [DOMHighResTimeStamp](http://www.w3.org/TR/hr-time/#sec-DOMHighResTimeStamp) mark for calculating high resolution durations between a Navigation Timing mark and a user mark in browsers that support [High Resolution Time](http://www.w3.org/TR/hr-time/) but don't support [User Timing](http://www.w3.org/TR/user-timing/)
55
+
56
+ **2.** Then just drop the [surfnperf.min.js](https://github.com/Comcast/Surf-N-Perf/blob/master/surfnperf.min.js) in your codebase and reference that JavaScript file in your HTML document. If you're using [RequireJS](http://requirejs.org/) or [Browserify](http://browserify.org/), it registers itself as 'surfnperf'.
57
+
58
+ ### Storing & Retrieving Performance Data
59
+
60
+ Details in the [JavaScript API](https://github.com/Comcast/Surf-N-Perf/wiki/JavaScript-API) page in the wiki
61
+
62
+ ## Ruby Project Integration
63
+
64
+ ### Using within a Rails project
65
+
66
+ The [surfnperf Ruby Gem](https://rubygems.org/gems/surfnperf) allows you to quickly & easily integrate Surf-N-Perf into your Rails projects. To include the necessary files, add `surfnperf` to your `Gemfile`:
67
+
68
+ ```ruby
69
+ gem 'surfnperf'
70
+ ```
71
+
72
+ After a `$ bundle install`, you'll be able to include the [main JavaScript file](https://github.com/Comcast/Surf-N-Perf/blob/master/surfnperf.js) in your JavaScript manifest by simply adding:
73
+
74
+ ```
75
+ //= require surfnperf
76
+ ```
77
+
78
+ The necessary script for the ```<head>``` of your HTML document is also available to you via a [partial template](http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials) that you can include in the appropriate layout file for your page, such as `app/views/layouts/application.html.erb` by simply adding this line:
79
+
80
+ ```erb
81
+ <%= render "surfnperf/head" %>
82
+ ```
83
+
84
+ Those 3 lines of code are all your need to get started using Surf-N-Perf in Rails!
85
+
86
+ ### Using within other Ruby projects that integrate with Sprockets
87
+
88
+ [Sprockets](https://github.com/sstephenson/sprockets) is what powers the [Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html) in Rails, as well as other Ruby website tools such as [Middleman](https://middlemanapp.com/). For these other Ruby projects that use [Sprockets](https://middlemanapp.com/advanced/asset_pipeline/), integration is similar to the Rails instructions above, with one extra step:
89
+
90
+ Add `surfnperf` to your `Gemfile`:
91
+
92
+ ```ruby
93
+ gem 'surfnperf'
94
+ ```
95
+
96
+ After a `$ bundle install`, include [surfnperf.js](https://github.com/Comcast/Surf-N-Perf/blob/master/surfnperf.js) in your JavaScript manifest by adding:
97
+
98
+ ```
99
+ //= require surfnperf
100
+ ```
101
+
102
+ For now, you'll have to manually include the [necessary script](#including-the-code-in-your-project) for the ```<head>``` of your HTML document. If someone would like to update the Ruby Gem to work as a proper [Middleman Extension](https://middlemanapp.com/advanced/custom_extensions/), we'd happily accept a [pull request](https://github.com/Comcast/Surf-N-Perf/issues/35).
103
+
104
+ ## Running Tests & Other Development Tools
105
+
106
+ Tests are written in [Jasmine](http://jasmine.github.io/) and run with [Karma](http://karma-runner.github.io/)
107
+
108
+ Install the dependencies by executing this command from the root of your Surf-N-Perf project directory:
109
+
110
+ ```bash
111
+ $ npm install
112
+ ```
113
+
114
+ And then run the tests, JSHint, beautify your code & generate the minified file with:
115
+
116
+ ```bash
117
+ $ grunt dev
118
+ ```
119
+
120
+ By default, it will run the tests using [PhantomJS](http://phantomjs.org/). You can also run the tests in any browser by going to http://localhost:9876/
121
+
122
+ If you encounter an error related to Grunt, it may not be installed, so [go install it](http://gruntjs.com/getting-started).
123
+
124
+ ## License
125
+
126
+ Licensed under the [MIT License](https://github.com/Comcast/Surf-N-Perf/blob/master/LICENSE)
@@ -0,0 +1,31 @@
1
+ <script>
2
+ var SURF_N_PERF = {
3
+ marks: {},
4
+ highResMarks: {}
5
+ };
6
+
7
+ SURF_N_PERF.marks.pageStart = (new Date()).getTime();
8
+
9
+ if(window.performance) {
10
+ if(window.performance.now) {
11
+ SURF_N_PERF.highResMarks.pageStart = window.performance.now();
12
+ }
13
+ if(window.performance.mark) {
14
+ window.performance.mark('pageStart');
15
+ }
16
+ }
17
+
18
+ SURF_N_PERF.setPageLoad = function() {
19
+ SURF_N_PERF.marks.loadEventEnd = (new Date()).getTime();
20
+
21
+ if(window.performance && window.performance.now) {
22
+ SURF_N_PERF.highResMarks.loadEventEnd = window.performance.now();
23
+ }
24
+ };
25
+
26
+ if(window.addEventListener) {
27
+ window.addEventListener('load', SURF_N_PERF.setPageLoad, false);
28
+ } else {
29
+ window.attachEvent('onload', SURF_N_PERF.setPageLoad);
30
+ }
31
+ </script>
data/lib/surfnperf.rb ADDED
@@ -0,0 +1,21 @@
1
+ module SurfNPerf
2
+ class << self
3
+ def load!
4
+ if rails?
5
+ register_rails_engine
6
+ end
7
+ end
8
+
9
+ def rails?
10
+ defined?(::Rails)
11
+ end
12
+
13
+ private
14
+
15
+ def register_rails_engine
16
+ require 'surfnperf/rails'
17
+ end
18
+ end
19
+ end
20
+
21
+ SurfNPerf.load!
@@ -0,0 +1,6 @@
1
+ module SurfNPerf
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,361 @@
1
+ /*!
2
+ * Copyright 2015 Comcast Cable Communications Management, LLC
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ (function(root, factory) {
24
+ if(typeof define === 'function' && define.amd) {
25
+ // For Require.js
26
+ define('surfnperf', factory);
27
+ } else if(typeof exports === 'object') {
28
+ // For Browserify
29
+ module.exports = factory(require('surfnperf'));
30
+ } else {
31
+ // Browser global if not using Require.js or Browserify
32
+ root.surfnperf = factory();
33
+ }
34
+ }(this, function() {
35
+
36
+ /**
37
+ * Date.now() shim for older browsers
38
+ */
39
+ if(!Date.now) {
40
+ Date.now = function now() {
41
+ return new Date().getTime();
42
+ };
43
+ }
44
+
45
+ var defaults = function(o, d) {
46
+ for(var prop in d) {
47
+ if(!o.hasOwnProperty(prop)) {
48
+ o[prop] = d[prop];
49
+ }
50
+ }
51
+ },
52
+ contains = function(array, value) {
53
+ if(Array.prototype.indexOf) {
54
+ return array.indexOf(value) != -1;
55
+ } else {
56
+ var i, length = array.length;
57
+ for(i = 0; i < length; i++) {
58
+ if(array[i] === value) {
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Frontend Web Performance Data Gathering
68
+ *
69
+ * @class SurfNPerf
70
+ */
71
+ var SurfNPerf = function() {
72
+ this._data = {
73
+ custom: {},
74
+ marks: {},
75
+ highResMarks: {},
76
+ events: {}
77
+ };
78
+
79
+ this._navigationTiming = null;
80
+ this._highResTime = null;
81
+ this._userTiming = null;
82
+
83
+ this._navigationTimingEvents = {
84
+ a: ["navigationStart", "unloadEventEnd", "unloadEventStart", "redirectStart", "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", "connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart", "responseEnd", "domLoading"],
85
+ b: ["domInteractive", "domContentLoadedEventStart", "domContentLoadedEventEnd", "domComplete", "loadEventStart", "loadEventEnd"]
86
+ };
87
+
88
+ this.initialize();
89
+ },
90
+ SNPProto = SurfNPerf.prototype;
91
+
92
+ SNPProto._setPerformanceApis = function() {
93
+ if(window.performance) {
94
+ this._navigationTiming = !!(window.performance.timing);
95
+ this._highResTime = !!(window.performance.now);
96
+ this._userTiming = !!(window.performance.mark && window.performance.measure && window.performance.getEntriesByName);
97
+ } else {
98
+ this._navigationTiming = false;
99
+ this._highResTime = false;
100
+ this._userTiming = false;
101
+ }
102
+ };
103
+
104
+ SNPProto._setPerfProperties = function() {
105
+ if(!window.SURF_N_PERF || !window.SURF_N_PERF.marks) {
106
+ window.SURF_N_PERF = {
107
+ marks: {},
108
+ highResMarks: {}
109
+ };
110
+ }
111
+ };
112
+
113
+ SNPProto._setInitialUrl = function() {
114
+ this.setCustom('initialUrl', window.location.pathname);
115
+ };
116
+
117
+ SNPProto.initialize = function() {
118
+ this._setPerformanceApis();
119
+ this._setPerfProperties();
120
+ this._setInitialUrl();
121
+ };
122
+
123
+ /**
124
+ * Returns the timing data for a particular eventKey
125
+ *
126
+ * @arguments {String} timeType 'highRes' (to return a DOMHighResTimeStamp, if available) or 'DOM' (to return a DOMTimeStamp's value) - optional. Defaults to 'highRes'
127
+ * @returns {DOMHighResTimeStamp | integer} time value
128
+ * @memberOf SurfNPerf
129
+ */
130
+ SNPProto.now = function(timeType) {
131
+ timeType = timeType || 'highRes';
132
+ if(this._highResTime && timeType === 'highRes') {
133
+ return window.performance.now();
134
+ } else {
135
+ return Date.now();
136
+ }
137
+ };
138
+
139
+ SNPProto.performanceTiming = function() {
140
+ return this._navigationTiming ? window.performance.timing : {};
141
+ };
142
+
143
+ SNPProto._performanceTimingL2 = function(eventKey) {
144
+ var delta = this.getTimingMark('loadEventEnd', 'DOM') - this.getTimingMark(eventKey, 'DOM'),
145
+ value = window.SURF_N_PERF.highResMarks.loadEventEnd - delta;
146
+ return(value < 0) ? 0 : this._round(value, {
147
+ decimalPlaces: 10
148
+ });
149
+ };
150
+
151
+ /**
152
+ * Returns the timing data for a particular eventKey
153
+ *
154
+ * @arguments {String} eventKey name of the timing event
155
+ * @arguments {String} timeType 'highRes' (to return a DOMHighResTimeStamp, if available) or 'DOM' (to return a DOMTimeStamp's value) - optional. Defaults to 'DOM'
156
+ * @returns {DOMHighResTimeStamp | integer} time value
157
+ * @memberOf SurfNPerf
158
+ */
159
+ SNPProto.getTimingMark = function(eventKey, timeType) {
160
+ timeType = timeType || 'DOM';
161
+
162
+ if(this._navigationTiming) {
163
+ if(timeType === 'DOM' || this._highResTime === false) {
164
+ return this.performanceTiming()[eventKey];
165
+ } else { // timeType === 'HighRes'
166
+ return this._performanceTimingL2(eventKey);
167
+ }
168
+ } else {
169
+ if(contains(this._navigationTimingEvents.a, eventKey)) {
170
+ return this.getMark('pageStart', 'DOM');
171
+ } else {
172
+ return this.getMark('loadEventEnd', 'DOM');
173
+ }
174
+ }
175
+ };
176
+
177
+ SNPProto.userTiming = function() {
178
+ return this._userTiming ? window.performance : {};
179
+ };
180
+
181
+ SNPProto.mark = function(eventKey) {
182
+ if(this._highResTime) {
183
+ this._data.highResMarks[eventKey] = this.now();
184
+ }
185
+ if(this._userTiming) {
186
+ this.userTiming().mark(eventKey);
187
+ }
188
+ this._data.marks[eventKey] = this.now('DOM');
189
+ };
190
+
191
+ SNPProto.getMark = function(eventKey, timeType) {
192
+ var mark;
193
+
194
+ timeType = timeType || 'highRes';
195
+
196
+ if(timeType === 'highRes' && this._highResTime === true) {
197
+ mark = this._data.highResMarks[eventKey] || window.SURF_N_PERF.highResMarks[eventKey];
198
+ }
199
+ return mark || this._data.marks[eventKey] || window.SURF_N_PERF.marks[eventKey];
200
+ };
201
+
202
+ SNPProto._isTimingMark = function(eventKey) {
203
+ return contains(this._navigationTimingEvents.a.concat(this._navigationTimingEvents.b), eventKey);
204
+ };
205
+
206
+ SNPProto._getDurationMark = function(eventKey) {
207
+ if(this._isTimingMark(eventKey)) {
208
+ return this.getTimingMark(eventKey, 'highRes');
209
+ } else {
210
+ return this.getMark(eventKey);
211
+ }
212
+ };
213
+
214
+ SNPProto._round = function(n, options) {
215
+ options = options || {};
216
+ var dp = options.decimalPlaces || 0;
217
+ n = +(n);
218
+ return n.toFixed ? +(n).toFixed(dp) : n;
219
+ };
220
+
221
+ SNPProto._roundedDuration = function(a, b, options) {
222
+ return this._round(b - a, options);
223
+ };
224
+
225
+ SNPProto._measureName = function(a, b) {
226
+ return '_SNP_' + a + '_TO_' + b;
227
+ };
228
+
229
+ SNPProto._setMeasure = function(a, b) {
230
+ try {
231
+ this.userTiming().measure(this._measureName(a, b), a, b);
232
+ } catch(e) {
233
+ if(window.console && window.console.error) {
234
+ if(e && e.message) {
235
+ console.error("Surf-N-Perf Exception:", e.message);
236
+ } else {
237
+ console.error("Surf-N-Perf Exception: at least one of these events/marks is not available yet", a, b);
238
+ }
239
+ }
240
+ }
241
+ };
242
+
243
+ SNPProto._getMeasureDuration = function(a, b) {
244
+ var measure = this.userTiming().getEntriesByName(this._measureName(a, b))[0] || {};
245
+ return measure.duration;
246
+ };
247
+
248
+ SNPProto.duration = function(a, b, options) {
249
+ if(this._userTiming) {
250
+ this._setMeasure(a, b);
251
+ return this._round(this._getMeasureDuration(a, b), options);
252
+ } else if(this._highResTime && a === 'navigationStart' && !this._isTimingMark(b)) {
253
+ return this._round(this.getMark(b), options);
254
+ } else {
255
+ return this._roundedDuration(this._getDurationMark(a), this._getDurationMark(b), options);
256
+ }
257
+ };
258
+
259
+ SNPProto.updateEvent = function(eventKey, key, value) {
260
+ var obj = {};
261
+ obj[eventKey] = {};
262
+
263
+ defaults(this._data.events, obj);
264
+
265
+ this._data.events[eventKey][key] = value;
266
+ };
267
+
268
+ SNPProto.resetEvent = function(eventKey, key, value) {
269
+ this._data.events[eventKey] = {};
270
+ this._data.events[eventKey][key] = value;
271
+ };
272
+
273
+ SNPProto.eventStart = function(eventKey) {
274
+ this.resetEvent(eventKey, 'start', this.now());
275
+ };
276
+
277
+ SNPProto.eventEnd = function(eventKey, options) {
278
+ var now = this.now(),
279
+ key;
280
+
281
+ options = options || {};
282
+
283
+ for(key in options) {
284
+ this.updateEvent(eventKey, key, options[key]);
285
+ }
286
+
287
+ this.updateEvent(eventKey, 'end', now);
288
+ };
289
+
290
+ SNPProto.getEventData = function(eventKey, key) {
291
+ var eventObj = this._data.events[eventKey];
292
+ if(eventObj) {
293
+ return eventObj[key];
294
+ }
295
+ };
296
+
297
+ SNPProto.eventDuration = function(eventKey, options) {
298
+ return this._roundedDuration(this.getEventData(eventKey, 'start'), this.getEventData(eventKey, 'end'), options);
299
+ };
300
+
301
+ SNPProto.setCustom = function(key, value) {
302
+ this._data.custom[key] = value;
303
+ };
304
+
305
+ SNPProto.getCustom = function(key) {
306
+ return this._data.custom[key];
307
+ };
308
+
309
+ /**
310
+ * Total time for App Cache, DNS & TCP
311
+ *
312
+ * @returns {integer} time in ms
313
+ * @memberOf SurfNPerf
314
+ */
315
+ SNPProto.getNetworkTime = function() {
316
+ return this.duration('fetchStart', 'connectEnd');
317
+ };
318
+
319
+ /**
320
+ * Total time for Request & Response
321
+ *
322
+ * @returns {integer} time in ms
323
+ * @memberOf SurfNPerf
324
+ */
325
+ SNPProto.getServerTime = function() {
326
+ return this.duration('requestStart', 'responseEnd');
327
+ };
328
+
329
+ /**
330
+ * Total time for App Cache, DNS, TCP, Request & Response
331
+ *
332
+ * @returns {integer} time in ms
333
+ * @memberOf SurfNPerf
334
+ */
335
+ SNPProto.getNetworkLatency = function() {
336
+ return this.duration('fetchStart', 'responseEnd');
337
+ };
338
+
339
+ /**
340
+ * Total time to process the Response & fire the onLoad event
341
+ *
342
+ * @returns {integer} time in ms
343
+ * @memberOf SurfNPerf
344
+ */
345
+ SNPProto.getProcessingLoadTime = function() {
346
+ return this.duration('responseEnd', 'loadEventEnd');
347
+ };
348
+
349
+ /**
350
+ * Total time for a page to load from the time the user initiates the access of the page to the firing of the onLoad event
351
+ *
352
+ * @returns {integer} time in ms
353
+ * @memberOf SurfNPerf
354
+ */
355
+ SNPProto.getFullRequestLoadTime = function() {
356
+ return this.duration('navigationStart', 'loadEventEnd');
357
+ };
358
+
359
+ return new SurfNPerf();
360
+
361
+ }));
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: surfnperf
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - John Riviello
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Micro-library for gathering frontend web page performance data
42
+ email:
43
+ - john_riviello@comcast.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - app/views/surfnperf/_head.html.erb
50
+ - lib/surfnperf.rb
51
+ - lib/surfnperf/rails.rb
52
+ - vendor/assets/javascripts/surfnperf.js
53
+ homepage: https://github.com/Comcast/Surf-N-Perf
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.4.3
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Micro-library for gathering frontend web page performance data
77
+ test_files: []