Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…26800) * Define rough code for PerformanceReporter * Create a component to manage the PerformanceReporter * Start adding tests for PerformanceReporter * Add test for web vitals reporting * Update schema to more closely match the API spec * Collect marks as counters and further update structure of API payload * Add some outstanding TODOs about the API structure * Add counter for long tasks * Add EnableClientMetrics without any System Console UI * Have PerformanceReporter use EnableClientMetrics * Have the PerformanceReporter only report results when logged in * Add test for having PerformanceReporter fall back to fetch * Stop logging errors for measurements failing * Remove buffered from observer * Remove the Mystery Ampersand * Still record marks with telemetry actions even if telemetry is disabled * Add timestamps to performance reports * Reuse the new telemetry code for the old telemetry * The second half of the last commit * Use Node performance libraries in all tests * Set version of PerformanceReport * Switch to the proper version of EnableClientMetrics * Remove TODO for unneeded field * Add user agent and platform detection * Updated metrics API route
- Loading branch information
Showing
22 changed files
with
1,188 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
webapp/channels/src/components/root/performance_reporter_controller.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
// See LICENSE.txt for license information. | ||
|
||
import {useEffect, useRef} from 'react'; | ||
import {useStore} from 'react-redux'; | ||
|
||
import {Client4} from 'mattermost-redux/client'; | ||
|
||
import PerformanceReporter from 'utils/performance_telemetry/reporter'; | ||
|
||
export default function PerformanceReporterController() { | ||
const store = useStore(); | ||
|
||
const reporter = useRef<PerformanceReporter>(); | ||
|
||
useEffect(() => { | ||
reporter.current = new PerformanceReporter(Client4, store); | ||
reporter.current.observe(); | ||
|
||
// There's no way to clean up web-vitals, so continue to assume that this component won't ever be unmounted | ||
return () => { | ||
// eslint-disable-next-line no-console | ||
console.error('PerformanceReporterController - Component unmounted or store changed'); | ||
}; | ||
}, [store]); | ||
|
||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
// See LICENSE.txt for license information. | ||
|
||
import {waitForObservations} from './performance_mock'; | ||
|
||
describe('PerformanceObserver', () => { | ||
test('should be able to observe a mark', async () => { | ||
const callback = jest.fn(); | ||
|
||
const observer = new PerformanceObserver(callback); | ||
observer.observe({entryTypes: ['mark']}); | ||
|
||
const testMark = performance.mark('testMark'); | ||
|
||
await waitForObservations(); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
|
||
const observedEntries = callback.mock.calls[0][0].getEntries(); | ||
expect(observedEntries).toHaveLength(1); | ||
expect(observedEntries[0]).toBe(testMark); | ||
expect(observedEntries[0]).toMatchObject({ | ||
entryType: 'mark', | ||
name: 'testMark', | ||
}); | ||
}); | ||
|
||
test('should be able to observe multiple marks', async () => { | ||
const callback = jest.fn(); | ||
|
||
const observer = new PerformanceObserver(callback); | ||
observer.observe({entryTypes: ['mark']}); | ||
|
||
const testMarkA = performance.mark('testMarkA'); | ||
const testMarkB = performance.mark('testMarkB'); | ||
|
||
await waitForObservations(); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
|
||
// Both marks were batched into a single call | ||
const observedEntries = callback.mock.calls[0][0].getEntries(); | ||
expect(observedEntries).toHaveLength(2); | ||
expect(observedEntries[0]).toBe(testMarkA); | ||
expect(observedEntries[0]).toMatchObject({ | ||
entryType: 'mark', | ||
name: 'testMarkA', | ||
}); | ||
expect(observedEntries[1]).toBe(testMarkB); | ||
expect(observedEntries[1]).toMatchObject({ | ||
entryType: 'mark', | ||
name: 'testMarkB', | ||
}); | ||
}); | ||
|
||
test('should be able to observe a measure', async () => { | ||
const callback = jest.fn(); | ||
|
||
const observer = new PerformanceObserver(callback); | ||
observer.observe({entryTypes: ['measure']}); | ||
|
||
const testMarkA = performance.mark('testMarkA'); | ||
const testMarkB = performance.mark('testMarkB'); | ||
const testMeasure = performance.measure('testMeasure', 'testMarkA', 'testMarkB'); | ||
|
||
await waitForObservations(); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
|
||
const observedEntries = callback.mock.calls[0][0].getEntries(); | ||
expect(observedEntries).toHaveLength(1); | ||
expect(observedEntries[0]).toBe(testMeasure); | ||
expect(observedEntries[0]).toMatchObject({ | ||
entryType: 'measure', | ||
name: 'testMeasure', | ||
duration: testMarkB.startTime - testMarkA.startTime, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
// See LICENSE.txt for license information. | ||
|
||
import {PerformanceObserver as NodePerformanceObserver, performance as nodePerformance} from 'node:perf_hooks'; | ||
|
||
// These aren't a perfect match for window.performance and PerformanceObserver, but they're close enough. They don't | ||
// work with `jest.useFakeTimers` because that overwrites window.performance in a way that breaks the Node.js version. | ||
// | ||
// To use PerformanceObserver, you need to use a `setTimeout` or `await observations()` to have a PerformanceObserver's | ||
// callback get called. See the accompanying tests for examples. | ||
|
||
Object.defineProperty(window, 'performance', { | ||
writable: true, | ||
value: nodePerformance, | ||
}); | ||
|
||
Object.defineProperty(global, 'PerformanceObserver', { | ||
value: NodePerformanceObserver, | ||
}); | ||
|
||
// Only Chrome-based browsers support long task timings currently, so make Node pretend it does too | ||
Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', { | ||
value: [...PerformanceObserver.supportedEntryTypes, 'longtask'], | ||
}); | ||
|
||
export function waitForObservations() { | ||
// Performance observations are processed after any timeout | ||
return new Promise((resolve) => setTimeout(resolve)); | ||
} |
Oops, something went wrong.