diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 511299fd3e3e..6f357d9a4a7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1345,48 +1345,6 @@ jobs: run: | echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 - overhead_metrics: - name: Overhead metrics - needs: [job_get_metadata, job_build] - runs-on: ubuntu-20.04 - timeout-minutes: 30 - if: | - contains(github.event.pull_request.labels.*.name, 'ci-overhead-measurements') - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version-file: 'package.json' - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - - name: Collect - run: yarn ci:collect - working-directory: dev-packages/overhead-metrics - - - name: Process - id: process - run: yarn ci:process - working-directory: dev-packages/overhead-metrics - # Don't run on forks - the PR comment cannot be added. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Upload results - uses: actions/upload-artifact@v4 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - with: - name: ${{ steps.process.outputs.artifactName }} - path: ${{ steps.process.outputs.artifactPath }} - retention-days: 7 - job_compile_bindings_profiling_node: name: Compile & Test Profiling Bindings (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.node || matrix.container }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} needs: [job_get_metadata, job_build] diff --git a/dev-packages/overhead-metrics/.eslintrc.cjs b/dev-packages/overhead-metrics/.eslintrc.cjs deleted file mode 100644 index 3eed32128e5c..000000000000 --- a/dev-packages/overhead-metrics/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - ignorePatterns: ['test-apps'], - overrides: [ - { - files: ['*.ts'], - rules: { - 'no-console': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@sentry-internal/sdk/no-optional-chaining': 'off', - '@sentry-internal/sdk/no-nullish-coalescing': 'off', - '@sentry-internal/sdk/no-class-field-initializers': 'off', - 'jsdoc/require-jsdoc': 'off', - }, - }, - ], -}; diff --git a/dev-packages/overhead-metrics/.gitignore b/dev-packages/overhead-metrics/.gitignore deleted file mode 100644 index 505d701f0e12..000000000000 --- a/dev-packages/overhead-metrics/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out diff --git a/dev-packages/overhead-metrics/README.md b/dev-packages/overhead-metrics/README.md deleted file mode 100644 index 51e7d2587ac0..000000000000 --- a/dev-packages/overhead-metrics/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Overhead performance metrics - -Evaluates Sentry & Replay impact on website performance by running a web app in Chromium via Playwright and collecting -various metrics. - -The general idea is to run a web app without Sentry, and then run the same app again with Sentry and another one with -Sentry+Replay included. For the three scenarios, we collect some metrics (CPU, memory, vitals) and later compare them -and post as a comment in a PR. Changes in the metrics, compared to previous runs from the main branch, should be -evaluated on case-by-case basis when preparing and reviewing the PR. - -## Resources - -- https://github.com/addyosmani/puppeteer-webperf diff --git a/dev-packages/overhead-metrics/configs/README.md b/dev-packages/overhead-metrics/configs/README.md deleted file mode 100644 index ceb96835f975..000000000000 --- a/dev-packages/overhead-metrics/configs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Replay metrics configuration & entrypoints (scripts) - -- [dev](dev) contains scripts launched during local development -- [ci](ci) contains scripts launched in CI diff --git a/dev-packages/overhead-metrics/configs/ci/collect.ts b/dev-packages/overhead-metrics/configs/ci/collect.ts deleted file mode 100644 index 88a510fabdf0..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/collect.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Metrics } from '../../src/collector.js'; -import { MetricsCollector } from '../../src/collector.js'; -import type { NumberProvider } from '../../src/results/metrics-stats.js'; -import { MetricsStats } from '../../src/results/metrics-stats.js'; -import { BookingAppScenario } from '../../src/scenarios.js'; -import { printStats } from '../../src/util/console.js'; -import { latestResultFile } from './env.js'; - -function checkStdDev(results: Metrics[], name: string, provider: NumberProvider, max: number): boolean { - const value = MetricsStats.stddev(results, provider); - if (value == undefined) { - console.warn(`✗ | Discarding results because StandardDeviation(${name}) is undefined`); - return false; - } else if (value > max) { - console.warn( - `✗ | Discarding results because StandardDeviation(${name}) is larger than ${max}. Actual value: ${value}`, - ); - return false; - } else { - console.log(`✓ | StandardDeviation(${name}) is ${value} (<= ${max})`); - } - return true; -} - -const collector = new MetricsCollector({ headless: true, cpuThrottling: 2 }); -const result = await collector.execute({ - name: 'jank', - scenarios: [ - new BookingAppScenario('index.html', 100), - new BookingAppScenario('with-sentry.html', 100), - new BookingAppScenario('with-replay.html', 100), - ], - runs: 10, - tries: 10, - async shouldAccept(results: Metrics[]): Promise { - await printStats(results); - - if ( - !checkStdDev(results, 'lcp', MetricsStats.lcp, 50) || - !checkStdDev(results, 'cls', MetricsStats.cls, 0.1) || - !checkStdDev(results, 'cpu', MetricsStats.cpu, 1) || - !checkStdDev(results, 'memory-mean', MetricsStats.memoryMean, 1000 * 1024) || - !checkStdDev(results, 'memory-max', MetricsStats.memoryMax, 1000 * 1024) - ) { - return false; - } - - const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; - if (cpuUsage > 0.85) { - // Note: complexity on the "JankTest" is defined by the `minimum = ...,` setting in app.js - specifying the number of animated elements. - console.warn( - `✗ | Discarding results because CPU usage is too high and may be inaccurate: ${(cpuUsage * 100).toFixed(2)} %.`, - 'Consider simplifying the scenario or changing the CPU throttling factor.', - ); - return false; - } - - return true; - }, -}); - -result.writeToFile(latestResultFile); diff --git a/dev-packages/overhead-metrics/configs/ci/env.ts b/dev-packages/overhead-metrics/configs/ci/env.ts deleted file mode 100644 index 511941e433b7..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/env.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const previousResultsDir = 'out/previous-results'; -export const baselineResultsDir = 'out/baseline-results'; -export const latestResultFile = 'out/latest-result.json'; -export const artifactName = 'replay-sdk-metrics'; diff --git a/dev-packages/overhead-metrics/configs/ci/process.ts b/dev-packages/overhead-metrics/configs/ci/process.ts deleted file mode 100644 index 31e7842844ef..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/process.ts +++ /dev/null @@ -1,54 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; - -import { ResultsAnalyzer } from '../../src/results/analyzer.js'; -import { PrCommentBuilder } from '../../src/results/pr-comment.js'; -import { Result } from '../../src/results/result.js'; -import { ResultsSet } from '../../src/results/results-set.js'; -import { Git } from '../../src/util/git.js'; -import { GitHub } from '../../src/util/github.js'; -import { artifactName, baselineResultsDir, latestResultFile, previousResultsDir } from './env.js'; - -const latestResult = Result.readFromFile(latestResultFile); -const branch = await Git.branch; -const baseBranch = await Git.baseBranch; -const branchIsBase = await Git.branchIsBase; - -await GitHub.downloadPreviousArtifact(baseBranch, baselineResultsDir, artifactName); - -if (branchIsBase) { - await GitHub.downloadPreviousArtifact(branch, previousResultsDir, artifactName); -} else { - // Copy over same results - await fs.copy(baselineResultsDir, previousResultsDir); -} - -GitHub.writeOutput('artifactName', artifactName); -GitHub.writeOutput('artifactPath', path.resolve(previousResultsDir)); - -const previousResults = new ResultsSet(previousResultsDir); - -const prComment = new PrCommentBuilder(); -if (baseBranch != branch) { - const baseResults = new ResultsSet(baselineResultsDir); - await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, baseResults), 'Baseline'); - await prComment.addAdditionalResultsSet( - `Baseline results on branch: ${baseBranch}`, - // We skip the first one here because it's already included as `Baseline` column above in addCurrentResult(). - baseResults - .items() - .slice(1, 10), - ); -} else { - await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, previousResults), 'Previous'); -} - -await prComment.addAdditionalResultsSet( - `Previous results on branch: ${branch}`, - previousResults.items().slice(0, 10), -); - -await GitHub.addOrUpdateComment(prComment); - -// Copy the latest test run results to the archived result dir. -await previousResults.add(latestResultFile, true); diff --git a/dev-packages/overhead-metrics/configs/dev/collect.ts b/dev-packages/overhead-metrics/configs/dev/collect.ts deleted file mode 100644 index 4b2ffbc5480a..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/collect.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Metrics } from '../../src/collector.js'; -import { MetricsCollector } from '../../src/collector.js'; -import { MetricsStats } from '../../src/results/metrics-stats.js'; -import { BookingAppScenario } from '../../src/scenarios.js'; -import { printStats } from '../../src/util/console.js'; -import { latestResultFile } from './env.js'; - -const collector = new MetricsCollector(); -const result = await collector.execute({ - name: 'dummy', - scenarios: [ - new BookingAppScenario('index.html', 50), - new BookingAppScenario('with-sentry.html', 50), - new BookingAppScenario('with-replay.html', 50), - new BookingAppScenario('index.html', 500), - new BookingAppScenario('with-sentry.html', 500), - new BookingAppScenario('with-replay.html', 500), - ], - runs: 1, - tries: 1, - async shouldAccept(results: Metrics[]): Promise { - printStats(results); - - const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; - if (cpuUsage > 0.9) { - console.error( - `CPU usage too high to be accurate: ${(cpuUsage * 100).toFixed(2)} %.`, - 'Consider simplifying the scenario or changing the CPU throttling factor.', - ); - return false; - } - return true; - }, -}); - -result.writeToFile(latestResultFile); diff --git a/dev-packages/overhead-metrics/configs/dev/env.ts b/dev-packages/overhead-metrics/configs/dev/env.ts deleted file mode 100644 index c2168763ea6e..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/env.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const outDir = 'out/results-dev'; -export const latestResultFile = 'out/latest-result.json'; diff --git a/dev-packages/overhead-metrics/configs/dev/process.ts b/dev-packages/overhead-metrics/configs/dev/process.ts deleted file mode 100644 index 096244b5c750..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/process.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ResultsAnalyzer } from '../../src/results/analyzer.js'; -import { Result } from '../../src/results/result.js'; -import { ResultsSet } from '../../src/results/results-set.js'; -import { printAnalysis } from '../../src/util/console.js'; -import { latestResultFile, outDir } from './env.js'; - -const resultsSet = new ResultsSet(outDir); -const latestResult = Result.readFromFile(latestResultFile); - -const analysis = await ResultsAnalyzer.analyze(latestResult, resultsSet); -printAnalysis(analysis); - -await resultsSet.add(latestResultFile, true); diff --git a/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json deleted file mode 100644 index 878b29487f54..000000000000 --- a/dev-packages/overhead-metrics/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "private": true, - "version": "8.35.0", - "name": "@sentry-internal/overhead-metrics", - "main": "index.js", - "author": "Sentry", - "license": "MIT", - "type": "module", - "scripts": { - "build": "tsc", - "dev:collect": "ts-node-esm ./configs/dev/collect.ts", - "dev:process": "ts-node-esm ./configs/dev/process.ts", - "dev:run:replay": "npx chrome ./test-apps/booking-app/with-replay.html", - "ci:collect": "ts-node-esm ./configs/ci/collect.ts", - "ci:process": "ts-node-esm ./configs/ci/process.ts", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish" - }, - "dependencies": { - "@octokit/rest": "^19.0.5", - "@types/node": "^18.11.17", - "axios": "^1.6.7", - "extract-zip": "^2.0.1", - "filesize": "^10.0.6", - "fs-extra": "^11.1.0", - "p-timeout": "^6.0.0", - "playwright": "^1.44.1", - "playwright-core": "^1.44.1", - "simple-git": "^3.16.0", - "simple-statistics": "^7.8.0", - "typescript": "4.9.5" - }, - "devDependencies": { - "ts-node": "^10.9.1" - } -} diff --git a/dev-packages/overhead-metrics/src/collector.ts b/dev-packages/overhead-metrics/src/collector.ts deleted file mode 100644 index 0cdb80130f80..000000000000 --- a/dev-packages/overhead-metrics/src/collector.ts +++ /dev/null @@ -1,204 +0,0 @@ -import pTimeout from 'p-timeout'; -import * as playwright from 'playwright'; - -import type { CpuUsageSerialized } from './perf/cpu.js'; -import { CpuUsage, CpuUsageSampler } from './perf/cpu.js'; -import type { JsHeapUsageSerialized } from './perf/memory.js'; -import { JsHeapUsage, JsHeapUsageSampler } from './perf/memory.js'; -import type { NetworkUsageSerialized } from './perf/network.js'; -import { NetworkUsage, NetworkUsageCollector } from './perf/network.js'; -import { PerfMetricsSampler } from './perf/sampler.js'; -import { Result } from './results/result.js'; -import type { Scenario, TestCase } from './scenarios.js'; -import { consoleGroup } from './util/console.js'; -import { WebVitals, WebVitalsCollector } from './vitals/index.js'; - -const networkConditions = 'Fast 3G'; - -// Same as puppeteer-core PredefinedNetworkConditions -const PredefinedNetworkConditions = Object.freeze({ - 'Slow 3G': { - download: ((500 * 1000) / 8) * 0.8, - upload: ((500 * 1000) / 8) * 0.8, - latency: 400 * 5, - connectionType: 'cellular3g', - }, - 'Fast 3G': { - download: ((1.6 * 1000 * 1000) / 8) * 0.9, - upload: ((750 * 1000) / 8) * 0.9, - latency: 150 * 3.75, - connectionType: 'cellular3g', - }, -}); - -export class Metrics { - public constructor( - public readonly vitals: WebVitals, - public readonly cpu: CpuUsage, - public readonly memory: JsHeapUsage, - public readonly network: NetworkUsage, - ) {} - - /** - * - */ - public static fromJSON( - data: Partial<{ - vitals: Partial; - cpu: CpuUsageSerialized; - memory: JsHeapUsageSerialized; - network: NetworkUsageSerialized; - }>, - ): Metrics { - return new Metrics( - WebVitals.fromJSON(data.vitals || {}), - CpuUsage.fromJSON(data.cpu || {}), - JsHeapUsage.fromJSON(data.memory || {}), - NetworkUsage.fromJSON(data.network || {}), - ); - } -} - -export interface MetricsCollectorOptions { - headless: boolean; - cpuThrottling: number; -} - -export class MetricsCollector { - private _options: MetricsCollectorOptions; - - public constructor(options?: Partial) { - this._options = { - headless: false, - cpuThrottling: 4, - ...options, - }; - } - - /** - * - */ - public async execute(testCase: TestCase): Promise { - console.log(`Executing test case ${testCase.name}`); - return consoleGroup(async () => { - const scenarioResults: Metrics[][] = []; - for (let s = 0; s < testCase.scenarios.length; s++) { - scenarioResults.push(await this._collect(testCase, s.toString(), testCase.scenarios[s])); - } - return new Result(testCase.name, this._options.cpuThrottling, networkConditions, scenarioResults); - }); - } - - /** - * - */ - private async _collect(testCase: TestCase, name: string, scenario: Scenario): Promise { - const label = `Scenario ${name} data collection (total ${testCase.runs} runs)`; - for (let try_ = 1; try_ <= testCase.tries; try_++) { - console.time(label); - const results: Metrics[] = []; - for (let run = 1; run <= testCase.runs; run++) { - const innerLabel = `Scenario ${name} data collection, run ${run}/${testCase.runs}`; - console.time(innerLabel); - try { - results.push(await this._run(scenario)); - } catch (e) { - console.warn(`${innerLabel} failed with ${e}`); - break; - } finally { - console.timeEnd(innerLabel); - } - } - console.timeEnd(label); - if (results.length == testCase.runs && (await testCase.shouldAccept(results))) { - console.log(`Test case ${testCase.name}, scenario ${name} passed on try ${try_}/${testCase.tries}`); - return results; - } else if (try_ != testCase.tries) { - console.log(`Test case ${testCase.name} failed on try ${try_}/${testCase.tries}, retrying`); - } else { - throw `Test case ${testCase.name}, scenario ${name} failed after ${testCase.tries} tries.`; - } - } - // Unreachable code, if configured properly: - console.assert(testCase.tries >= 1); - return []; - } - - /** - * - */ - private async _run(scenario: Scenario): Promise { - const disposeCallbacks: (() => Promise)[] = []; - try { - return await pTimeout( - (async () => { - const browser = await playwright.chromium.launch({ - headless: this._options.headless, - }); - disposeCallbacks.push(() => browser.close()); - const page = await browser.newPage(); - disposeCallbacks.push(() => page.close()); - - const errorLogs: Array = []; - await page.on('console', message => { - if (message.type() === 'error') errorLogs.push(message.text()); - }); - await page.on('crash', _ => { - errorLogs.push('Page crashed'); - }); - await page.on('pageerror', error => { - errorLogs.push(`${error.name}: ${error.message}`); - }); - - const cdp = await page.context().newCDPSession(page); - - // Simulate throttling. - await cdp.send('Network.emulateNetworkConditions', { - offline: false, - latency: PredefinedNetworkConditions[networkConditions].latency, - uploadThroughput: PredefinedNetworkConditions[networkConditions].upload, - downloadThroughput: PredefinedNetworkConditions[networkConditions].download, - }); - await cdp.send('Emulation.setCPUThrottlingRate', { rate: this._options.cpuThrottling }); - - // Collect CPU and memory info 10 times per second. - const perfSampler = await PerfMetricsSampler.create(cdp, 100); - disposeCallbacks.push(async () => perfSampler.stop()); - const cpuSampler = new CpuUsageSampler(perfSampler); - const memSampler = new JsHeapUsageSampler(perfSampler); - - const networkCollector = await NetworkUsageCollector.create(page); - const vitalsCollector = await WebVitalsCollector.create(page); - - await scenario.run(browser, page); - - // NOTE: FID needs some interaction to actually show a value - const vitals = await vitalsCollector.collect(); - - if (errorLogs.length > 0) { - throw `Error logs in browser console:\n\t\t${errorLogs.join('\n\t\t')}`; - } - - return new Metrics(vitals, cpuSampler.getData(), memSampler.getData(), networkCollector.getData()); - })(), - { milliseconds: 60 * 1000 }, - ); - } finally { - console.log('Disposing of browser and resources'); - disposeCallbacks.reverse(); - const errors = []; - for (const cb of disposeCallbacks) { - try { - await cb(); - } catch (e) { - errors.push(e instanceof Error ? `${e.name}: ${e.message}` : `${e}`); - } - } - if (errors.length > 0) { - console.warn(`All disposose callbacks have finished. Errors: ${errors}`); - } else { - console.warn('All disposose callbacks have finished.'); - } - } - } -} diff --git a/dev-packages/overhead-metrics/src/perf/cpu.ts b/dev-packages/overhead-metrics/src/perf/cpu.ts deleted file mode 100644 index 2ec17c1866fa..000000000000 --- a/dev-packages/overhead-metrics/src/perf/cpu.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { JsonObject } from '../util/json.js'; -import type { PerfMetrics, PerfMetricsSampler } from './sampler.js'; -import { TimeBasedMap } from './sampler.js'; - -export { CpuUsageSampler, CpuUsage }; - -export type CpuUsageSerialized = Partial<{ snapshots: JsonObject; average: number }>; - -class CpuUsage { - public constructor(public snapshots: TimeBasedMap, public average: number) {} - - public static fromJSON(data: CpuUsageSerialized): CpuUsage { - return new CpuUsage(TimeBasedMap.fromJSON(data.snapshots || {}), data.average as number); - } -} - -class MetricsDataPoint { - public constructor(public timestamp: number, public activeTime: number) {} -} - -class CpuUsageSampler { - private _snapshots: TimeBasedMap = new TimeBasedMap(); - private _average: number = 0; - private _initial?: MetricsDataPoint = undefined; - private _startTime!: number; - private _lastTimestamp!: number; - private _cumulativeActiveTime!: number; - - public constructor(sampler: PerfMetricsSampler) { - sampler.subscribe(this._collect.bind(this)); - } - - public getData(): CpuUsage { - return new CpuUsage(this._snapshots, this._average); - } - - private async _collect(metrics: PerfMetrics): Promise { - const data = new MetricsDataPoint(metrics.Timestamp, metrics.Duration); - if (this._initial == undefined) { - this._initial = data; - this._startTime = data.timestamp; - } else { - const frameDuration = data.timestamp - this._lastTimestamp; - const usage = frameDuration == 0 ? 0 : (data.activeTime - this._cumulativeActiveTime) / frameDuration; - - this._snapshots.set(data.timestamp, usage); - this._average = data.activeTime / (data.timestamp - this._startTime); - } - this._lastTimestamp = data.timestamp; - this._cumulativeActiveTime = data.activeTime; - } -} diff --git a/dev-packages/overhead-metrics/src/perf/memory.ts b/dev-packages/overhead-metrics/src/perf/memory.ts deleted file mode 100644 index 561d5d7ebd99..000000000000 --- a/dev-packages/overhead-metrics/src/perf/memory.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { JsonObject } from '../util/json.js'; -import type { PerfMetrics, PerfMetricsSampler } from './sampler.js'; -import { TimeBasedMap } from './sampler.js'; - -export { JsHeapUsageSampler, JsHeapUsage }; - -export type JsHeapUsageSerialized = Partial<{ snapshots: JsonObject }>; - -class JsHeapUsage { - public constructor(public snapshots: TimeBasedMap) {} - - public static fromJSON(data: JsHeapUsageSerialized): JsHeapUsage { - return new JsHeapUsage(TimeBasedMap.fromJSON(data.snapshots || {})); - } -} - -class JsHeapUsageSampler { - private _snapshots: TimeBasedMap = new TimeBasedMap(); - - public constructor(sampler: PerfMetricsSampler) { - sampler.subscribe(this._collect.bind(this)); - } - - public getData(): JsHeapUsage { - return new JsHeapUsage(this._snapshots); - } - - private async _collect(metrics: PerfMetrics): Promise { - this._snapshots.set(metrics.Timestamp, metrics.JSHeapUsedSize!); - } -} diff --git a/dev-packages/overhead-metrics/src/perf/network.ts b/dev-packages/overhead-metrics/src/perf/network.ts deleted file mode 100644 index 03b76d2fcc4d..000000000000 --- a/dev-packages/overhead-metrics/src/perf/network.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type * as playwright from 'playwright'; - -export class NetworkEvent { - public constructor( - public url: string | undefined, - public requestSize: number | undefined, - public responseSize: number | undefined, - public requestTimeNs: bigint | undefined, - public responseTimeNs: bigint | undefined, - ) {} - - /** - * - */ - public static fromJSON(data: Partial): NetworkEvent { - return new NetworkEvent( - data.url as string, - data.requestSize as number, - data.responseSize as number, - data.requestTimeNs == undefined ? undefined : BigInt(data.requestTimeNs), - data.responseTimeNs == undefined ? undefined : BigInt(data.responseTimeNs), - ); - } -} - -export type NetworkUsageSerialized = Partial<{ events: Array }>; - -export class NetworkUsage { - public constructor(public events: Array) {} - - /** - * - */ - public static fromJSON(data: NetworkUsageSerialized): NetworkUsage { - return new NetworkUsage(data.events?.map(e => NetworkEvent.fromJSON(e)) || []); - } -} - -export class NetworkUsageCollector { - private _events = new Array(); - - /** - * - */ - public static async create(page: playwright.Page): Promise { - const self = new NetworkUsageCollector(); - await page.route(_ => true, self._captureRequest.bind(self)); - return self; - } - - /** - * - */ - public getData(): NetworkUsage { - return new NetworkUsage(this._events); - } - - /** - * - */ - private async _captureRequest(route: playwright.Route, request: playwright.Request): Promise { - const url = request.url(); - try { - const event = new NetworkEvent( - url, - request.postDataBuffer()?.length, - undefined, - process.hrtime.bigint(), - undefined, - ); - this._events.push(event); - // Note: playwright would error out on file:/// requests. They are used to access local test app resources. - if (url.startsWith('file:///')) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - route.continue(); - } else { - const response = await route.fetch(); - const body = await response.body(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - route.fulfill({ response, body }); - event.responseTimeNs = process.hrtime.bigint(); - event.responseSize = body.length; - } - } catch (e) { - console.log(`Error when capturing request: ${request.method()} ${url} - ${e}`); - } - } -} diff --git a/dev-packages/overhead-metrics/src/perf/sampler.ts b/dev-packages/overhead-metrics/src/perf/sampler.ts deleted file mode 100644 index 1c5c631f231a..000000000000 --- a/dev-packages/overhead-metrics/src/perf/sampler.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type * as playwright from 'playwright'; -import type { Protocol } from 'playwright-core/types/protocol'; - -import type { JsonObject } from '../util/json'; - -export type PerfMetricsConsumer = (metrics: PerfMetrics) => Promise; -export type TimestampSeconds = number; - -export class TimeBasedMap extends Map { - /** - * - */ - public static fromJSON(entries: JsonObject): TimeBasedMap { - const result = new TimeBasedMap(); - // eslint-disable-next-line guard-for-in - for (const key in entries) { - result.set(parseFloat(key), entries[key]); - } - return result; - } - - /** - * - */ - public toJSON(): JsonObject { - return Object.fromEntries(this.entries()); - } -} - -export class PerfMetrics { - public constructor(private _metrics: Protocol.Performance.Metric[]) {} - - /** - * - */ - public get Timestamp(): number { - return this._find('Timestamp'); - } - - /** - * - */ - public get Duration(): number { - return this._find('TaskDuration'); - } - - /** - * - */ - public get JSHeapUsedSize(): number { - return this._find('JSHeapUsedSize'); - } - - /** - * - */ - private _find(name: string): number { - return this._metrics.find(metric => metric.name == name)!.value; - } -} - -export class PerfMetricsSampler { - private _consumers: PerfMetricsConsumer[] = []; - private _timer!: NodeJS.Timer; - private _errorPrinted: boolean = false; - - private constructor(private _cdp: playwright.CDPSession) {} - - /** - * - */ - public static async create(cdp: playwright.CDPSession, interval: number): Promise { - const self = new PerfMetricsSampler(cdp); - await cdp.send('Performance.enable', { timeDomain: 'timeTicks' }); - - // collect first sample immediately - self._collectSample(); - - // and set up automatic collection in the given interval - self._timer = setInterval(self._collectSample.bind(self), interval); - - return self; - } - - /** - * - */ - public subscribe(consumer: PerfMetricsConsumer): void { - this._consumers.push(consumer); - } - - /** - * - */ - public stop(): void { - clearInterval(this._timer); - } - - /** - * - */ - private _collectSample(): void { - this._cdp.send('Performance.getMetrics').then( - response => { - const metrics = new PerfMetrics(response.metrics); - this._consumers.forEach(cb => cb(metrics).catch(console.error)); - }, - e => { - // This happens if the browser closed unexpectedly. No reason to try again. - if (!this._errorPrinted) { - this._errorPrinted = true; - console.log(e); - this.stop(); - } - }, - ); - } -} diff --git a/dev-packages/overhead-metrics/src/results/analyzer.ts b/dev-packages/overhead-metrics/src/results/analyzer.ts deleted file mode 100644 index f27356f4d507..000000000000 --- a/dev-packages/overhead-metrics/src/results/analyzer.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { filesize } from 'filesize'; - -import type { GitHash } from '../util/git.js'; -import { JsonStringify } from '../util/json.js'; -import type { AnalyticsFunction, NumberProvider } from './metrics-stats.js'; -import { MetricsStats } from './metrics-stats.js'; -import type { Result } from './result.js'; -import type { ResultsSet } from './results-set.js'; - -// Compares latest result to previous/baseline results and produces the needed info. - -export class ResultsAnalyzer { - private constructor(private _result: Result) {} - - /** - * - */ - public static async analyze(currentResult: Result, baselineResults?: ResultsSet): Promise { - const items = new ResultsAnalyzer(currentResult)._collect(); - - const baseline = baselineResults?.find( - other => - other.cpuThrottling == currentResult.cpuThrottling && - other.name == currentResult.name && - other.networkConditions == currentResult.networkConditions && - JsonStringify(other) != JsonStringify(currentResult), - ); - - let otherHash: GitHash | undefined; - if (baseline != undefined) { - otherHash = baseline[0]; - const baseItems = new ResultsAnalyzer(baseline[1])._collect(); - // update items with baseline results - for (const base of baseItems) { - for (const item of items) { - if (item.metric == base.metric) { - item.others = base.values; - } - } - } - } - - return { - items: items, - otherHash: otherHash, - }; - } - - /** - * - */ - private _collect(): AnalyzerItem[] { - const items = new Array(); - - const scenarioResults = this._result.scenarioResults; - - const pushIfDefined = function ( - metric: AnalyzerItemMetric, - unit: AnalyzerItemUnit, - source: NumberProvider, - fn: AnalyticsFunction, - ): void { - const values = scenarioResults.map(items => fn(items, source)); - // only push if at least one value is defined - if (values.findIndex(v => v != undefined) >= 0) { - items.push({ - metric: metric, - values: new AnalyzerItemNumberValues(unit, values), - }); - } - }; - - pushIfDefined(AnalyzerItemMetric.lcp, AnalyzerItemUnit.ms, MetricsStats.lcp, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.cls, AnalyzerItemUnit.ms, MetricsStats.cls, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.cpu, AnalyzerItemUnit.ratio, MetricsStats.cpu, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.memoryAvg, AnalyzerItemUnit.bytes, MetricsStats.memoryMean, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.memoryMax, AnalyzerItemUnit.bytes, MetricsStats.memoryMax, MetricsStats.max); - pushIfDefined(AnalyzerItemMetric.netTx, AnalyzerItemUnit.bytes, MetricsStats.netTx, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netRx, AnalyzerItemUnit.bytes, MetricsStats.netRx, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netCount, AnalyzerItemUnit.integer, MetricsStats.netCount, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netTime, AnalyzerItemUnit.ms, MetricsStats.netTime, MetricsStats.mean); - - return items; - } -} - -export enum AnalyzerItemUnit { - ms, - ratio, // 1.0 == 100 % - bytes, - integer, -} - -export interface AnalyzerItemValues { - value(index: number): string; - diff(aIndex: number, bIndex: number): string; - percent(aIndex: number, bIndex: number): string; -} - -const AnalyzerItemValueNotAvailable = 'n/a'; - -class AnalyzerItemNumberValues implements AnalyzerItemValues { - public constructor(private _unit: AnalyzerItemUnit, private _values: (number | undefined)[]) {} - - public value(index: number): string { - if (!this._has(index)) return AnalyzerItemValueNotAvailable; - return this._withUnit(this._get(index)); - } - - public diff(aIndex: number, bIndex: number): string { - if (!this._has(aIndex) || !this._has(bIndex)) return AnalyzerItemValueNotAvailable; - const diff = this._get(bIndex) - this._get(aIndex); - const str = this._withUnit(diff, true); - return diff > 0 ? `+${str}` : str; - } - - public percent(aIndex: number, bIndex: number): string { - if (!this._has(aIndex) || !this._has(bIndex) || this._get(aIndex) == 0.0) return AnalyzerItemValueNotAvailable; - const percent = (this._get(bIndex) / this._get(aIndex)) * 100 - 100; - const str = `${percent.toFixed(2)} %`; - return percent > 0 ? `+${str}` : str; - } - - private _has(index: number): boolean { - return index >= 0 && index < this._values.length && this._values[index] != undefined; - } - - private _get(index: number): number { - return this._values[index]!; - } - - private _withUnit(value: number, isDiff: boolean = false): string { - switch (this._unit) { - case AnalyzerItemUnit.bytes: - return filesize(value) as string; - case AnalyzerItemUnit.ratio: - return `${(value * 100).toFixed(2)} ${isDiff ? 'pp' : '%'}`; - case AnalyzerItemUnit.integer: - return `${value}`; - default: - return `${value.toFixed(2)} ${AnalyzerItemUnit[this._unit]}`; - } - } -} - -export enum AnalyzerItemMetric { - lcp, - cls, - cpu, - memoryAvg, - memoryMax, - netTx, - netRx, - netCount, - netTime, -} - -export interface AnalyzerItem { - metric: AnalyzerItemMetric; - - // Current (latest) result. - values: AnalyzerItemValues; - - // Previous or baseline results, depending on the context. - others?: AnalyzerItemValues; -} - -export interface Analysis { - items: AnalyzerItem[]; - - // Commit hash that the the previous or baseline (depending on the context) result was collected for. - otherHash?: GitHash; -} diff --git a/dev-packages/overhead-metrics/src/results/metrics-stats.ts b/dev-packages/overhead-metrics/src/results/metrics-stats.ts deleted file mode 100644 index 2ccab6632905..000000000000 --- a/dev-packages/overhead-metrics/src/results/metrics-stats.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as ss from 'simple-statistics'; - -import type { Metrics } from '../collector'; - -export type NumberProvider = (metrics: Metrics) => number | undefined; -export type AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => number | undefined; - -export class MetricsStats { - public static lcp: NumberProvider = metrics => metrics.vitals.lcp; - public static cls: NumberProvider = metrics => metrics.vitals.cls; - public static cpu: NumberProvider = metrics => metrics.cpu.average; - public static memoryMean: NumberProvider = metrics => ss.mean(Array.from(metrics.memory.snapshots.values())); - public static memoryMax: NumberProvider = metrics => ss.max(Array.from(metrics.memory.snapshots.values())); - public static netTx: NumberProvider = metrics => ss.sum(metrics.network.events.map(e => e.requestSize || 0)); - public static netRx: NumberProvider = metrics => ss.sum(metrics.network.events.map(e => e.responseSize || 0)); - public static netCount: NumberProvider = metrics => - ss.sum(metrics.network.events.map(e => (e.requestTimeNs && e.responseTimeNs ? 1 : 0))); - public static netTime: NumberProvider = metrics => - ss.sum( - metrics.network.events.map(e => - e.requestTimeNs && e.responseTimeNs ? Number(e.responseTimeNs - e.requestTimeNs) / 1e6 : 0, - ), - ); - - public static mean: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.mean(numbers) : undefined; - }; - - public static max: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.max(numbers) : undefined; - }; - - public static stddev: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.standardDeviation(numbers) : undefined; - }; - - /** - * - */ - private static _collect(items: Metrics[], dataProvider: NumberProvider): number[] { - return items.map(dataProvider).filter(v => v != undefined && !Number.isNaN(v)) as number[]; - } - - // See https://en.wikipedia.org/wiki/Interquartile_range#Outliers for details on filtering. - /** - * - */ - private static _filteredValues(numbers: number[]): number[] { - numbers.sort((a, b) => a - b); - - if (numbers.length < 1) { - return []; - } - - const q1 = ss.quantileSorted(numbers, 0.25); - const q3 = ss.quantileSorted(numbers, 0.75); - const iqr = q3 - q1; - - return numbers.filter(num => num >= q1 - 1.5 * iqr && num <= q3 + 1.5 * iqr); - } -} diff --git a/dev-packages/overhead-metrics/src/results/pr-comment.ts b/dev-packages/overhead-metrics/src/results/pr-comment.ts deleted file mode 100644 index cd81d54dce20..000000000000 --- a/dev-packages/overhead-metrics/src/results/pr-comment.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Git } from '../util/git.js'; -import type { Analysis, AnalyzerItemValues } from './analyzer.js'; -import { AnalyzerItemMetric, ResultsAnalyzer } from './analyzer.js'; -import { Result } from './result.js'; -import type { ResultSetItem } from './results-set.js'; - -function trimIndent(str: string): string { - return str - .trim() - .split('\n') - .map(s => s.trim()) - .join('\n'); -} - -function printableMetricName(metric: AnalyzerItemMetric): string { - switch (metric) { - case AnalyzerItemMetric.lcp: - return 'LCP'; - case AnalyzerItemMetric.cls: - return 'CLS'; - case AnalyzerItemMetric.cpu: - return 'CPU'; - case AnalyzerItemMetric.memoryAvg: - return 'JS heap avg'; - case AnalyzerItemMetric.memoryMax: - return 'JS heap max'; - default: - return AnalyzerItemMetric[metric]; - } -} - -export class PrCommentBuilder { - private _buffer: string = ''; - - /** - * - */ - public get title(): string { - return 'Replay SDK metrics :rocket:'; - } - - /** - * - */ - public get body(): string { - const now = new Date(); - return trimIndent(` - ${this._buffer} -
-
- *) pp - percentage points - an absolute difference between two percentages.
- Last updated: -
- `); - } - - /** - * - */ - public async addCurrentResult(analysis: Analysis, otherName: string): Promise { - // Decides whether to print the "Other" for comparison depending on it being set in the input data. - const hasOther = analysis.otherHash != undefined; - const maybeOther = function (content: () => string): string { - return hasOther ? content() : ''; - }; - - const currentHash = await Git.hash; - - this._buffer += `

