From dbf629a0680355a3c78576292c4076f4d73d99cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Wed, 3 Mar 2021 19:10:53 +0100 Subject: [PATCH 01/17] =?UTF-8?q?=E2=9A=97=20POC:=20performance=20impact?= =?UTF-8?q?=20summary=20tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- performances/package.json | 8 +++ performances/src/main.ts | 39 ++++++++++++ performances/src/profiling.ts | 114 ++++++++++++++++++++++++++++++++++ performances/tsconfig.json | 9 +++ yarn.lock | 37 ++++++++++- 6 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 performances/package.json create mode 100644 performances/src/main.ts create mode 100644 performances/src/profiling.ts create mode 100644 performances/tsconfig.json diff --git a/package.json b/package.json index dc6145c14d..0e8b210380 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "private": true, "workspaces": [ "packages/*", - "developer-extension" + "developer-extension", + "performances" ], "scripts": { "postinstall": "scripts/cli update_submodule", diff --git a/performances/package.json b/performances/package.json new file mode 100644 index 0000000000..8185a1e825 --- /dev/null +++ b/performances/package.json @@ -0,0 +1,8 @@ +{ + "private": true, + "name": "performances", + "version": "0.0.0", + "dependencies": { + "puppeteer": "8.0.0" + } +} diff --git a/performances/src/main.ts b/performances/src/main.ts new file mode 100644 index 0000000000..13fd1107d5 --- /dev/null +++ b/performances/src/main.ts @@ -0,0 +1,39 @@ +import puppeteer, { Page } from 'puppeteer' + +import { startProfiling } from './profiling' + +main().catch(console.error) + +async function main() { + const browser = await puppeteer.launch({ defaultViewport: { width: 1366, height: 768 }, headless: true }) + try { + const page = await browser.newPage() + await setupSDK(page) + const stopProfiling = await startProfiling(page) + await runScenario(page) + await stopProfiling() + } finally { + await browser.close() + } +} + +async function runScenario(page: Page) { + await page.goto('https://en.wikipedia.org/wiki/Ubuntu') + await page.goto('https://en.wikipedia.org/wiki/Datadog') + await page.goto('https://en.wikipedia.org/wiki/Event_monitoring') + await page.goto('about:blank') +} + +async function setupSDK(page: Page) { + await page.evaluateOnNewDocument(` + import('https://www.datadoghq-browser-agent.com/datadog-rum.js') + .then(() => { + window.DD_RUM.init({ + clientToken: 'xxx', + applicationId: 'xxx', + site: 'localhost', + trackInteractions: true, + }) + }) + `) +} diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts new file mode 100644 index 0000000000..0be9a2ce73 --- /dev/null +++ b/performances/src/profiling.ts @@ -0,0 +1,114 @@ +import { CDPSession, Page, Protocol } from 'puppeteer' + +export async function startProfiling(page: Page) { + const client = await page.target().createCDPSession() + const stopCPUProfiling = await startCPUProfiling(client) + const stopMemoryProfiling = await startMemoryProfiling(client) + const stopNetworkProfiling = await startNetworkProfiling(client) + + return async () => { + await stopCPUProfiling() + await stopMemoryProfiling() + stopNetworkProfiling() + } +} + +async function startCPUProfiling(client: CDPSession) { + await client.send('Profiler.enable') + await client.send('Profiler.start') + + return async () => { + const { profile } = await client.send('Profiler.stop') + + const timeDeltaForNodeId = new Map() + + for (let index = 0; index < profile.samples!.length; index += 1) { + const nodeId = profile.samples![index] + timeDeltaForNodeId.set(nodeId, (timeDeltaForNodeId.get(nodeId) || 0) + profile.timeDeltas![index]) + } + + let total = 0 + for (const node of profile.nodes) { + if (isSDKCallFrame(node.callFrame)) { + total += timeDeltaForNodeId.get(node.id) || 0 + } + } + + console.log(`CPU: ${total} microseconds`) + } +} + +async function startMemoryProfiling(client: CDPSession) { + await client.send('HeapProfiler.enable') + await client.send('HeapProfiler.startSampling', { + // Set a low sampling interval to have more precise measurement + samplingInterval: 1000, + }) + + return async () => { + await client.send('HeapProfiler.collectGarbage') + const { profile } = await client.send('HeapProfiler.stopSampling') + + const sizeForNodeId = new Map() + + for (const sample of profile.samples) { + sizeForNodeId.set(sample.nodeId, (sizeForNodeId.get(sample.nodeId) || 0) + sample.size) + } + + let total = 0 + for (const node of iterNodes(profile.head)) { + if (isSDKCallFrame(node.callFrame)) { + total += sizeForNodeId.get(node.id) || 0 + } + } + + console.log(`Memory: ${total} bytes`) + } +} + +async function startNetworkProfiling(client: CDPSession) { + await client.send('Network.enable') + let total = 0 + const requestListener = (info: Protocol.Network.RequestWillBeSentEvent) => { + if (info.initiator.stack && isSDKCallFrame(info.initiator.stack.callFrames[0])) { + total += getRequestApproximateSize(info.request) + } + } + client.on('Network.requestWillBeSent', requestListener) + return () => { + client.off('Network.requestWillBeSent', requestListener) + + console.log(`Bandwidth: ${total} bytes`) + } +} + +function isSDKCallFrame(callFrame: Protocol.Runtime.CallFrame) { + return callFrame.url.startsWith('https://www.datadoghq-browser-agent.com/') +} + +function* iterNodes(root: N): Generator { + yield root + if (root.children) { + for (const child of root.children) { + yield* iterNodes(child) + } + } +} + +function getRequestApproximateSize(request: Protocol.Network.Request) { + let bodySize = 0 + if (request.postDataEntries) { + for (const { bytes } of request.postDataEntries) { + if (bytes) { + bodySize += Buffer.from(bytes, 'base64').byteLength + } + } + } + + let headerSize = 0 + for (const [name, value] of Object.entries(request.headers)) { + headerSize += name.length + value.length + } + + return bodySize + headerSize +} diff --git a/performances/tsconfig.json b/performances/tsconfig.json new file mode 100644 index 0000000000..d2ff85f149 --- /dev/null +++ b/performances/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.base.json", + + "compilerOptions": { + "baseUrl": ".", + "target": "es6", + "lib": ["ES2019"] + } +} diff --git a/yarn.lock b/yarn.lock index 3f38632abb..576126b009 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4180,6 +4180,11 @@ detect-libc@^1.0.2, detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +devtools-protocol@0.0.854822: + version "0.0.854822" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.854822.tgz#eac3a5260a6b3b4e729a09fdc0c77b0d322e777b" + integrity sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg== + devtools@6.1.3: version "6.1.3" resolved "https://registry.yarnpkg.com/devtools/-/devtools-6.1.3.tgz#8f1791031523f52154ab8f61c9731872e33d6a77" @@ -6104,6 +6109,14 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -8076,7 +8089,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0: +node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -8878,7 +8891,7 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-dir@^4.1.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -8999,7 +9012,7 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.0" -proxy-from-env@^1.0.0: +proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -9096,6 +9109,24 @@ puppeteer-core@^3.0.0: unbzip2-stream "^1.3.3" ws "^7.2.3" +puppeteer@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-8.0.0.tgz#a236669118aa795331c2d0ca19877159e7664705" + integrity sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ== + dependencies: + debug "^4.1.0" + devtools-protocol "0.0.854822" + extract-zip "^2.0.0" + https-proxy-agent "^5.0.0" + node-fetch "^2.6.1" + pkg-dir "^4.2.0" + progress "^2.0.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" + q@^1.5.1, q@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" From 7ad2edba1c6b6d09e28fcaf292f214950c49fa53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 11:43:42 +0100 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=90=9B=20do=20not=20install=20RUM?= =?UTF-8?q?=20on=20about:blank=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index 13fd1107d5..d1f98f75b3 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -26,14 +26,16 @@ async function runScenario(page: Page) { async function setupSDK(page: Page) { await page.evaluateOnNewDocument(` - import('https://www.datadoghq-browser-agent.com/datadog-rum.js') - .then(() => { - window.DD_RUM.init({ - clientToken: 'xxx', - applicationId: 'xxx', - site: 'localhost', - trackInteractions: true, + if (location.href !== 'about:blank') { + import('https://www.datadoghq-browser-agent.com/datadog-rum.js') + .then(() => { + window.DD_RUM.init({ + clientToken: 'xxx', + applicationId: 'xxx', + site: 'localhost', + trackInteractions: true, + }) }) - }) + } `) } From 50e95a7340807caf128115822dd5e1942df51036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 12:03:58 +0100 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=90=9B=20fix=20memory=20profiling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It appears that memory profiling is cleared when navigating on another page. To have consistent memory profiling, take measurements on each loaded pages. --- performances/src/main.ts | 13 +++++---- performances/src/profiling.ts | 54 +++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index d1f98f75b3..3bfa4c8d8f 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -9,18 +9,21 @@ async function main() { try { const page = await browser.newPage() await setupSDK(page) - const stopProfiling = await startProfiling(page) - await runScenario(page) + const { stopProfiling, takeMeasurements } = await startProfiling(page) + await runScenario(page, takeMeasurements) await stopProfiling() } finally { await browser.close() } } -async function runScenario(page: Page) { - await page.goto('https://en.wikipedia.org/wiki/Ubuntu') - await page.goto('https://en.wikipedia.org/wiki/Datadog') +async function runScenario(page: Page, takeMeasurements: () => Promise) { await page.goto('https://en.wikipedia.org/wiki/Event_monitoring') + await takeMeasurements() + await page.goto('https://en.wikipedia.org/wiki/Datadog') + await takeMeasurements() + await page.goto('https://en.wikipedia.org/wiki/Ubuntu') + await takeMeasurements() await page.goto('about:blank') } diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index 0be9a2ce73..1fd64ec8a8 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -3,13 +3,18 @@ import { CDPSession, Page, Protocol } from 'puppeteer' export async function startProfiling(page: Page) { const client = await page.target().createCDPSession() const stopCPUProfiling = await startCPUProfiling(client) - const stopMemoryProfiling = await startMemoryProfiling(client) + const { stopMemoryProfiling, takeMemoryMeasurements } = await startMemoryProfiling(client) const stopNetworkProfiling = await startNetworkProfiling(client) - return async () => { - await stopCPUProfiling() - await stopMemoryProfiling() - stopNetworkProfiling() + return { + takeMeasurements: async () => { + await takeMemoryMeasurements() + }, + stopProfiling: async () => { + await stopCPUProfiling() + await stopMemoryProfiling() + stopNetworkProfiling() + }, } } @@ -42,27 +47,38 @@ async function startMemoryProfiling(client: CDPSession) { await client.send('HeapProfiler.enable') await client.send('HeapProfiler.startSampling', { // Set a low sampling interval to have more precise measurement - samplingInterval: 1000, + samplingInterval: 100, }) - return async () => { - await client.send('HeapProfiler.collectGarbage') - const { profile } = await client.send('HeapProfiler.stopSampling') + const measurements: number[] = [] - const sizeForNodeId = new Map() + return { + takeMemoryMeasurements: async () => { + await client.send('HeapProfiler.collectGarbage') + const { profile } = await client.send('HeapProfiler.getSamplingProfile') - for (const sample of profile.samples) { - sizeForNodeId.set(sample.nodeId, (sizeForNodeId.get(sample.nodeId) || 0) + sample.size) - } + const sizeForNodeId = new Map() - let total = 0 - for (const node of iterNodes(profile.head)) { - if (isSDKCallFrame(node.callFrame)) { - total += sizeForNodeId.get(node.id) || 0 + for (const sample of profile.samples) { + sizeForNodeId.set(sample.nodeId, (sizeForNodeId.get(sample.nodeId) || 0) + sample.size) } - } - console.log(`Memory: ${total} bytes`) + let total = 0 + for (const node of iterNodes(profile.head)) { + if (isSDKCallFrame(node.callFrame)) { + total += sizeForNodeId.get(node.id) || 0 + } + } + measurements.push(total) + }, + + stopMemoryProfiling: async () => { + await client.send('HeapProfiler.stopSampling') + + measurements.sort((a, b) => a - b) + const median = measurements[Math.floor(measurements.length / 2)] + console.log(`Memory: ${median} bytes (median)`) + }, } } From 0b7a6f9805e8c2b0a74b9272ac27bc36e649e613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 11:39:47 +0100 Subject: [PATCH 04/17] =?UTF-8?q?=E2=9C=A8=20show=20download=20bandwidth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/profiling.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index 1fd64ec8a8..4b700baedc 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -34,7 +34,7 @@ async function startCPUProfiling(client: CDPSession) { let total = 0 for (const node of profile.nodes) { - if (isSDKCallFrame(node.callFrame)) { + if (isSdkUrl(node.callFrame.url)) { total += timeDeltaForNodeId.get(node.id) || 0 } } @@ -65,7 +65,7 @@ async function startMemoryProfiling(client: CDPSession) { let total = 0 for (const node of iterNodes(profile.head)) { - if (isSDKCallFrame(node.callFrame)) { + if (isSdkUrl(node.callFrame.url)) { total += sizeForNodeId.get(node.id) || 0 } } @@ -84,22 +84,38 @@ async function startMemoryProfiling(client: CDPSession) { async function startNetworkProfiling(client: CDPSession) { await client.send('Network.enable') - let total = 0 - const requestListener = (info: Protocol.Network.RequestWillBeSentEvent) => { - if (info.initiator.stack && isSDKCallFrame(info.initiator.stack.callFrames[0])) { - total += getRequestApproximateSize(info.request) + let totalUpload = 0 + let totalDownload = 0 + + const sdkRequestIds = new Set() + + const requestListener = ({ initiator, request, requestId }: Protocol.Network.RequestWillBeSentEvent) => { + if (isSdkUrl(request.url) || (initiator.stack && isSdkUrl(initiator.stack.callFrames[0].url))) { + totalUpload += getRequestApproximateSize(request) + sdkRequestIds.add(requestId) + } + } + + const loadingFinishedListener = ({ requestId, encodedDataLength }: Protocol.Network.LoadingFinishedEvent) => { + if (sdkRequestIds.has(requestId)) { + totalDownload += encodedDataLength } } + client.on('Network.requestWillBeSent', requestListener) + client.on('Network.loadingFinished', loadingFinishedListener) return () => { client.off('Network.requestWillBeSent', requestListener) + client.off('Network.loadingFinishedListener', loadingFinishedListener) - console.log(`Bandwidth: ${total} bytes`) + console.log(`Bandwidth:`) + console.log(` up ${totalUpload} bytes`) + console.log(` down ${totalDownload} bytes`) } } -function isSDKCallFrame(callFrame: Protocol.Runtime.CallFrame) { - return callFrame.url.startsWith('https://www.datadoghq-browser-agent.com/') +function isSdkUrl(url: string) { + return url.startsWith('https://www.datadoghq-browser-agent.com/') } function* iterNodes(root: N): Generator { From 76c35c6fd1679ae40d1aec81d045133081dff479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 17:18:50 +0100 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=8F=97=20abstract=20profiling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/format.ts | 18 ++++++++++++ performances/src/main.ts | 17 +++++++++-- performances/src/profiling.ts | 53 ++++++++++++++++++++++------------- performances/src/types.ts | 11 ++++++++ 4 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 performances/src/format.ts create mode 100644 performances/src/types.ts diff --git a/performances/src/format.ts b/performances/src/format.ts new file mode 100644 index 0000000000..44d1afb4e5 --- /dev/null +++ b/performances/src/format.ts @@ -0,0 +1,18 @@ +import { ProfilingResult, ProfilingResults } from './types' + +export function formatProfilingResults(results: ProfilingResults) { + return `\ +Memory (median): ${formatNumber(results.memory.sdk)} bytes ${formatPercent(results.memory)} +CPU: ${formatNumber(results.cpu.sdk)} microseconds ${formatPercent(results.cpu)} +Bandwidth: + upload: ${formatNumber(results.upload.sdk)} bytes + download: ${formatNumber(results.download.sdk)} bytes` +} + +function formatNumber(n: number) { + return new Intl.NumberFormat('en-US').format(n) +} + +function formatPercent({ total, sdk }: ProfilingResult) { + return `(${formatNumber((sdk / total) * 100)}%)` +} diff --git a/performances/src/main.ts b/performances/src/main.ts index 3bfa4c8d8f..9c2f355a29 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -1,23 +1,35 @@ import puppeteer, { Page } from 'puppeteer' +import { formatProfilingResults } from './format' import { startProfiling } from './profiling' +import { ProfilingResults } from './types' main().catch(console.error) async function main() { + const wikipediaResults = await profileScenario(runWikipediaScenario) + + console.log('# Wikipedia:') + console.log() + console.log(formatProfilingResults(wikipediaResults)) +} + +async function profileScenario(runScenario: (page: Page, takeMeasurements: () => Promise) => Promise) { const browser = await puppeteer.launch({ defaultViewport: { width: 1366, height: 768 }, headless: true }) + let result: ProfilingResults try { const page = await browser.newPage() await setupSDK(page) const { stopProfiling, takeMeasurements } = await startProfiling(page) await runScenario(page, takeMeasurements) - await stopProfiling() + result = await stopProfiling() } finally { await browser.close() } + return result } -async function runScenario(page: Page, takeMeasurements: () => Promise) { +async function runWikipediaScenario(page: Page, takeMeasurements: () => Promise) { await page.goto('https://en.wikipedia.org/wiki/Event_monitoring') await takeMeasurements() await page.goto('https://en.wikipedia.org/wiki/Datadog') @@ -28,6 +40,7 @@ async function runScenario(page: Page, takeMeasurements: () => Promise) { } async function setupSDK(page: Page) { + await page.setBypassCSP(true) await page.evaluateOnNewDocument(` if (location.href !== 'about:blank') { import('https://www.datadoghq-browser-agent.com/datadog-rum.js') diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index 4b700baedc..fc7b6ae135 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -1,4 +1,5 @@ import { CDPSession, Page, Protocol } from 'puppeteer' +import { ProfilingResults } from './types' export async function startProfiling(page: Page) { const client = await page.target().createCDPSession() @@ -10,11 +11,11 @@ export async function startProfiling(page: Page) { takeMeasurements: async () => { await takeMemoryMeasurements() }, - stopProfiling: async () => { - await stopCPUProfiling() - await stopMemoryProfiling() - stopNetworkProfiling() - }, + stopProfiling: async (): Promise => ({ + memory: await stopMemoryProfiling(), + cpu: await stopCPUProfiling(), + ...stopNetworkProfiling(), + }), } } @@ -32,14 +33,17 @@ async function startCPUProfiling(client: CDPSession) { timeDeltaForNodeId.set(nodeId, (timeDeltaForNodeId.get(nodeId) || 0) + profile.timeDeltas![index]) } - let total = 0 + let totalConsumption = 0 + let sdkConsumption = 0 for (const node of profile.nodes) { + const consumption = timeDeltaForNodeId.get(node.id) || 0 + totalConsumption += consumption if (isSdkUrl(node.callFrame.url)) { - total += timeDeltaForNodeId.get(node.id) || 0 + sdkConsumption += consumption } } - console.log(`CPU: ${total} microseconds`) + return { total: totalConsumption, sdk: sdkConsumption } } } @@ -50,7 +54,7 @@ async function startMemoryProfiling(client: CDPSession) { samplingInterval: 100, }) - const measurements: number[] = [] + const measurements: Array<{ sdkConsumption: number; totalConsumption: number }> = [] return { takeMemoryMeasurements: async () => { @@ -63,21 +67,24 @@ async function startMemoryProfiling(client: CDPSession) { sizeForNodeId.set(sample.nodeId, (sizeForNodeId.get(sample.nodeId) || 0) + sample.size) } - let total = 0 + let totalConsumption = 0 + let sdkConsumption = 0 for (const node of iterNodes(profile.head)) { + const consumption = sizeForNodeId.get(node.id) || 0 + totalConsumption += consumption if (isSdkUrl(node.callFrame.url)) { - total += sizeForNodeId.get(node.id) || 0 + sdkConsumption += consumption } } - measurements.push(total) + measurements.push({ totalConsumption, sdkConsumption }) }, stopMemoryProfiling: async () => { await client.send('HeapProfiler.stopSampling') - measurements.sort((a, b) => a - b) - const median = measurements[Math.floor(measurements.length / 2)] - console.log(`Memory: ${median} bytes (median)`) + measurements.sort((a, b) => a.sdkConsumption - b.sdkConsumption) + const { sdkConsumption, totalConsumption } = measurements[Math.floor(measurements.length / 2)] + return { total: totalConsumption, sdk: sdkConsumption } }, } } @@ -86,19 +93,24 @@ async function startNetworkProfiling(client: CDPSession) { await client.send('Network.enable') let totalUpload = 0 let totalDownload = 0 + let sdkUpload = 0 + let sdkDownload = 0 const sdkRequestIds = new Set() const requestListener = ({ initiator, request, requestId }: Protocol.Network.RequestWillBeSentEvent) => { + const size = getRequestApproximateSize(request) + totalUpload += size if (isSdkUrl(request.url) || (initiator.stack && isSdkUrl(initiator.stack.callFrames[0].url))) { - totalUpload += getRequestApproximateSize(request) + sdkUpload += size sdkRequestIds.add(requestId) } } const loadingFinishedListener = ({ requestId, encodedDataLength }: Protocol.Network.LoadingFinishedEvent) => { + totalDownload += encodedDataLength if (sdkRequestIds.has(requestId)) { - totalDownload += encodedDataLength + sdkDownload += encodedDataLength } } @@ -108,9 +120,10 @@ async function startNetworkProfiling(client: CDPSession) { client.off('Network.requestWillBeSent', requestListener) client.off('Network.loadingFinishedListener', loadingFinishedListener) - console.log(`Bandwidth:`) - console.log(` up ${totalUpload} bytes`) - console.log(` down ${totalDownload} bytes`) + return { + upload: { total: totalUpload, sdk: sdkUpload }, + download: { total: totalDownload, sdk: sdkDownload }, + } } } diff --git a/performances/src/types.ts b/performances/src/types.ts new file mode 100644 index 0000000000..e47d5c56db --- /dev/null +++ b/performances/src/types.ts @@ -0,0 +1,11 @@ +export interface ProfilingResults { + memory: ProfilingResult + cpu: ProfilingResult + download: ProfilingResult + upload: ProfilingResult +} + +export interface ProfilingResult { + sdk: number + total: number +} From 3616a6f2fa9af43b20785b61104f77fd5377c885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 17:19:51 +0100 Subject: [PATCH 06/17] =?UTF-8?q?=E2=9C=A8=20add=20twitter=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 52 +++++++++++++++++++++++++++++++- performances/src/trackNetwork.ts | 40 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 performances/src/trackNetwork.ts diff --git a/performances/src/main.ts b/performances/src/main.ts index 9c2f355a29..bb610ff5fc 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -2,6 +2,7 @@ import puppeteer, { Page } from 'puppeteer' import { formatProfilingResults } from './format' import { startProfiling } from './profiling' +import { trackNetwork } from './trackNetwork' import { ProfilingResults } from './types' main().catch(console.error) @@ -12,10 +13,21 @@ async function main() { console.log('# Wikipedia:') console.log() console.log(formatProfilingResults(wikipediaResults)) + console.log() + + const twitterResults = await profileScenario(runTwitterScenario) + + console.log('# Twitter:') + console.log() + console.log(formatProfilingResults(twitterResults)) } async function profileScenario(runScenario: (page: Page, takeMeasurements: () => Promise) => Promise) { - const browser = await puppeteer.launch({ defaultViewport: { width: 1366, height: 768 }, headless: true }) + const browser = await puppeteer.launch({ + defaultViewport: { width: 1366, height: 768 }, + // Twitter detects headless browsing and refuses to load + headless: false, + }) let result: ProfilingResults try { const page = await browser.newPage() @@ -39,6 +51,44 @@ async function runWikipediaScenario(page: Page, takeMeasurements: () => Promise< await page.goto('about:blank') } +async function runTwitterScenario(page: Page, takeMeasurements: () => Promise) { + const { waitForNetworkIdle } = trackNetwork(page) + await page.goto('https://twitter.com/explore') + await waitForNetworkIdle() + + // Even if the network is idle, sometimes links take a bit longer to render + await page.waitForSelector('[data-testid="trend"]') + await takeMeasurements() + await page.click('[data-testid="trend"]') + await waitForNetworkIdle() + await takeMeasurements() + + // Click on all tabs + const tabs = await page.$$('[role="tab"]') + for (const tab of tabs) { + await tab.click() + await waitForNetworkIdle() + await takeMeasurements() + } + + await page.click('[aria-label="Settings"]') + await waitForNetworkIdle() + await takeMeasurements() + + // Scroll to the bottom of the page, because some checkboxes may be hidden below fixed banners + await page.evaluate(`scrollTo(0, 100000)`) + + // Click on all checkboxes except the first one + const checkboxes = await page.$$('input[type="checkbox"]') + for (const checkbox of checkboxes.slice(1)) { + await checkbox.click() + await waitForNetworkIdle() + await takeMeasurements() + } + + await page.goto('about:blank') +} + async function setupSDK(page: Page) { await page.setBypassCSP(true) await page.evaluateOnNewDocument(` diff --git a/performances/src/trackNetwork.ts b/performances/src/trackNetwork.ts new file mode 100644 index 0000000000..df58321c51 --- /dev/null +++ b/performances/src/trackNetwork.ts @@ -0,0 +1,40 @@ +import { HTTPRequest, Page } from 'puppeteer' + +export function trackNetwork(page: Page) { + const pendingRequests = new Set() + + page.on('request', (request) => { + pendingRequests.add(request) + }) + page.on('requestfailed', (request) => { + pendingRequests.delete(request) + }) + page.on('requestfinished', (request) => { + pendingRequests.delete(request) + }) + + return { + waitForNetworkIdle: async () => + new Promise((resolve) => { + let timeoutId: NodeJS.Timeout + + wake() + + page.on('request', wake) + page.on('requestfinished', wake) + page.on('requestfailed', wake) + + function wake() { + clearTimeout(timeoutId) + if (pendingRequests.size === 0) { + timeoutId = setTimeout(() => { + page.off('request', wake) + page.off('requestfinished', wake) + page.off('requestfailed', wake) + resolve() + }, 200) + } + } + }), + } +} From 1c274c28d237d40127360142c07ea5d0541320aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 17:36:58 +0100 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=8F=97=20introduce=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 23 +++++++++++++++-------- performances/src/profiling.ts | 26 +++++++++++++------------- performances/src/types.ts | 4 ++++ 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index bb610ff5fc..2795da50e4 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -3,26 +3,33 @@ import puppeteer, { Page } from 'puppeteer' import { formatProfilingResults } from './format' import { startProfiling } from './profiling' import { trackNetwork } from './trackNetwork' -import { ProfilingResults } from './types' +import { ProfilingResults, ProfilingOptions } from './types' main().catch(console.error) async function main() { - const wikipediaResults = await profileScenario(runWikipediaScenario) + const options: ProfilingOptions = { + bundleUrl: 'https://www.datadoghq-browser-agent.com/datadog-rum.js', + } + + const wikipediaResults = await profileScenario(options, runWikipediaScenario) console.log('# Wikipedia:') console.log() console.log(formatProfilingResults(wikipediaResults)) console.log() - const twitterResults = await profileScenario(runTwitterScenario) + const twitterResults = await profileScenario(options, runTwitterScenario) console.log('# Twitter:') console.log() console.log(formatProfilingResults(twitterResults)) } -async function profileScenario(runScenario: (page: Page, takeMeasurements: () => Promise) => Promise) { +async function profileScenario( + options: ProfilingOptions, + runScenario: (page: Page, takeMeasurements: () => Promise) => Promise +) { const browser = await puppeteer.launch({ defaultViewport: { width: 1366, height: 768 }, // Twitter detects headless browsing and refuses to load @@ -31,8 +38,8 @@ async function profileScenario(runScenario: (page: Page, takeMeasurements: () => let result: ProfilingResults try { const page = await browser.newPage() - await setupSDK(page) - const { stopProfiling, takeMeasurements } = await startProfiling(page) + await setupSDK(page, options) + const { stopProfiling, takeMeasurements } = await startProfiling(options, page) await runScenario(page, takeMeasurements) result = await stopProfiling() } finally { @@ -89,11 +96,11 @@ async function runTwitterScenario(page: Page, takeMeasurements: () => Promise { window.DD_RUM.init({ clientToken: 'xxx', diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index fc7b6ae135..a8e7736bb4 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -1,11 +1,11 @@ import { CDPSession, Page, Protocol } from 'puppeteer' -import { ProfilingResults } from './types' +import { ProfilingResults, ProfilingOptions } from './types' -export async function startProfiling(page: Page) { +export async function startProfiling(options: ProfilingOptions, page: Page) { const client = await page.target().createCDPSession() - const stopCPUProfiling = await startCPUProfiling(client) - const { stopMemoryProfiling, takeMemoryMeasurements } = await startMemoryProfiling(client) - const stopNetworkProfiling = await startNetworkProfiling(client) + const stopCPUProfiling = await startCPUProfiling(options, client) + const { stopMemoryProfiling, takeMemoryMeasurements } = await startMemoryProfiling(options, client) + const stopNetworkProfiling = await startNetworkProfiling(options, client) return { takeMeasurements: async () => { @@ -19,7 +19,7 @@ export async function startProfiling(page: Page) { } } -async function startCPUProfiling(client: CDPSession) { +async function startCPUProfiling(options: ProfilingOptions, client: CDPSession) { await client.send('Profiler.enable') await client.send('Profiler.start') @@ -38,7 +38,7 @@ async function startCPUProfiling(client: CDPSession) { for (const node of profile.nodes) { const consumption = timeDeltaForNodeId.get(node.id) || 0 totalConsumption += consumption - if (isSdkUrl(node.callFrame.url)) { + if (isSdkUrl(options, node.callFrame.url)) { sdkConsumption += consumption } } @@ -47,7 +47,7 @@ async function startCPUProfiling(client: CDPSession) { } } -async function startMemoryProfiling(client: CDPSession) { +async function startMemoryProfiling(options: ProfilingOptions, client: CDPSession) { await client.send('HeapProfiler.enable') await client.send('HeapProfiler.startSampling', { // Set a low sampling interval to have more precise measurement @@ -72,7 +72,7 @@ async function startMemoryProfiling(client: CDPSession) { for (const node of iterNodes(profile.head)) { const consumption = sizeForNodeId.get(node.id) || 0 totalConsumption += consumption - if (isSdkUrl(node.callFrame.url)) { + if (isSdkUrl(options, node.callFrame.url)) { sdkConsumption += consumption } } @@ -89,7 +89,7 @@ async function startMemoryProfiling(client: CDPSession) { } } -async function startNetworkProfiling(client: CDPSession) { +async function startNetworkProfiling(options: ProfilingOptions, client: CDPSession) { await client.send('Network.enable') let totalUpload = 0 let totalDownload = 0 @@ -101,7 +101,7 @@ async function startNetworkProfiling(client: CDPSession) { const requestListener = ({ initiator, request, requestId }: Protocol.Network.RequestWillBeSentEvent) => { const size = getRequestApproximateSize(request) totalUpload += size - if (isSdkUrl(request.url) || (initiator.stack && isSdkUrl(initiator.stack.callFrames[0].url))) { + if (isSdkUrl(options, request.url) || (initiator.stack && isSdkUrl(options, initiator.stack.callFrames[0].url))) { sdkUpload += size sdkRequestIds.add(requestId) } @@ -127,8 +127,8 @@ async function startNetworkProfiling(client: CDPSession) { } } -function isSdkUrl(url: string) { - return url.startsWith('https://www.datadoghq-browser-agent.com/') +function isSdkUrl(options: ProfilingOptions, url: string) { + return url === options.bundleUrl } function* iterNodes(root: N): Generator { diff --git a/performances/src/types.ts b/performances/src/types.ts index e47d5c56db..48613d2534 100644 --- a/performances/src/types.ts +++ b/performances/src/types.ts @@ -1,3 +1,7 @@ +export interface ProfilingOptions { + bundleUrl: string +} + export interface ProfilingResults { memory: ProfilingResult cpu: ProfilingResult From b282d6270270e94c312bedd31b0d064feef8623e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 17:40:29 +0100 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=90=9B=20fix=20network=20profiling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 4 +++- performances/src/profiling.ts | 6 +++--- performances/src/types.ts | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index 2795da50e4..88622a7242 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -10,6 +10,7 @@ main().catch(console.error) async function main() { const options: ProfilingOptions = { bundleUrl: 'https://www.datadoghq-browser-agent.com/datadog-rum.js', + proxyHost: 'datadog-browser-sdk-profiling-proxy', } const wikipediaResults = await profileScenario(options, runWikipediaScenario) @@ -105,8 +106,9 @@ async function setupSDK(page: Page, options: ProfilingOptions) { window.DD_RUM.init({ clientToken: 'xxx', applicationId: 'xxx', - site: 'localhost', + site: 'datadoghq.com', trackInteractions: true, + proxyHost: ${JSON.stringify(options.proxyHost)} }) }) } diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index a8e7736bb4..8ea4d48afd 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -98,10 +98,10 @@ async function startNetworkProfiling(options: ProfilingOptions, client: CDPSessi const sdkRequestIds = new Set() - const requestListener = ({ initiator, request, requestId }: Protocol.Network.RequestWillBeSentEvent) => { + const requestListener = ({ request, requestId }: Protocol.Network.RequestWillBeSentEvent) => { const size = getRequestApproximateSize(request) totalUpload += size - if (isSdkUrl(options, request.url) || (initiator.stack && isSdkUrl(options, initiator.stack.callFrames[0].url))) { + if (isSdkUrl(options, request.url)) { sdkUpload += size sdkRequestIds.add(requestId) } @@ -128,7 +128,7 @@ async function startNetworkProfiling(options: ProfilingOptions, client: CDPSessi } function isSdkUrl(options: ProfilingOptions, url: string) { - return url === options.bundleUrl + return url === options.bundleUrl || url.startsWith(`https://${options.proxyHost}/`) } function* iterNodes(root: N): Generator { diff --git a/performances/src/types.ts b/performances/src/types.ts index 48613d2534..0ed6fb7dcf 100644 --- a/performances/src/types.ts +++ b/performances/src/types.ts @@ -1,5 +1,6 @@ export interface ProfilingOptions { bundleUrl: string + proxyHost: string } export interface ProfilingResults { From fac2cf11a3c87f540a779d99752e37145d19bfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 4 Mar 2021 18:34:16 +0100 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=92=9A=20add=20puppeteer=20to=20lic?= =?UTF-8?q?enses=20and=20fix=20eslint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 + LICENSE-3rdparty.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 433c82cdd8..0a2381b5f5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { './test/app/tsconfig.json', './test/e2e/tsconfig.json', './developer-extension/tsconfig.json', + './performances/tsconfig.json', ], sourceType: 'module', }, diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 8df30d4d7f..c58dbb5de1 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -60,6 +60,7 @@ dev,lerna,MIT,Copyright 2015-present Lerna Contributors dev,npm-run-all,MIT,Copyright 2015 Toru Nagashima dev,pako,MIT,(C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin dev,prettier,MIT,Copyright James Long and contributors +dev,puppeteer,Apache-2.0,Copyright 2017 Google Inc. dev,replace-in-file,MIT,Copyright 2015-2019 Adam Reis dev,sinon,BSD-3-Clause,Copyright 2010-2017 Christian Johansen dev,string-replace-loader,MIT,Copyright 2015 Valentyn Barmashyn From 8783c8ecf89213f8aa72a025e2e2d347182e6bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 10:21:54 +0100 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=93=9D=20add=20a=20bit=20of=20docum?= =?UTF-8?q?entation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/README.md | 5 +++++ performances/package.json | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 performances/README.md diff --git a/performances/README.md b/performances/README.md new file mode 100644 index 0000000000..fe42db1253 --- /dev/null +++ b/performances/README.md @@ -0,0 +1,5 @@ +# Browser SDK performances impact estimation tool + +This tool will run various scenarios in a browser and profile the impact of the Browser SDK. + +Use `yarn start` to execute it. diff --git a/performances/package.json b/performances/package.json index 8185a1e825..7e9b85c61e 100644 --- a/performances/package.json +++ b/performances/package.json @@ -2,6 +2,9 @@ "private": true, "name": "performances", "version": "0.0.0", + "scripts": { + "start": "ts-node ./src/main.ts" + }, "dependencies": { "puppeteer": "8.0.0" } From e53464f6f7d763bbd477c270e5fb7af66eb76742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 14:31:09 +0100 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=90=9B=20make=20sure=20websites=20a?= =?UTF-8?q?re=20in=20english?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/performances/src/main.ts b/performances/src/main.ts index 88622a7242..fa910d36c4 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -39,6 +39,9 @@ async function profileScenario( let result: ProfilingResults try { const page = await browser.newPage() + await page.setExtraHTTPHeaders({ + 'Accept-Language': 'en-US', + }) await setupSDK(page, options) const { stopProfiling, takeMeasurements } = await startProfiling(options, page) await runScenario(page, takeMeasurements) From f66fb00726ad881c13bb8ddc83136e514fefa2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 14:34:20 +0100 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=91=8C=20exit=20process=20with=20co?= =?UTF-8?q?de=201=20in=20case=20of=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index fa910d36c4..92f75202a7 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -5,7 +5,10 @@ import { startProfiling } from './profiling' import { trackNetwork } from './trackNetwork' import { ProfilingResults, ProfilingOptions } from './types' -main().catch(console.error) +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) async function main() { const options: ProfilingOptions = { From d704baa391e7a880758878dd36983ebb4e08500b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 15:20:19 +0100 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=91=8C=E2=9C=A8=20search=20for=20a?= =?UTF-8?q?=20page=20in=20the=20Wikipedia=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/performances/src/main.ts b/performances/src/main.ts index 92f75202a7..219995e20a 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -58,10 +58,20 @@ async function profileScenario( async function runWikipediaScenario(page: Page, takeMeasurements: () => Promise) { await page.goto('https://en.wikipedia.org/wiki/Event_monitoring') await takeMeasurements() + await page.goto('https://en.wikipedia.org/wiki/Datadog') await takeMeasurements() + + await page.type('[type="search"]', 'median', { + // large delay to trigger the autocomplete menu at each key press + delay: 400, + }) + await Promise.all([page.waitForNavigation(), page.keyboard.press('Enter')]) + await takeMeasurements() + await page.goto('https://en.wikipedia.org/wiki/Ubuntu') await takeMeasurements() + await page.goto('about:blank') } From 3b59ce37bc1f33928ab7f87e351dcc2b21f4f8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 15:21:38 +0100 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=91=8C=F0=9F=8E=A8=20simplify=20"ta?= =?UTF-8?q?keMeasurements"=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/profiling.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/performances/src/profiling.ts b/performances/src/profiling.ts index 8ea4d48afd..2258255bfa 100644 --- a/performances/src/profiling.ts +++ b/performances/src/profiling.ts @@ -8,9 +8,7 @@ export async function startProfiling(options: ProfilingOptions, page: Page) { const stopNetworkProfiling = await startNetworkProfiling(options, client) return { - takeMeasurements: async () => { - await takeMemoryMeasurements() - }, + takeMeasurements: takeMemoryMeasurements, stopProfiling: async (): Promise => ({ memory: await stopMemoryProfiling(), cpu: await stopCPUProfiling(), From e61863594b28c2ab9c39efe5367064146544e7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 5 Mar 2021 15:56:33 +0100 Subject: [PATCH 15/17] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=F0=9F=91=8C=20use=20pr?= =?UTF-8?q?esent=20tense=20in=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/performances/README.md b/performances/README.md index fe42db1253..9e220bdeba 100644 --- a/performances/README.md +++ b/performances/README.md @@ -1,5 +1,5 @@ # Browser SDK performances impact estimation tool -This tool will run various scenarios in a browser and profile the impact of the Browser SDK. +This tool runs various scenarios in a browser and profile the impact of the Browser SDK. Use `yarn start` to execute it. From 07b1de2e3103fa9d8b55d50ff6269a46b6697b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Mon, 8 Mar 2021 15:53:59 +0100 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=91=8C=20add=20scenarios=20descript?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/main.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/performances/src/main.ts b/performances/src/main.ts index 219995e20a..580c504813 100644 --- a/performances/src/main.ts +++ b/performances/src/main.ts @@ -17,17 +17,31 @@ async function main() { } const wikipediaResults = await profileScenario(options, runWikipediaScenario) + const twitterResults = await profileScenario(options, runTwitterScenario) - console.log('# Wikipedia:') - console.log() - console.log(formatProfilingResults(wikipediaResults)) - console.log() + console.log(` +# Wikipedia - const twitterResults = await profileScenario(options, runTwitterScenario) +Illustrates a mostly static site scenario. + +* Navigate on three Wikipedia articles +* Do a search (with dynamic autocompletion) and go to the first result + +${formatProfilingResults(wikipediaResults)} + + +# Twitter + +Illustrates a SPA scenario. + +* Navigate to the top trending topics +* Click on the first trending topic +* Click on Top, Latest, People, Photos and Videos tabs +* Navigate to the Settings page +* Click on a few checkboxes - console.log('# Twitter:') - console.log() - console.log(formatProfilingResults(twitterResults)) +${formatProfilingResults(twitterResults)} +`) } async function profileScenario( From 1ecedcd3273b4bb66d0a27e2c95487a78f04ac45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Mon, 8 Mar 2021 16:58:37 +0100 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=91=8C=20format=20number=20with=20u?= =?UTF-8?q?nits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- performances/src/format.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/performances/src/format.ts b/performances/src/format.ts index 44d1afb4e5..2ff2ef4cc2 100644 --- a/performances/src/format.ts +++ b/performances/src/format.ts @@ -1,18 +1,33 @@ import { ProfilingResult, ProfilingResults } from './types' +const DURATION_UNITS = ['μs', 'ms', 's'] + +const BYTES_UNITS = ['B', 'kB', 'MB'] + export function formatProfilingResults(results: ProfilingResults) { return `\ -Memory (median): ${formatNumber(results.memory.sdk)} bytes ${formatPercent(results.memory)} -CPU: ${formatNumber(results.cpu.sdk)} microseconds ${formatPercent(results.cpu)} +Memory (median): ${formatNumberWithUnit(results.memory.sdk, BYTES_UNITS)} ${formatPercent(results.memory)} +CPU: ${formatNumberWithUnit(results.cpu.sdk, DURATION_UNITS)} ${formatPercent(results.cpu)} Bandwidth: - upload: ${formatNumber(results.upload.sdk)} bytes - download: ${formatNumber(results.download.sdk)} bytes` + upload: ${formatNumberWithUnit(results.upload.sdk, BYTES_UNITS)} + download: ${formatNumberWithUnit(results.download.sdk, BYTES_UNITS)}` } -function formatNumber(n: number) { - return new Intl.NumberFormat('en-US').format(n) +function formatNumberWithUnit(n: number, units: string[]) { + let unit: string + for (unit of units) { + if (n < 1000) { + break + } + n /= 1000 + } + return `${formatNumber(n)} ${unit!}` } function formatPercent({ total, sdk }: ProfilingResult) { return `(${formatNumber((sdk / total) * 100)}%)` } + +function formatNumber(n: number) { + return new Intl.NumberFormat('en-US', { maximumFractionDigits: n < 10 ? 2 : n < 100 ? 1 : 0 }).format(n) +}