-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support request timing data in nodejs (#68)
Co-authored-by: Kevin Paxton <kevin.paxton@formidable.com>
- Loading branch information
1 parent
9b79d0a
commit 81f6e01
Showing
11 changed files
with
489 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
'@envyjs/webui': patch | ||
'@envyjs/core': patch | ||
'@envyjs/node': patch | ||
--- | ||
|
||
Support HAR timing data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// HAR timing Data adapted from | ||
// https://github.com/exogen/node-fetch-har/blob/master/index.js | ||
|
||
import { HttpRequest } from '@envyjs/core'; | ||
|
||
export type HRTime = [number, number]; | ||
|
||
export type Timestamps = { | ||
firstByte?: HRTime; | ||
start: HRTime; | ||
socket?: HRTime; | ||
lookup?: HRTime; | ||
connect?: HRTime; | ||
received?: HRTime; | ||
secureConnect?: HRTime; | ||
sent?: HRTime; | ||
}; | ||
|
||
export function calculateTiming(time: Timestamps): HttpRequest['timings'] { | ||
// For backwards compatibility with HAR 1.1, the `connect` timing | ||
// includes `ssl` instead of being mutually exclusive. | ||
const legacyConnnect = time.secureConnect || time.connect; | ||
|
||
const blocked = getDuration(time.start, time.socket); | ||
const dns = getDuration(time.socket, time.lookup); | ||
const connect = getDuration(time.lookup, legacyConnnect); | ||
|
||
let ssl = -1; | ||
if (time.secureConnect) { | ||
ssl = getDuration(time.connect, time.secureConnect); | ||
} | ||
|
||
const send = getDuration(legacyConnnect, time.sent); | ||
const wait = Math.max(getDuration(time.sent, time.firstByte), 0); | ||
const receive = getDuration(time.firstByte, time.received); | ||
|
||
return { | ||
blocked, | ||
dns, | ||
connect, | ||
send, | ||
wait, | ||
receive, | ||
ssl, | ||
}; | ||
} | ||
|
||
export function getDuration(a: HRTime | undefined, b: HRTime | undefined): number { | ||
if (!(a && b)) return 0; | ||
const seconds = b[0] - a[0]; | ||
const nanoseconds = b[1] - a[1]; | ||
return seconds * 1000 + nanoseconds / 1e6; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { types as utilTypes } from 'util'; | ||
|
||
import { wrap as _wrap } from 'shimmer'; | ||
|
||
// ESM handling of wrapping | ||
export const wrap: typeof _wrap = (moduleExports, name, wrapper) => { | ||
if (!utilTypes.isProxy(moduleExports)) { | ||
return _wrap(moduleExports, name, wrapper); | ||
} else { | ||
const wrapped = _wrap(Object.assign({}, moduleExports), name, wrapper); | ||
|
||
return Object.defineProperty(moduleExports, name, { | ||
value: wrapped, | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { cleanup, render } from '@testing-library/react'; | ||
|
||
import { numberFormat } from '@/utils'; | ||
|
||
import TimingsDiagram from './TimingsDiagram'; | ||
|
||
const timings = { | ||
blocked: 10, | ||
dns: 20, | ||
connect: 100, | ||
ssl: 70, | ||
send: 30, | ||
wait: 30, | ||
receive: 10, | ||
}; | ||
|
||
const timingsWithoutSsl = { | ||
blocked: 10, | ||
dns: 20, | ||
connect: 100, | ||
ssl: -1, | ||
send: 30, | ||
wait: 30, | ||
receive: 10, | ||
}; | ||
|
||
describe('TimingsDiagram', () => { | ||
afterEach(() => { | ||
cleanup(); | ||
}); | ||
|
||
it('should render without error', () => { | ||
render(<TimingsDiagram timings={timings} />); | ||
}); | ||
|
||
it('should not render anything if timings are `undefined`', () => { | ||
const { container } = render(<TimingsDiagram timings={undefined} />); | ||
expect(container).toBeEmptyDOMElement(); | ||
}); | ||
|
||
it('should not render anything if timings are `undefined`', () => { | ||
const { container } = render(<TimingsDiagram timings={undefined} />); | ||
expect(container).toBeEmptyDOMElement(); | ||
}); | ||
|
||
it('should render a row for each timing showing the correct value', () => { | ||
const expectations = [ | ||
{ label: 'Blocked', value: 10 }, | ||
{ label: 'DNS', value: 20 }, | ||
{ label: 'Connecting', value: 30 }, | ||
{ label: 'TLS setup', value: 70 }, | ||
{ label: 'Sending', value: 30 }, | ||
{ label: 'Waiting', value: 30 }, | ||
{ label: 'Receiving', value: 10 }, | ||
]; | ||
|
||
const { getByTestId } = render(<TimingsDiagram timings={timings} />); | ||
const tableBody = getByTestId('timings-table-body'); | ||
|
||
let row = tableBody.firstElementChild; | ||
for (const { label, value } of expectations) { | ||
if (!row) break; | ||
|
||
const heading = row.querySelector('th'); | ||
const timing = row.querySelector('td'); | ||
expect(heading).toHaveTextContent(label); | ||
expect(timing).toHaveTextContent(numberFormat(value)); | ||
|
||
row = row.nextElementSibling; | ||
} | ||
}); | ||
|
||
it('should render a row for each timing showing the correct value when there is no SSL involved', () => { | ||
const expectations = [ | ||
{ label: 'Blocked', value: 10 }, | ||
{ label: 'DNS', value: 20 }, | ||
{ label: 'Connecting', value: 100 }, | ||
{ label: 'Sending', value: 30 }, | ||
{ label: 'Waiting', value: 30 }, | ||
{ label: 'Receiving', value: 10 }, | ||
]; | ||
|
||
const { getByTestId } = render(<TimingsDiagram timings={timingsWithoutSsl} />); | ||
const tableBody = getByTestId('timings-table-body'); | ||
|
||
let row = tableBody.firstElementChild; | ||
for (const { label, value } of expectations) { | ||
if (!row) break; | ||
|
||
const heading = row.querySelector('th'); | ||
const timing = row.querySelector('td'); | ||
expect(heading).toHaveTextContent(label); | ||
expect(timing).toHaveTextContent(numberFormat(value)); | ||
|
||
row = row.nextElementSibling; | ||
} | ||
}); | ||
}); |
Oops, something went wrong.