${this.title}

`; - if (!hasOther) { - this._buffer += `Latest data for: ${currentHash}`; - } - this._buffer += ` - - - - - ${maybeOther(() => '')} - - - - - - ${maybeOther(() => '')} - - - - - - - - `; - - const valueColumns = function (values: AnalyzerItemValues): string { - return ` - - - - - - - - `; - }; - - for (const item of analysis.items) { - if (hasOther) { - this._buffer += ` - - - - - `; - } else { - this._buffer += ` - - - ${valueColumns(item.values)} - `; - } - } - - this._buffer += ` -
  Plain+Sentry+Replay
RevisionValueValueDiffRatioValueDiffRatio
${values.value(0)}${values.value(1)}${values.diff(0, 1)}${values.percent(0, 1)}${values.value(2)}${values.diff(0, 2)}${values.percent(0, 2)}
${printableMetricName(item.metric)}This PR ${currentHash} - ${valueColumns(item.values)} -
${otherName} ${analysis.otherHash} - ${valueColumns(item.others!)} -
${printableMetricName(item.metric)}
`; - } - - /** - * - */ - public async addAdditionalResultsSet(name: string, resultFiles: ResultSetItem[]): Promise { - if (resultFiles.length == 0) return; - - this._buffer += ` -
-

${name}

