From 23a2fb82c959852010e40ea6d78e7c8e8927a032 Mon Sep 17 00:00:00 2001 From: omrilotan Date: Mon, 1 Mar 2021 13:31:50 +0200 Subject: [PATCH 1/3] Add asset hitrate --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 4 ++++ package.json | 2 +- src/assets/index.js | 22 ++++++++++++++++++++++ src/assets/spec.js | 20 ++++++++++++-------- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4a1d0..21b6cb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# 3.2.0 + +## New features + +### Add assets browser cache hitrate +Add `final_asset_javascript_hitrate`, `final_asset_javascript_hitrate`, `final_asset_images_hitrate`, `final_asset_other_hitrate`. Measure 0 - 1 + +# 3.1.1 + +## Fixes + +### Add some backwards compatibility for older browsers +Remove use of Array.flat, and supply a fallback for Number.isNaN and Number.isFinite + +# 3.1.0 + +## New features + +### Add redirect_count to navigation metrics +Add `redirect_count` to navigation metrics: Numbers of redirects while requesting this page. + # 3.0.1 ## Fixes diff --git a/README.md b/README.md index 595fa33..eaa51f6 100644 --- a/README.md +++ b/README.md @@ -75,15 +75,19 @@ import { navigation, paint } from 'page-timing'; | final_asset_javascript_count | Total **number** of Javascript resources | **assets** | _number_ | final_asset_javascript_load | Loading **time spent** on Javascript resources | **assets** | _number_ | final_asset_javascript_size | Total **size** of Javascript resources | **assets** | _number_ +| final_asset_javascript_hitrate | Browser cache hit rate (0-1) for Javascript resources | **assets** | _number_ | final_asset_stylesheets_count | Total **number** of CSS resources | **assets** | _number_ | final_asset_stylesheets_load | Loading **time spent** on CSS resources | **assets** | _number_ | final_asset_stylesheets_size | Total **size** of CSS resources | **assets** | _number_ +| final_asset_stylesheets_hitrate | Browser cache hit rate (0-1) for CSS resources | **assets** | _number_ | final_asset_images_count | Total **number** of image resources | **assets** | _number_ | final_asset_images_load | Loading **time spent** on image resources | **assets** | _number_ | final_asset_images_size | Total **size** of image resources | **assets** | _number_ +| final_asset_images_hitrate | Browser cache hit rate (0-1) for image resources | **assets** | _number_ | final_asset_other_count | Total **number** of other resources | **assets** | _number_ | final_asset_other_load | Loading **time spent** on other resources | **assets** | _number_ | final_asset_other_size | Total **size** of other resources | **assets** | _number_ +| final_asset_other_hitrate | Browser cache hit rate (0-1) for other resources | **assets** | _number_ | connection_type | bluetooth, cellular, ethernet, none, wifi, wimax, other, unknown | **connection** | _string_ | effective_bandwidth | Mbps | **connection** | _number_ | effective_connection_type | slow-2g, 2g, 3g, 4g | **connection** | _string_ diff --git a/package.json b/package.json index c2e79e5..0490c68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "page-timing", - "version": "3.1.1", + "version": "3.2.0", "description": "⏱ Collect and measure browser performance metrics", "keywords": [ "browser", diff --git a/src/assets/index.js b/src/assets/index.js index ba01831..ce44269 100644 --- a/src/assets/index.js +++ b/src/assets/index.js @@ -13,6 +13,7 @@ export async function assets() { } const metrics = {}; + const caches = {}; const entries = await getEntries('resource'); for (const entry of entries) { @@ -20,6 +21,11 @@ export async function assets() { add(metrics, type, 'count', 1); add(metrics, type, 'load', entry.duration); add(metrics, type, 'size', entry.decodedBodySize); + cache(caches, type, entry.duration); + } + + for (const type in caches) { + add(metrics, type, 'hitrate', caches[type].hit / caches[type].total); } for (const key in metrics) { @@ -45,3 +51,19 @@ function add(accumulator, type, key, value) { accumulator[field] = accumulator[field] || 0; accumulator[field] += number(value) || 0; } + +/** + * Mutating + * @param {object} accumulator + * @param {string} type + * @param {number} duration (0 for cache hit and more for cache miss) + * @returns {void} + */ +function cache(accumulator, type, duration) { + accumulator[type] = accumulator[type] || {}; + accumulator[type].total = accumulator[type].total || 0; + accumulator[type].hit = accumulator[type].hit || 0; + + accumulator[type].total++; + accumulator[type].hit += +!duration; +} diff --git a/src/assets/spec.js b/src/assets/spec.js index aef7478..055e8c0 100644 --- a/src/assets/spec.js +++ b/src/assets/spec.js @@ -2,25 +2,29 @@ import { getEntriesByTypeMock } from '../../spec-helpers/index.js'; import { assets } from './index.js'; const { getEntriesByType } = window.performance; +const PREFIX = 'final_asset'; +const types = [ 'images', 'javascript', 'stylesheets', 'other' ]; +const metrics = [ 'count', 'load', 'size', 'hitrate' ]; +let data; describe('assets', () => { - before(() => { + before(async() => { window.performance.getEntriesByType = getEntriesByTypeMock; + data = await assets(); + console.log(data); }); after(() => { window.performance.getEntriesByType = getEntriesByType; }); - [ - 'final_asset_other_count', 'final_asset_other_load', 'final_asset_other_size', - 'final_asset_stylesheets_count', 'final_asset_stylesheets_load', 'final_asset_stylesheets_size', - 'final_asset_javascript_count', 'final_asset_javascript_load', 'final_asset_javascript_size', - 'final_asset_images_count', 'final_asset_images_load', 'final_asset_images_size' - ].forEach( + types.map( + (type) => metrics.map( + (metric) => [ PREFIX, type, metric ].join('_') + ) + ).flat().forEach( (event) => it( event, async() => { - const data = await assets(); expect(data).to.have.property(event); expect(data[event]).to.be.a('number'); } From 30d718f64084450f0ba8703b1c4e0a051cf08e06 Mon Sep 17 00:00:00 2001 From: omrilotan Date: Tue, 2 Mar 2021 21:23:38 +0200 Subject: [PATCH 2/3] Revert "Add asset hitrate" This reverts commit 23a2fb82c959852010e40ea6d78e7c8e8927a032. --- CHANGELOG.md | 21 --------------------- README.md | 4 ---- package.json | 2 +- src/assets/index.js | 22 ---------------------- src/assets/spec.js | 20 ++++++++------------ 5 files changed, 9 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b6cb7..7c4a1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,3 @@ -# 3.2.0 - -## New features - -### Add assets browser cache hitrate -Add `final_asset_javascript_hitrate`, `final_asset_javascript_hitrate`, `final_asset_images_hitrate`, `final_asset_other_hitrate`. Measure 0 - 1 - -# 3.1.1 - -## Fixes - -### Add some backwards compatibility for older browsers -Remove use of Array.flat, and supply a fallback for Number.isNaN and Number.isFinite - -# 3.1.0 - -## New features - -### Add redirect_count to navigation metrics -Add `redirect_count` to navigation metrics: Numbers of redirects while requesting this page. - # 3.0.1 ## Fixes diff --git a/README.md b/README.md index eaa51f6..595fa33 100644 --- a/README.md +++ b/README.md @@ -75,19 +75,15 @@ import { navigation, paint } from 'page-timing'; | final_asset_javascript_count | Total **number** of Javascript resources | **assets** | _number_ | final_asset_javascript_load | Loading **time spent** on Javascript resources | **assets** | _number_ | final_asset_javascript_size | Total **size** of Javascript resources | **assets** | _number_ -| final_asset_javascript_hitrate | Browser cache hit rate (0-1) for Javascript resources | **assets** | _number_ | final_asset_stylesheets_count | Total **number** of CSS resources | **assets** | _number_ | final_asset_stylesheets_load | Loading **time spent** on CSS resources | **assets** | _number_ | final_asset_stylesheets_size | Total **size** of CSS resources | **assets** | _number_ -| final_asset_stylesheets_hitrate | Browser cache hit rate (0-1) for CSS resources | **assets** | _number_ | final_asset_images_count | Total **number** of image resources | **assets** | _number_ | final_asset_images_load | Loading **time spent** on image resources | **assets** | _number_ | final_asset_images_size | Total **size** of image resources | **assets** | _number_ -| final_asset_images_hitrate | Browser cache hit rate (0-1) for image resources | **assets** | _number_ | final_asset_other_count | Total **number** of other resources | **assets** | _number_ | final_asset_other_load | Loading **time spent** on other resources | **assets** | _number_ | final_asset_other_size | Total **size** of other resources | **assets** | _number_ -| final_asset_other_hitrate | Browser cache hit rate (0-1) for other resources | **assets** | _number_ | connection_type | bluetooth, cellular, ethernet, none, wifi, wimax, other, unknown | **connection** | _string_ | effective_bandwidth | Mbps | **connection** | _number_ | effective_connection_type | slow-2g, 2g, 3g, 4g | **connection** | _string_ diff --git a/package.json b/package.json index 0490c68..c2e79e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "page-timing", - "version": "3.2.0", + "version": "3.1.1", "description": "⏱ Collect and measure browser performance metrics", "keywords": [ "browser", diff --git a/src/assets/index.js b/src/assets/index.js index ce44269..ba01831 100644 --- a/src/assets/index.js +++ b/src/assets/index.js @@ -13,7 +13,6 @@ export async function assets() { } const metrics = {}; - const caches = {}; const entries = await getEntries('resource'); for (const entry of entries) { @@ -21,11 +20,6 @@ export async function assets() { add(metrics, type, 'count', 1); add(metrics, type, 'load', entry.duration); add(metrics, type, 'size', entry.decodedBodySize); - cache(caches, type, entry.duration); - } - - for (const type in caches) { - add(metrics, type, 'hitrate', caches[type].hit / caches[type].total); } for (const key in metrics) { @@ -51,19 +45,3 @@ function add(accumulator, type, key, value) { accumulator[field] = accumulator[field] || 0; accumulator[field] += number(value) || 0; } - -/** - * Mutating - * @param {object} accumulator - * @param {string} type - * @param {number} duration (0 for cache hit and more for cache miss) - * @returns {void} - */ -function cache(accumulator, type, duration) { - accumulator[type] = accumulator[type] || {}; - accumulator[type].total = accumulator[type].total || 0; - accumulator[type].hit = accumulator[type].hit || 0; - - accumulator[type].total++; - accumulator[type].hit += +!duration; -} diff --git a/src/assets/spec.js b/src/assets/spec.js index 055e8c0..aef7478 100644 --- a/src/assets/spec.js +++ b/src/assets/spec.js @@ -2,29 +2,25 @@ import { getEntriesByTypeMock } from '../../spec-helpers/index.js'; import { assets } from './index.js'; const { getEntriesByType } = window.performance; -const PREFIX = 'final_asset'; -const types = [ 'images', 'javascript', 'stylesheets', 'other' ]; -const metrics = [ 'count', 'load', 'size', 'hitrate' ]; -let data; describe('assets', () => { - before(async() => { + before(() => { window.performance.getEntriesByType = getEntriesByTypeMock; - data = await assets(); - console.log(data); }); after(() => { window.performance.getEntriesByType = getEntriesByType; }); - types.map( - (type) => metrics.map( - (metric) => [ PREFIX, type, metric ].join('_') - ) - ).flat().forEach( + [ + 'final_asset_other_count', 'final_asset_other_load', 'final_asset_other_size', + 'final_asset_stylesheets_count', 'final_asset_stylesheets_load', 'final_asset_stylesheets_size', + 'final_asset_javascript_count', 'final_asset_javascript_load', 'final_asset_javascript_size', + 'final_asset_images_count', 'final_asset_images_load', 'final_asset_images_size' + ].forEach( (event) => it( event, async() => { + const data = await assets(); expect(data).to.have.property(event); expect(data[event]).to.be.a('number'); } From e8223f197a0c49c60ecad6edcf1a0f7afc6f45c3 Mon Sep 17 00:00:00 2001 From: omrilotan Date: Tue, 2 Mar 2021 23:39:37 +0200 Subject: [PATCH 3/3] Add hitrate function --- README.md | 29 +++++++++++++++++++++++++++++ package.json | 2 +- src/cachehit/index.js | 28 ++++++++++++++++++++++++++++ src/cachehit/spec.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/cachehit/index.js create mode 100644 src/cachehit/spec.js diff --git a/README.md b/README.md index 595fa33..f761262 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,35 @@ import { navigation, paint } from 'page-timing'; ## More functions +### `cachehit` +Get resource browser cache cachehit (0 - 1 number) +```js +import { cachehit } from 'page-timing'; + +const rate = await cachehit(); // 0.855 +``` + +Pass in a filter function to get results for a subset of resources: +```js +// Javascript files only +const javascriptCacheHitRate = await cachehit({ + filter: ({ name }) => /.m?js[$\?]/.test(name), + limit: 50 +}); + +// Specific domain +const myCDNCacheHitRate = await cachehit({ + filter: ({ name }) => /https:\/\/[\w-]*.mycdn.com/.test(), + limit: 50 +}); +``` + +#### Arguments +- **filter**: A filter function to create a subset of files we want to get cache rate for. This function accepts one argument: a [PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming) object. The default is no filter (all resources). +- **limit**: A number, in milliseconds, which represents resource load duration that we consider served from cache. The default is 50. + +> † There are two types of browser cache: memory cache and disk cache. Memory cache will have a duration of 0 (limit: 0) and disk cache will have higher duration, while still relatively low. For this reason the default limit value is 50 (milliseconds). This gives a close estimation of browser cache hit-rate. + ### `fps` Measure page frame rate at a certain point in time ```js diff --git a/package.json b/package.json index c2e79e5..0490c68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "page-timing", - "version": "3.1.1", + "version": "3.2.0", "description": "⏱ Collect and measure browser performance metrics", "keywords": [ "browser", diff --git a/src/cachehit/index.js b/src/cachehit/index.js new file mode 100644 index 0000000..da6fdd0 --- /dev/null +++ b/src/cachehit/index.js @@ -0,0 +1,28 @@ +/** + * Check window frames per second rate + * @param {function} filter + * @param {number} limit + * @returns {number?} + */ +export const cachehit = ({ filter = () => true, limit = 50 } = {}) => new Promise( + ((resolve, reject) => { + try { + const entries = window.performance.getEntriesByType('resource').filter(filter); + console.log(entries.length); + + if (!entries.length) { + resolve(undefined); + return; + } + + const cached = entries.filter( + ({ duration }) => duration < limit + ); + resolve( + cached.length / entries.length + ); + } catch (error) { + reject(error); + } + }) +); diff --git a/src/cachehit/spec.js b/src/cachehit/spec.js new file mode 100644 index 0000000..6928047 --- /dev/null +++ b/src/cachehit/spec.js @@ -0,0 +1,31 @@ +import { getEntriesByTypeMock } from '../../spec-helpers/index.js'; +import { cachehit } from './index.js'; + +const { getEntriesByType } = window.performance; + +describe('cachehit', () => { + before(() => { + window.performance.getEntriesByType = getEntriesByTypeMock; + }); + after(() => { + window.performance.getEntriesByType = getEntriesByType; + }); + it('should return all results cache hit rate', async() => { + expect(await cachehit()).to.equal(0.2161290322580645); + }); + it('should measure only a subset of the resources', async() => { + expect(await cachehit({ + filter: ({ name }) => /.woff2?[$\?]/.test(name), + })).to.equal(1); + }); + it('should have a lower result for a different limit', async() => { + expect(await cachehit({ + limit: 10 + })).to.equal(0.035483870967741936); + }); + it('should return undefined when no matching entries were found', async() => { + expect(await cachehit({ + filter: ({ name }) => name.includes('nothing.com') + })).to.be.undefined; + }); +});