Skip to content
This repository has been archived by the owner on Aug 10, 2023. It is now read-only.

Commit

Permalink
Add measure function (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
omrilotan authored Mar 14, 2019
1 parent b585ee3 commit 66f3bab
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 43 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
expect: true,
assert: true,
sleep: true,
wait: true,
onload: true,
},
rules: {
Expand Down
5 changes: 3 additions & 2 deletions .karma/env-setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const sleep = (num = 0) => new Promise((resolve) => setTimeout(resolve, Math.min(num, 1500)));
const wait = require('@lets/wait');
const sleep = require('@lets/sleep');

const onload = () => new Promise((resolve) => {
isReady() || (document.onreadystatechange = isReady);
Expand All @@ -15,7 +16,7 @@ const onload = () => new Promise((resolve) => {

Object.assign(
global,
{sleep, onload},
{sleep, wait, onload},
require('chai')
);

Expand Down
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ This program collects each metrics' total time from `timeOrigin` (falls back to
Empty metrics (whose value is 0) will be omitted from the results.

## API
The method accepts named arguments, all of which are optional
- [`pageTiming`](#pageTiming)
- [`measure`](#measure)

## pageTiming
Get a formatted object of page performance metrics.

```js
import { pageTiming } from 'page-timing';
const results = pageTiming();

fetch('/send-metrics', {
method: 'POST',
body: JSON.stringify(results),
});
```

The method accepts named arguments, all of which are optional
```js
pageTiming({metrics: [], reducer: () => {}, accumulator = []});
const results = pageTiming({metrics: [], reducer: () => {}, accumulator = []});
```

| Key | Role | Type | Default value
Expand Down Expand Up @@ -130,11 +145,34 @@ Output
}
```

## measure
Wrap a function and measure it's execution time in milliseconds into a [performance measure](https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure) entry.

```js
import { measure } from 'page-timing';

await pageTiming.measure(wait, 'my-function');

// Example: Convert entries to a named array
performance.getEntriesByType('measure').reduce(
(accumulator, {name, duration}) => Object.assign(accumulator, {[name]: duration}),
{}
);
// {my-function: 53.35999990347773}

// Example: Retrieve a specific entry
const { duration } = performance.getEntriesByType('measure')
.find(({name}) => name === 'my-function');
// 53.35999990347773
```

## Dist
Browser entry (dist) exposes method as `pageTiming.pageTiming`:
Browser entry (dist) exposes methods as `pageTiming.pageTiming` and `pageTiming.measure`:

```js
window.pageTiming.pageTiming(); // [...]
const results = window.pageTiming.pageTiming(); // [...]

window.pageTiming.measure(myFunction, 'my-function');
```


Expand All @@ -156,4 +194,4 @@ window.pageTiming.pageTiming(); // [...]
| download Time | `responseEnd - responseStart`
| DOM Content loaded event time | `domContentLoadedEventEnd - domContentLoadedEventStart`

![](https://www.w3.org/TR/navigation-timing/timing-overview.png)
[![](https://www.w3.org/TR/navigation-timing/timing-overview.png)](https://www.w3.org/TR/navigation-timing/)
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "page-timing",
"version": "1.1.0",
"version": "1.2.0",
"description": "⏱ Measure browser timing and do something with it",
"keywords": [
"browser",
"website",
"page",
"performance",
"measure",
"speed",
"loading",
"navigation-timing",
"har",
""
],
"author": "Fiverr SRE",
Expand Down Expand Up @@ -39,6 +39,8 @@
"@babel/plugin-transform-spread": "^7.2.2",
"@babel/preset-env": "^7.2.3",
"@fiverr/eslint-config-fiverr": "^2.0.1",
"@lets/sleep": "^1.0.0",
"@lets/wait": "^1.0.0",
"babel-loader": "^8.0.5",
"chai": "^4.2.0",
"eslint": "^5.12.0",
Expand Down
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { performanceMetrics } from './performance-metrics';
import { paintEntries } from './paint-entries';
import { measure } from './measure';
import { measurement } from './measurement';
import { supported } from './supported';
export { measure } from './measure';

const DEFAULT_REDUCER = (accumulator, [key, value]) => [...accumulator, [key, value]];

Expand All @@ -20,7 +21,7 @@ export function pageTiming({metrics, reducer = DEFAULT_REDUCER, accumulator = []
accumulator = performanceMetrics(metrics)
.reduce(
(accumulator, metric, index, metrics) => {
const value = measure(metric);
const value = measurement(metric);

return value > 0 ?
reducer(accumulator, [metric, value], index, metrics)
Expand Down
4 changes: 2 additions & 2 deletions src/integration/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const results = Object.entries(mock).reduce(
describe('Integration', async() => {
delete require.cache[require.resolve('../')];
delete require.cache[require.resolve('../start')];
delete require.cache[require.resolve('../measure')];
delete require.cache[require.resolve('../measurement')];
require('../supported').supported(); // cached result

const performance = Object.getOwnPropertyDescriptor(window, 'performance');
Expand All @@ -54,7 +54,7 @@ describe('Integration', async() => {

it('Should extract all performance metrics to a structured object, and skip 0 values', async() => {
await onload();
await sleep(400);
await wait(400);

const pageTiming = require('../').pageTiming;

Expand Down
28 changes: 22 additions & 6 deletions src/measure/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { start } from '../start';

/**
* Get the measurement diff from start
* @param {String} measurement
* @return {Number}
* Add a 'measure' entry measuring the execution of a function
* @param {Function} fn
* @param {String} name
* @return {Any} original method return value
*/
export const measure = (measurement) => Math.max(window.performance.timing[measurement] - start(), 0);
export async function measure(fn, name) {
const {performance} = window;
const unique = Math.random().toString(32).substr(2);

const [start, end] = ['start', 'end'].map(
(suffix) => [name, suffix, unique].join('-')
);

performance.mark(start);
const result = await fn();

performance.mark(end);
performance.measure(name, start, end);
performance.clearMarks(start);
performance.clearMarks(end);

return result;
}
51 changes: 31 additions & 20 deletions src/measure/spec.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
const { measure } = require('.');

describe('measure', () => {
const performance = Object.getOwnPropertyDescriptor(window, 'performance');

afterEach(() =>
Object.defineProperty(window, 'performance', performance)
);
beforeEach(() => {
performance.clearMarks();
});
it('Should add a measure entry for an async function', async() => {
await measure(
async() => await wait(50),
'my-function'
);
const [{duration, name}] = performance.getEntriesByType('measure');

it('Should return the diff between timing metric to start point', () => {
window.performance = {
timeOrigin: 100,
timing: {
something: 250,
},
};
expect(measure('something')).to.equal(150);
expect(name).to.equal('my-function');
expect(duration).to.be.at.least(50);
expect(duration).to.be.at.most(100);
});
it('Should add a measure entry for a sync function', () => {
measure(
() => sleep(50),
'my-function'
);

it('Should return 0 if the time unit it lower than start (e.g. 0)', () => {
window.performance = {
timeOrigin: 100,
timing: {
something: 50,
const [{duration, name}] = performance.getEntriesByType('measure');

expect(name).to.equal('my-function');
expect(duration).to.be.at.least(50);
expect(duration).to.be.at.most(100);
});
it('Should return original function return value', async() => {
const result = await measure(
async() => {
await wait(50);
return [1, 2, 3];
},
};
expect(measure('something')).to.equal(0);
'my-function'
);
expect(result).to.deep.equal([1, 2, 3]);
});
});
8 changes: 8 additions & 0 deletions src/measurement/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { start } from '../start';

/**
* Get the measurement diff from start
* @param {String} entry
* @return {Number}
*/
export const measurement = (entry) => Math.max(window.performance.timing[entry] - start(), 0);
29 changes: 29 additions & 0 deletions src/measurement/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { measurement } = require('.');

describe('measurement', () => {
const performance = Object.getOwnPropertyDescriptor(window, 'performance');

afterEach(() =>
Object.defineProperty(window, 'performance', performance)
);

it('Should return the diff between timing metric to start point', () => {
window.performance = {
timeOrigin: 100,
timing: {
something: 250,
},
};
expect(measurement('something')).to.equal(150);
});

it('Should return 0 if the time unit it lower than start (e.g. 0)', () => {
window.performance = {
timeOrigin: 100,
timing: {
something: 50,
},
};
expect(measurement('something')).to.equal(0);
});
});
8 changes: 4 additions & 4 deletions src/spec.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { pageTiming } from '.';

const SLEEP_FOR = 400;
const WAIT_FOR = 400;

describe('page-timing', async() => {
it('Should return result by default', async() => {
await onload();
await sleep(SLEEP_FOR);
await wait(WAIT_FOR);
const measurements = pageTiming();
expect(measurements).to.have.lengthOf.at.least(5);
});

it('Should collect metrics as arrays of [key, value]', async() => {
await onload();
await sleep(SLEEP_FOR);
await wait(WAIT_FOR);
const measurements = pageTiming({
metrics: ['domInteractive', 'loadEventEnd'],
});
Expand All @@ -27,7 +27,7 @@ describe('page-timing', async() => {

it('Should format metrics', async() => {
await onload();
await sleep(SLEEP_FOR);
await wait(WAIT_FOR);
const [interactive, load] = pageTiming({
metrics: ['domInteractive', 'loadEventEnd'],
reducer: (accumulator, [key, value]) => [
Expand Down

0 comments on commit 66f3bab

Please sign in to comment.