- `; - - // Each `resultFile` will be printed as a single row - with metrics as table columns. - for (let i = 0; i < resultFiles.length; i++) { - const resultFile = resultFiles[i]; - // Load the file and "analyse" - collect stats we want to print. - const analysis = await ResultsAnalyzer.analyze(Result.readFromFile(resultFile.path)); - - if (i == 0) { - // Add table header - this._buffer += ''; - for (const item of analysis.items) { - this._buffer += ``; - } - this._buffer += ''; - } - - // Add table row - this._buffer += ``; - for (const item of analysis.items) { - // TODO maybe find a better way of showing this. After the change to multiple scenarios, this shows diff between "With Sentry" and "With Sentry + Replay" - this._buffer += ``; - } - this._buffer += ''; - } - - this._buffer += ` -
Revision${printableMetricName(item.metric)}
${resultFile.hash}${item.values.diff(0, 2)}
-
`; - } -} diff --git a/dev-packages/overhead-metrics/src/results/result.ts b/dev-packages/overhead-metrics/src/results/result.ts deleted file mode 100644 index 3794e0163c39..000000000000 --- a/dev-packages/overhead-metrics/src/results/result.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as fs from 'fs'; -import path from 'path'; - -import { Metrics } from '../collector.js'; -import type { JsonObject } from '../util/json.js'; -import { JsonStringify } from '../util/json.js'; - -export class Result { - public constructor( - public readonly name: string, - public readonly cpuThrottling: number, - public readonly networkConditions: string, - public readonly scenarioResults: Metrics[][], - ) {} - - /** - * - */ - public static readFromFile(filePath: string): Result { - const json = fs.readFileSync(filePath, { encoding: 'utf-8' }); - const data = JSON.parse(json) as JsonObject; - return new Result( - data.name as string, - data.cpuThrottling as number, - data.networkConditions as string, - ((data.scenarioResults as Partial[][]) || []).map(list => list.map(Metrics.fromJSON.bind(Metrics))), - ); - } - - /** - * - */ - public writeToFile(filePath: string): void { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - const json = JsonStringify(this); - fs.writeFileSync(filePath, json); - } -} diff --git a/dev-packages/overhead-metrics/src/results/results-set.ts b/dev-packages/overhead-metrics/src/results/results-set.ts deleted file mode 100644 index 9eb65ff3cb4f..000000000000 --- a/dev-packages/overhead-metrics/src/results/results-set.ts +++ /dev/null @@ -1,116 +0,0 @@ -import assert from 'assert'; -import * as fs from 'fs'; -import path from 'path'; - -import type { GitHash } from '../util/git.js'; -import { Git } from '../util/git.js'; -import { Result } from './result.js'; - -const delimiter = '-'; - -export class ResultSetItem { - public constructor(public path: string) {} - - /** - * - */ - public get name(): string { - return path.basename(this.path); - } - - /** - * - */ - public get number(): number { - return parseInt(this.parts[0]); - } - - /** - * - */ - public get hash(): GitHash { - return this.parts[1]; - } - - /** - * - */ - public get parts(): string[] { - return path.basename(this.path).split(delimiter); - } -} - -/// Wraps a directory containing multiple (N--result.json) files. -/// The files are numbered from the most recently added one, to the oldest one. - -export class ResultsSet { - public constructor(private _directory: string) { - if (!fs.existsSync(_directory)) { - fs.mkdirSync(_directory, { recursive: true }); - } - } - - /** - * - */ - public find(predicate: (value: Result) => boolean): [GitHash, Result] | undefined { - for (const item of this.items()) { - const result = Result.readFromFile(item.path); - if (predicate(result)) { - return [item.hash, result]; - } - } - return undefined; - } - - /** - * - */ - public items(): ResultSetItem[] { - return this._files() - .map(file => { - return new ResultSetItem(path.join(this._directory, file.name)); - }) - .filter(item => !isNaN(item.number)) - .sort((a, b) => a.number - b.number); - } - - /** - * - */ - public async add(newFile: string, onlyIfDifferent: boolean = false): Promise { - console.log(`Preparing to add ${newFile} to ${this._directory}`); - assert(fs.existsSync(newFile)); - - // Get the list of file sorted by the prefix number in the descending order (starting with the oldest files). - const files = this.items().sort((a, b) => b.number - a.number); - - if (onlyIfDifferent && files.length > 0) { - const latestFile = files[files.length - 1]; - if (fs.readFileSync(latestFile.path, { encoding: 'utf-8' }) == fs.readFileSync(newFile, { encoding: 'utf-8' })) { - console.log(`Skipping - it's already stored as ${latestFile.name}`); - return; - } - } - - // Rename all existing files, increasing the prefix - for (const file of files) { - const parts = file.name.split(delimiter); - parts[0] = (file.number + 1).toString(); - const newPath = path.join(this._directory, parts.join(delimiter)); - console.log(`Renaming ${file.path} to ${newPath}`); - fs.renameSync(file.path, newPath); - } - - const newName = `1${delimiter}${await Git.hash}${delimiter}result.json`; - console.log(`Adding ${newFile} to ${this._directory} as ${newName}`); - fs.copyFileSync(newFile, path.join(this._directory, newName)); - } - - /** - * - */ - private _files(): fs.Dirent[] { - return fs.readdirSync(this._directory, { withFileTypes: true }).filter(v => v.isFile()); - } -} diff --git a/dev-packages/overhead-metrics/src/scenarios.ts b/dev-packages/overhead-metrics/src/scenarios.ts deleted file mode 100644 index f4d59cf06d53..000000000000 --- a/dev-packages/overhead-metrics/src/scenarios.ts +++ /dev/null @@ -1,80 +0,0 @@ -import assert from 'assert'; -import * as fs from 'fs'; -import path from 'path'; -import type * as playwright from 'playwright'; - -import type { Metrics } from './collector'; - -// A testing scenario we want to collect metrics for. -export interface Scenario { - run(browser: playwright.Browser, page: playwright.Page): Promise; -} - -// Two scenarios that are compared to each other. -export interface TestCase { - name: string; - scenarios: Scenario[]; - runs: number; - tries: number; - - // Test function that will be executed and given a scenarios result set with exactly `runs` number of items. - // Should returns true if this "try" should be accepted and collected. - // If false is returned, `Collector` will retry up to `tries` number of times. - shouldAccept(results: Metrics[]): Promise; -} - -// A simple scenario that just loads the given URL. - -export class LoadPageScenario implements Scenario { - public constructor(public url: string) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - await page.goto(this.url, { waitUntil: 'load', timeout: 60000 }); - } -} - -// Loads test-apps/jank/ as a page source & waits for a short time before quitting. - -export class JankTestScenario implements Scenario { - public constructor(private _indexFile: string) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - let url = path.resolve(`./test-apps/jank/${this._indexFile}`); - assert(fs.existsSync(url)); - url = `file:///${url.replace(/\\/g, '/')}`; - console.log('Navigating to ', url); - await page.goto(url, { waitUntil: 'load', timeout: 60000 }); - await new Promise(resolve => setTimeout(resolve, 12000)); - } -} - -export class BookingAppScenario implements Scenario { - public constructor(private _indexFile: string, private _count: number) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - let url = path.resolve(`./test-apps/booking-app/${this._indexFile}`); - assert(fs.existsSync(url)); - url = `file:///${url.replace(/\\/g, '/')}?count=${this._count}`; - console.log('Navigating to ', url); - await page.goto(url, { waitUntil: 'load', timeout: 60000 }); - - // Click "Update" - await page.locator('#search button').click(); - - for (let i = 1; i < 10; i++) { - await page.locator(`.result:nth-child(${i}) [data-select]`).click(); - } - - // Wait for flushing, which we set to 2000ms - to be safe, we add 1s on top - await new Promise(resolve => setTimeout(resolve, 3000)); - } -} diff --git a/dev-packages/overhead-metrics/src/util/console.ts b/dev-packages/overhead-metrics/src/util/console.ts deleted file mode 100644 index b3343e05ffae..000000000000 --- a/dev-packages/overhead-metrics/src/util/console.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { filesize } from 'filesize'; - -import type { Metrics } from '../collector.js'; -import type { Analysis } from '../results/analyzer.js'; -import { AnalyzerItemMetric } from '../results/analyzer.js'; -import { MetricsStats } from '../results/metrics-stats.js'; - -export async function consoleGroup(code: () => Promise): Promise { - console.group(); - return code().finally(console.groupEnd); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PrintableTable = { [k: string]: any }; - -export function printStats(items: Metrics[]): void { - console.table({ - lcp: `${MetricsStats.mean(items, MetricsStats.lcp)?.toFixed(2)} ms`, - cls: `${MetricsStats.mean(items, MetricsStats.cls)?.toFixed(2)} ms`, - cpu: `${((MetricsStats.mean(items, MetricsStats.cpu) || 0) * 100).toFixed(2)} %`, - memoryMean: filesize(MetricsStats.mean(items, MetricsStats.memoryMean)), - memoryMax: filesize(MetricsStats.max(items, MetricsStats.memoryMax)), - netTx: filesize(MetricsStats.mean(items, MetricsStats.netTx)), - netRx: filesize(MetricsStats.mean(items, MetricsStats.netRx)), - netCount: MetricsStats.mean(items, MetricsStats.netCount), - netTime: `${MetricsStats.mean(items, MetricsStats.netTime)?.toFixed(2)} ms`, - }); -} - -export function printAnalysis(analysis: Analysis): void { - const table: PrintableTable = {}; - for (const item of analysis.items) { - table[AnalyzerItemMetric[item.metric]] = { - value: item.values.value(0), - withSentry: item.values.diff(0, 1), - withReplay: item.values.diff(0, 2), - ...(item.others == undefined - ? {} - : { - previous: item.others.value(0), - previousWithSentry: item.others.diff(0, 1), - previousWithReplay: item.others.diff(0, 2), - }), - }; - } - console.table(table); -} diff --git a/dev-packages/overhead-metrics/src/util/git.ts b/dev-packages/overhead-metrics/src/util/git.ts deleted file mode 100644 index 6882c2f213b5..000000000000 --- a/dev-packages/overhead-metrics/src/util/git.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { simpleGit } from 'simple-git'; - -export type GitHash = string; -const git = simpleGit(); - -async function defaultBranch(): Promise { - const remoteInfo = (await git.remote(['show', 'origin'])) as string; - for (let line of remoteInfo.split('\n')) { - line = line.trim(); - if (line.startsWith('HEAD branch:')) { - return line.substring('HEAD branch:'.length).trim(); - } - } - throw "Couldn't find base branch name"; -} - -export const Git = { - get repository(): Promise { - return (async () => { - if (typeof process.env.GITHUB_REPOSITORY == 'string' && process.env.GITHUB_REPOSITORY.length > 0) { - return `github.com/${process.env.GITHUB_REPOSITORY}`; - } else { - let url = (await git.remote(['get-url', 'origin'])) as string; - url = url.trim(); - url = url.replace(/^git@/, ''); - url = url.replace(/\.git$/, ''); - return url.replace(':', '/'); - } - })(); - }, - - get branch(): Promise { - return (async () => { - if (typeof process.env.GITHUB_HEAD_REF == 'string' && process.env.GITHUB_HEAD_REF.length > 0) { - return process.env.GITHUB_HEAD_REF; - } else if (typeof process.env.GITHUB_REF == 'string' && process.env.GITHUB_REF.startsWith('refs/heads/')) { - return process.env.GITHUB_REF.substring('refs/heads/'.length); - } else { - const branches = (await git.branchLocal()).branches; - for (const name in branches) { - if (branches[name].current) return name; - } - throw "Couldn't find current branch name"; - } - })(); - }, - - get baseBranch(): Promise { - if (typeof process.env.GITHUB_BASE_REF == 'string' && process.env.GITHUB_BASE_REF.length > 0) { - return Promise.resolve(process.env.GITHUB_BASE_REF); - } else { - return defaultBranch(); - } - }, - - get branchIsBase(): Promise { - return (async () => { - const branch = await this.branch; - const baseBranch = await this.baseBranch; - - return branch === baseBranch; - })(); - }, - - get hash(): Promise { - return (async () => { - let gitHash = await git.revparse('HEAD'); - const diff = await git.diff(); - if (diff.trim().length > 0) { - gitHash += '+dirty'; - } - return gitHash; - })(); - }, -}; diff --git a/dev-packages/overhead-metrics/src/util/github.ts b/dev-packages/overhead-metrics/src/util/github.ts deleted file mode 100644 index 707d9529f8ae..000000000000 --- a/dev-packages/overhead-metrics/src/util/github.ts +++ /dev/null @@ -1,205 +0,0 @@ -import * as fs from 'fs'; -import path from 'path'; -import { Octokit } from '@octokit/rest'; -import axios from 'axios'; -import extract from 'extract-zip'; - -import type { PrCommentBuilder } from '../results/pr-comment.js'; -import { consoleGroup } from './console.js'; -import { Git } from './git.js'; - -const octokit = new Octokit({ - auth: process.env.GITHUB_TOKEN, - // log: console, -}); - -const [, owner, repo] = (await Git.repository).split('/') as [string, string, string]; -const defaultArgs = { owner, repo }; - -async function downloadArtifact(url: string, path: string): Promise { - const writer = fs.createWriteStream(path); - return axios({ - method: 'get', - url: url, - responseType: 'stream', - headers: { - Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, - }, - }).then(response => { - return new Promise((resolve, reject) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - response.data.pipe(writer); - let error: Error; - writer.on('error', err => { - error = err; - writer.close(); - reject(err); - }); - writer.on('close', () => { - if (!error) resolve(); - }); - }); - }); -} - -async function tryAddOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { - /* Env var GITHUB_REF is only set if a branch or tag is available for the current CI event trigger type. - The ref given is fully-formed, meaning that - * for branches the format is refs/heads/, - * for pull requests it is refs/pull//merge, - * and for tags it is refs/tags/. - For example, refs/heads/feature-branch-1. - */ - let prNumber: number | undefined; - const githubRef = process.env.GITHUB_REF; - if (typeof githubRef == 'string' && githubRef.length > 0 && githubRef.startsWith('refs/pull/')) { - prNumber = parseInt(githubRef.split('/')[2] as string); - console.log(`Determined PR number ${prNumber} based on GITHUB_REF environment variable: '${githubRef}'`); - } else if (!(await Git.branchIsBase)) { - prNumber = ( - await octokit.rest.pulls.list({ - ...defaultArgs, - base: await Git.baseBranch, - head: await Git.branch, - }) - ).data[0]?.number; - if (prNumber != undefined) { - console.log(`Found PR number ${prNumber} based on base and head branches`); - } - } - - if (prNumber == undefined) return false; - - // Determine the PR comment author: - // Trying to fetch `octokit.users.getAuthenticated()` throws (in CI only): - // {"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#get-the-authenticated-user"} - // Let's make this conditional on some env variable that's unlikely to be set locally but will be set in GH Actions. - // Do not use "CI" because that's commonly set during local development and testing. - const author = - typeof process.env.GITHUB_ACTION == 'string' - ? 'github-actions[bot]' - : (await octokit.users.getAuthenticated()).data.login; - - // Try to find an existing comment by the author and title. - const comment = await (async () => { - for await (const comments of octokit.paginate.iterator(octokit.rest.issues.listComments, { - ...defaultArgs, - issue_number: prNumber, - })) { - const found = comments.data.find(comment => { - return ( - comment.user?.login == author && comment.body != undefined && comment.body.indexOf(commentBuilder.title) >= 0 - ); - }); - if (found) return found; - } - return undefined; - })(); - - if (comment != undefined) { - console.log(`Updating PR comment ${comment.html_url} body`); - await octokit.rest.issues.updateComment({ - ...defaultArgs, - comment_id: comment.id, - body: commentBuilder.body, - }); - } else { - console.log(`Adding a new comment to PR ${prNumber}`); - await octokit.rest.issues.createComment({ - ...defaultArgs, - issue_number: prNumber, - body: commentBuilder.body, - }); - } - - return true; -} - -export const GitHub = { - writeOutput(name: string, value: string): void { - if (typeof process.env.GITHUB_OUTPUT == 'string' && process.env.GITHUB_OUTPUT.length > 0) { - fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); - } - console.log(`Output ${name} = ${value}`); - }, - - downloadPreviousArtifact(branch: string, targetDir: string, artifactName: string): Promise { - console.log(`Trying to download previous artifact '${artifactName}' for branch '${branch}'`); - return consoleGroup(async () => { - fs.mkdirSync(targetDir, { recursive: true }); - - const workflow = await (async () => { - for await (const workflows of octokit.paginate.iterator(octokit.rest.actions.listRepoWorkflows, defaultArgs)) { - const found = workflows.data.find(w => w.name == process.env.GITHUB_WORKFLOW); - if (found) return found; - } - return undefined; - })(); - if (workflow == undefined) { - console.log( - `Skipping previous artifact '${artifactName}' download for branch '${branch}' - not running in CI?`, - "Environment variable GITHUB_WORKFLOW isn't set.", - ); - return; - } - - const workflowRuns = await octokit.actions.listWorkflowRuns({ - ...defaultArgs, - workflow_id: workflow.id, - branch: branch, - status: 'success', - }); - - const firstRun = workflowRuns.data.workflow_runs[0]; - - if (workflowRuns.data.total_count == 0 || !firstRun) { - console.warn(`Couldn't find any successful run for workflow '${workflow.name}'`); - return; - } - - const artifact = ( - await octokit.actions.listWorkflowRunArtifacts({ - ...defaultArgs, - run_id: firstRun.id, - }) - ).data.artifacts.find(it => it.name == artifactName); - - if (artifact == undefined) { - console.warn(`Couldn't find any artifact matching ${artifactName}`); - return; - } - - console.log(`Downloading artifact ${artifact.archive_download_url} and extracting to ${targetDir}`); - - const tempFilePath = path.resolve(targetDir, '../tmp-artifacts.zip'); - if (fs.existsSync(tempFilePath)) { - fs.unlinkSync(tempFilePath); - } - - try { - await downloadArtifact(artifact.archive_download_url, tempFilePath); - await extract(tempFilePath, { dir: path.resolve(targetDir) }); - } finally { - if (fs.existsSync(tempFilePath)) { - fs.unlinkSync(tempFilePath); - } - } - }); - }, - - async addOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { - console.log('Adding/updating PR comment'); - return consoleGroup(async () => { - let successful = false; - try { - successful = await tryAddOrUpdateComment(commentBuilder); - } finally { - if (!successful) { - const file = 'out/comment.html'; - console.log(`Writing built comment to ${path.resolve(file)}`); - fs.writeFileSync(file, commentBuilder.body); - } - } - }); - }, -}; diff --git a/dev-packages/overhead-metrics/src/util/json.ts b/dev-packages/overhead-metrics/src/util/json.ts deleted file mode 100644 index 87a5676869aa..000000000000 --- a/dev-packages/overhead-metrics/src/util/json.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -export type JsonObject = { [k: string]: T }; - -export function JsonStringify(object: T): string { - return JSON.stringify( - object, - (_: unknown, value: any): unknown => { - if (typeof value != 'undefined' && typeof value.toJSON == 'function') { - return value.toJSON(); - } else if (typeof value == 'bigint') { - return value.toString(); - } else { - return value; - } - }, - 2, - ); -} diff --git a/dev-packages/overhead-metrics/src/vitals/cls.ts b/dev-packages/overhead-metrics/src/vitals/cls.ts deleted file mode 100644 index 3e1ab977fb86..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/cls.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type * as playwright from 'playwright'; - -export { CLS }; - -// https://web.dev/cls/ -class CLS { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.cumulativeLayoutShiftScore = undefined; - - const observer = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - if (window.cumulativeLayoutShiftScore === undefined) { - window.cumulativeLayoutShiftScore = entry.value; - } else if (!entry.hadRecentInput) { - window.cumulativeLayoutShiftScore += entry.value; - } - } - }); - - observer.observe({type: 'layout-shift', buffered: true}); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.cumulativeLayoutShiftScore'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/fid.ts b/dev-packages/overhead-metrics/src/vitals/fid.ts deleted file mode 100644 index feb2324aa034..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/fid.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as playwright from 'playwright'; - -export { FID }; - -// https://web.dev/fid/ -class FID { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.firstInputDelay = undefined; - - const observer = new PerformanceObserver((entryList) => { - for (const entry of entryList.getEntries()) { - window.firstInputDelay = entry.processingStart - entry.startTime; - } - }) - - observer.observe({type: 'first-input', buffered: true}); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.firstInputDelay'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/index.ts b/dev-packages/overhead-metrics/src/vitals/index.ts deleted file mode 100644 index b573edb26bb6..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type * as playwright from 'playwright'; - -import { CLS } from './cls.js'; -import { FID } from './fid.js'; -import { LCP } from './lcp.js'; - -export { WebVitals, WebVitalsCollector }; - -class WebVitals { - public constructor(public lcp: number | undefined, public cls: number | undefined, public fid: number | undefined) {} - - public static fromJSON(data: Partial): WebVitals { - return new WebVitals(data.lcp as number, data.cls as number, data.fid as number); - } -} - -class WebVitalsCollector { - private constructor(private _lcp: LCP, private _cls: CLS, private _fid: FID) {} - - public static async create(page: playwright.Page): Promise { - const result = new WebVitalsCollector(new LCP(page), new CLS(page), new FID(page)); - await result._lcp.setup(); - await result._cls.setup(); - await result._fid.setup(); - return result; - } - - public async collect(): Promise { - return new WebVitals(await this._lcp.collect(), await this._cls.collect(), await this._fid.collect()); - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/lcp.ts b/dev-packages/overhead-metrics/src/vitals/lcp.ts deleted file mode 100644 index a471bf60ba4b..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/lcp.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as playwright from 'playwright'; - -export { LCP }; - -// https://web.dev/lcp/ -class LCP { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.largestContentfulPaint = undefined; - - const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1]; - window.largestContentfulPaint = lastEntry.renderTime || lastEntry.loadTime; - }); - - observer.observe({ type: 'largest-contentful-paint', buffered: true }); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.largestContentfulPaint'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg deleted file mode 100644 index ff0962fc24f1..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg deleted file mode 100644 index 138f0b58ab2e..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg deleted file mode 100644 index 31f6bc1d5184..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/index.html b/dev-packages/overhead-metrics/test-apps/booking-app/index.html deleted file mode 100644 index e3972c61e4c0..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/index.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/main.js b/dev-packages/overhead-metrics/test-apps/booking-app/main.js deleted file mode 100644 index 1ad19f429506..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/main.js +++ /dev/null @@ -1,180 +0,0 @@ -(function () { - const searchForm = document.querySelector('#search'); - - searchForm.addEventListener('submit', event => { - event.preventDefault(); - - updateOffers(); - }); - - const obs = new MutationObserver(function (mutations) { - console.log(mutations); - }); - - obs.observe(document.documentElement, { - attributes: true, - attributeOldValue: true, - characterData: true, - characterDataOldValue: true, - childList: true, - subtree: true, - }); -})(); - -function updateOffers() { - const list = document.querySelector('.result-list'); - - // Clear out existing children - for (let el of list.children) { - list.removeChild(el); - } - - // Add new children - // Allow to define children count via URL ?count=100 - const url = new URL(window.location.href); - const count = parseInt(url.searchParams.get('count') || 50); - for (let i = 0; i < count; i++) { - const el = document.createElement('div'); - el.classList.add('result'); - el.innerHTML = generateResult(); - - const id = crypto.randomUUID(); - el.setAttribute('id', id); - - addListeners(id, el); - - list.appendChild(el); - } -} - -function addListeners(id, el) { - el.querySelector('[data-long-text-open]').addEventListener('click', event => { - const parent = event.target.closest('.long-text'); - parent.setAttribute('data-show-long', ''); - }); - el.querySelector('[data-long-text-close]').addEventListener('click', event => { - const parent = event.target.closest('.long-text'); - parent.removeAttribute('data-show-long'); - }); - - // These are purposefully inefficient - el.querySelector('[data-select]').addEventListener('click', () => { - document.querySelectorAll('.result').forEach(result => { - if (result.getAttribute('id') === id) { - result.setAttribute('data-show-options', 'yes'); - } else { - result.setAttribute('data-show-options', 'no'); - } - }); - - // Do some more, extra expensive work - document.querySelectorAll('.select__price').forEach(el => { - el.setAttribute('js-is-checked', new Date().toISOString()); - el.setAttribute('js-is-checked-2', new Date().toISOString()); - el.setAttribute('js-is-checked-3', 'yes'); - el.setAttribute('js-is-checked-4', 'yes'); - el.setAttribute('js-is-checked-5', 'yes'); - el.setAttribute('js-is-checked-6', 'yes'); - }); - document.querySelectorAll('.tag').forEach(el => el.setAttribute('js-is-checked', 'yes')); - document.querySelectorAll('h3').forEach(el => el.setAttribute('js-is-checked', 'yes')); - }); -} - -const baseTitles = ['Cottage house', 'Cabin', 'Villa', 'House', 'Appartment', 'Cosy appartment']; -const baseBeds = ['2', '2+2', '4+2', '6+2', '6+4']; -const baseDescription = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; - -function generateResult() { - const title = `${getRandomItem(baseTitles)} ${Math.ceil(Math.random() * 20)}`; - const beds = getRandomItem(baseBeds); - const description = baseDescription - .split(' ') - .slice(Math.ceil(Math.random() * 10)) - .join(' '); - const price = 200 + Math.random() * 800; - - // Make short version of description - const descriptionShort = description.slice(0, 200); - const priceStr = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price); - - const imgSrc = `./img/house-${Math.floor(Math.random() * 3)}.jpg`; - - const placeholders = { - title, - beds, - description, - descriptionShort, - priceStr, - imgSrc, - }; - - return replacePlaceholders(template, placeholders); -} - -function getRandomItem(list) { - return list[Math.floor(Math.random() * list.length)]; -} - -function replacePlaceholders(str, placeholders) { - let replacedStr = str; - Object.keys(placeholders).forEach(placeholder => { - replacedStr = replacedStr.replaceAll(`{{${placeholder}}}`, placeholders[placeholder]); - }); - - return replacedStr; -} - -const template = `
- {{title}} -
- -
-
-

{{title}}

- -
- {{beds}} -
-
- -
-
- {{descriptionShort}} -
- -
- {{description}} - -
-
- -
- -
- -
-
- -
- -
- -
-
-
`; diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html deleted file mode 100644 index ae99a6171f0c..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - - - diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html deleted file mode 100644 index 94c581f184ab..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - - - diff --git a/dev-packages/overhead-metrics/test-apps/jank/README.md b/dev-packages/overhead-metrics/test-apps/jank/README.md deleted file mode 100644 index beb81a4eadd2..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Chrome DevTools Jank article sample code - -- Originally coming from - [devtools-samples](https://github.com/GoogleChrome/devtools-samples/tree/4818abc9dbcdb954d0eb9b70879f4ea18756451f/jank), - licensed under Apache 2.0. -- Linking article: diff --git a/dev-packages/overhead-metrics/test-apps/jank/app.js b/dev-packages/overhead-metrics/test-apps/jank/app.js deleted file mode 100644 index 23fc9ced5111..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/app.js +++ /dev/null @@ -1,170 +0,0 @@ -/* Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ - -document.addEventListener('DOMContentLoaded', function () { - 'use strict'; - - var app = {}, - proto = document.querySelector('.proto'), - movers, - bodySize = document.body.getBoundingClientRect(), - ballSize = proto.getBoundingClientRect(), - maxHeight = Math.floor(bodySize.height - ballSize.height), - maxWidth = 97, // 100vw - width of square (3vw) - incrementor = 10, - distance = 3, - frame, - minimum = 20, - subtract = document.querySelector('.subtract'), - add = document.querySelector('.add'); - - app.optimize = true; - app.count = minimum; - app.enableApp = true; - - app.init = function () { - if (movers) { - bodySize = document.body.getBoundingClientRect(); - for (var i = 0; i < movers.length; i++) { - document.body.removeChild(movers[i]); - } - document.body.appendChild(proto); - ballSize = proto.getBoundingClientRect(); - document.body.removeChild(proto); - maxHeight = Math.floor(bodySize.height - ballSize.height); - } - for (var i = 0; i < app.count; i++) { - var m = proto.cloneNode(); - var top = Math.floor(Math.random() * maxHeight); - if (top === maxHeight) { - m.classList.add('up'); - } else { - m.classList.add('down'); - } - m.style.left = i / (app.count / maxWidth) + 'vw'; - m.style.top = top + 'px'; - document.body.appendChild(m); - } - movers = document.querySelectorAll('.mover'); - }; - - app.update = function (timestamp) { - for (var i = 0; i < app.count; i++) { - var m = movers[i]; - if (!app.optimize) { - var pos = m.classList.contains('down') ? m.offsetTop + distance : m.offsetTop - distance; - if (pos < 0) pos = 0; - if (pos > maxHeight) pos = maxHeight; - m.style.top = pos + 'px'; - if (m.offsetTop === 0) { - m.classList.remove('up'); - m.classList.add('down'); - } - if (m.offsetTop === maxHeight) { - m.classList.remove('down'); - m.classList.add('up'); - } - } else { - var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px'))); - m.classList.contains('down') ? (pos += distance) : (pos -= distance); - if (pos < 0) pos = 0; - if (pos > maxHeight) pos = maxHeight; - m.style.top = pos + 'px'; - if (pos === 0) { - m.classList.remove('up'); - m.classList.add('down'); - } - if (pos === maxHeight) { - m.classList.remove('down'); - m.classList.add('up'); - } - } - } - frame = window.requestAnimationFrame(app.update); - }; - - document.querySelector('.stop').addEventListener('click', function (e) { - if (app.enableApp) { - cancelAnimationFrame(frame); - e.target.textContent = 'Start'; - app.enableApp = false; - } else { - frame = window.requestAnimationFrame(app.update); - e.target.textContent = 'Stop'; - app.enableApp = true; - } - }); - - document.querySelector('.optimize').addEventListener('click', function (e) { - if (e.target.textContent === 'Optimize') { - app.optimize = true; - e.target.textContent = 'Un-Optimize'; - } else { - app.optimize = false; - e.target.textContent = 'Optimize'; - } - }); - - add.addEventListener('click', function (e) { - cancelAnimationFrame(frame); - app.count += incrementor; - subtract.disabled = false; - app.init(); - frame = requestAnimationFrame(app.update); - }); - - subtract.addEventListener('click', function () { - cancelAnimationFrame(frame); - app.count -= incrementor; - app.init(); - frame = requestAnimationFrame(app.update); - if (app.count === minimum) { - subtract.disabled = true; - } - }); - - function debounce(func, wait, immediate) { - var timeout; - return function () { - var context = this, - args = arguments; - var later = function () { - timeout = null; - if (!immediate) func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; - } - - var onResize = debounce(function () { - if (app.enableApp) { - cancelAnimationFrame(frame); - app.init(); - frame = requestAnimationFrame(app.update); - } - }, 500); - - window.addEventListener('resize', onResize); - - add.textContent = 'Add ' + incrementor; - subtract.textContent = 'Subtract ' + incrementor; - document.body.removeChild(proto); - proto.classList.remove('.proto'); - app.init(); - window.app = app; - frame = window.requestAnimationFrame(app.update); -}); diff --git a/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png b/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png deleted file mode 100644 index 7f01723cee0f..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/jank/index.html b/dev-packages/overhead-metrics/test-apps/jank/index.html deleted file mode 100644 index 5c06fc377622..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png b/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png deleted file mode 100644 index 84df3e22f6b0..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/jank/styles.css b/dev-packages/overhead-metrics/test-apps/jank/styles.css deleted file mode 100644 index 052c8c4e608c..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/styles.css +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied. See the License for the specific language governing permissions - * and limitations under the License. */ - -* { - margin: 0; - padding: 0; -} - -body { - height: 100vh; - width: 100vw; -} - -.controls { - position: fixed; - top: 2vw; - left: 2vw; - z-index: 1; -} - -.controls button { - display: block; - font-size: 1em; - padding: 1em; - margin: 1em; - background-color: beige; - color: black; -} - -.subtract:disabled { - opacity: 0.2; -} - -.mover { - height: 3vw; - position: absolute; - z-index: 0; -} - -.border { - border: 1px solid black; -} - -@media (max-width: 600px) { - .controls button { - min-width: 20vw; - } -} diff --git a/dev-packages/overhead-metrics/test-apps/jank/with-replay.html b/dev-packages/overhead-metrics/test-apps/jank/with-replay.html deleted file mode 100644 index 6c5f32cc7e8d..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/with-replay.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html b/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html deleted file mode 100644 index 8beacb69b440..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/tsconfig.json b/dev-packages/overhead-metrics/tsconfig.json deleted file mode 100644 index 193dd1f8acde..000000000000 --- a/dev-packages/overhead-metrics/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "target": "es2020", - "module": "esnext", - "outDir": "build", - "esModuleInterop": true - }, - "include": ["src/**/*.ts", "configs/**/*.ts"] -} diff --git a/package.json b/package.json index 365e1eb13922..12476eed2ccd 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,10 @@ "lint:biome": "biome check .", "lint:prettier": "prettier \"**/*.md\" \"**/*.css\" --check", "postpublish": "lerna run --stream --concurrency 1 postpublish", - "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test", - "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit", + "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\" test", + "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\" test:unit", "test:update-snapshots": "lerna run test:update-snapshots", - "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", + "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\"", "test:pr:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts --affected", "test:pr:node": "UNIT_TEST_ENV=node ts-node ./scripts/ci-unit-tests.ts --affected", "test:ci:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts", @@ -88,7 +88,6 @@ "dev-packages/bundle-analyzer-scenarios", "dev-packages/e2e-tests", "dev-packages/node-integration-tests", - "dev-packages/overhead-metrics", "dev-packages/test-utils", "dev-packages/size-limit-gh-action", "dev-packages/clear-cache-gh-action", diff --git a/yarn.lock b/yarn.lock index cdd55b36d9a6..15543cfd506a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6974,19 +6974,6 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/core@^4.1.0": - version "4.2.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz#8c253ba9605aca605bc46187c34fcccae6a96648" - integrity sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg== - dependencies: - "@octokit/auth-token" "^3.0.0" - "@octokit/graphql" "^5.0.0" - "@octokit/request" "^6.0.0" - "@octokit/request-error" "^3.0.0" - "@octokit/types" "^9.0.0" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - "@octokit/core@^4.2.1": version "4.2.4" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.4.tgz#d8769ec2b43ff37cc3ea89ec4681a20ba58ef907" @@ -7068,13 +7055,6 @@ dependencies: "@octokit/types" "^6.40.0" -"@octokit/plugin-paginate-rest@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz#f34b5a7d9416019126042cd7d7b811e006c0d561" - integrity sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw== - dependencies: - "@octokit/types" "^9.0.0" - "@octokit/plugin-paginate-rest@^6.1.2": version "6.1.2" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz#f86456a7a1fe9e58fec6385a85cf1b34072341f8" @@ -7096,14 +7076,6 @@ "@octokit/types" "^6.39.0" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@^7.0.0": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz#f7ebe18144fd89460f98f35a587b056646e84502" - integrity sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA== - dependencies: - "@octokit/types" "^9.0.0" - deprecation "^2.3.1" - "@octokit/plugin-rest-endpoint-methods@^7.1.2": version "7.2.3" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz#37a84b171a6cb6658816c82c4082ac3512021797" @@ -7163,16 +7135,6 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^7.1.2" -"@octokit/rest@^19.0.5": - version "19.0.7" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.7.tgz#d2e21b4995ab96ae5bfae50b4969da7e04e0bb70" - integrity sha512-HRtSfjrWmWVNp2uAkEpQnuGMJsu/+dBr47dRc5QVgsCbnIc1+GFEaoKBWkYG+zjrsHpSqcAElMio+n10c0b5JA== - dependencies: - "@octokit/core" "^4.1.0" - "@octokit/plugin-paginate-rest" "^6.0.0" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^7.0.0" - "@octokit/tsconfig@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@octokit/tsconfig/-/tsconfig-1.0.2.tgz#59b024d6f3c0ed82f00d08ead5b3750469125af7" @@ -10069,11 +10031,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== -"@types/node@^18.11.17": - version "18.14.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1" - integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA== - "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -10361,13 +10318,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" - integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.48.0": version "5.48.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz#54f8368d080eb384a455f60c2ee044e948a8ce67" @@ -18385,17 +18335,6 @@ extract-stack@^2.0.0: resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-2.0.0.tgz#11367bc865bfcd9bc0db3123e5edb57786f11f9b" integrity sha512-AEo4zm+TenK7zQorGK1f9mJ8L14hnTDi2ZQPR+Mub1NX8zimka1mXpV5LpH8x9HoUmFSHZCfLHqWvp0Y4FxxzQ== -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fake-indexeddb@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.2.tgz#e7a884158fa576e00f03e973b9874619947013e4" @@ -18616,11 +18555,6 @@ filelist@^1.0.1: dependencies: minimatch "^5.0.1" -filesize@^10.0.6: - version "10.0.6" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.0.6.tgz#5f4cd2721664cd925db3a7a5a87bbfd6ab5ebb1a" - integrity sha512-rzpOZ4C9vMFDqOa6dNpog92CoLYjD79dnjLk2TYDDtImRIyLTOzqojCb05Opd1WuiWjs+fshhCgTd8cl7y5t+g== - filesize@^9.0.11: version "9.0.11" resolved "https://registry.yarnpkg.com/filesize/-/filesize-9.0.11.tgz#4ac3a42c084232dd9b2a1da0107f32d42fcfa5e4" @@ -26527,11 +26461,6 @@ p-timeout@^5.0.2: resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-5.1.0.tgz#b3c691cf4415138ce2d9cfe071dba11f0fee085b" integrity sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew== -p-timeout@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.1.tgz#bcee5e37d730f5474d973b6ff226751a1a5e6ff1" - integrity sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w== - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -27185,12 +27114,12 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.44.1, playwright-core@^1.44.1: +playwright-core@1.44.1: version "1.44.1" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== -playwright@1.44.1, playwright@^1.44.1: +playwright@1.44.1: version "1.44.1" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== @@ -30403,15 +30332,6 @@ simple-get@^4.0.0, simple-get@^4.0.1: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^3.16.0: - version "3.16.1" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.16.1.tgz#b67f18cbd3c68bbc4b9177ed49256afe51f12d47" - integrity sha512-xzRxMKiy1zEYeHGXgAzvuXffDS0xgsq07Oi4LWEEcVH29vLpcZ2tyQRWyK0NLLlCVaKysZeem5tC1qHEOxsKwA== - dependencies: - "@kwsites/file-exists" "^1.1.1" - "@kwsites/promise-deferred" "^1.1.1" - debug "^4.3.4" - simple-git@^3.27.0: version "3.27.0" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.27.0.tgz#f4b09e807bda56a4a3968f635c0e4888d3decbd5" @@ -30426,11 +30346,6 @@ simple-html-tokenizer@^0.5.11: resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" integrity sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og== -simple-statistics@^7.8.0: - version "7.8.3" - resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.3.tgz#62998dd7786ba14fa27b07f4f3cd498466f7961a" - integrity sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A== - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -32187,7 +32102,7 @@ ts-jest@^27.1.4: semver "7.x" yargs-parser "20.x" -ts-node@10.9.1, ts-node@^10.9.1: +ts-node@10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==