Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[React RUM] Add a ReactComponentTracker component #3086

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

Miz85
Copy link

@Miz85 Miz85 commented Oct 22, 2024

Motivation

Provide a way to measure React components render duration and report them to DataDog as Custom Vitals

Changes

  • Create a performance domain in the rum-react package
  • Provide a ReactComponentTracker component that can be used to wrap components that you want to measure render duration for.

Testing

  • Local
  • Staging
  • Unit
  • End to end

I have gone over the contributing documentation.

@Miz85 Miz85 requested a review from a team as a code owner October 22, 2024 15:20
@bits-bot
Copy link

bits-bot commented Oct 22, 2024

CLA assistant check
All committers have signed the CLA.

@codecov-commenter
Copy link

codecov-commenter commented Oct 22, 2024

Codecov Report

Attention: Patch coverage is 45.45455% with 6 lines in your changes missing coverage. Please review.

Project coverage is 93.40%. Comparing base (a0e447e) to head (ea3b972).

Files with missing lines Patch % Lines
...kages/rum-react/src/domain/performance/getTimer.ts 45.45% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3086      +/-   ##
==========================================
- Coverage   93.47%   93.40%   -0.07%     
==========================================
  Files         287      288       +1     
  Lines        7784     7795      +11     
  Branches     1759     1760       +1     
==========================================
+ Hits         7276     7281       +5     
- Misses        508      514       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

cit-pr-commenter bot commented Oct 22, 2024

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 161.60 KiB 161.60 KiB 0 B 0.00%
Logs 55.85 KiB 55.85 KiB 0 B 0.00%
Rum Slim 110.44 KiB 110.44 KiB 0 B 0.00%
Worker 25.21 KiB 25.21 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base Average Cpu Time (ms) Local Average Cpu Time (ms) 𝚫
addglobalcontext 0.002 0.001 -0.000
addaction 0.035 0.031 -0.004
addtiming 0.001 0.001 -0.000
adderror 0.049 0.033 -0.016
startstopsessionreplayrecording 0.935 0.813 -0.122
startview 1.258 0.987 -0.271
logmessage 0.023 0.019 -0.004
🧠 Memory Performance
Action Name Base Consumption Memory (bytes) Local Consumption Memory (bytes) 𝚫 (bytes)
addglobalcontext 8.05 KiB 7.59 KiB -472 B
addaction 39.60 KiB 39.00 KiB -621 B
addtiming 6.79 KiB 6.81 KiB 27 B
adderror 44.86 KiB 43.38 KiB -1511 B
startstopsessionreplayrecording 4.18 KiB 4.50 KiB 333 B
startview 420.58 KiB 425.93 KiB 5.35 KiB
logmessage 39.70 KiB 41.22 KiB 1.53 KiB

🔗 RealWorld

@@ -0,0 +1,23 @@
export function getTimer() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏rename this function to createTimer. The file could be renamed to timer.ts.

Comment on lines +5 to +11
const timer = getTimer()

timer.startTimer()
setTimeout(() => {
timer.stopTimer()
expect(timer.getDuration()).toBeGreaterThan(1000)
}, 1000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏we don't want to actually wait for one second in unit tests. You can use mockClock():

Suggested change
const timer = getTimer()
timer.startTimer()
setTimeout(() => {
timer.stopTimer()
expect(timer.getDuration()).toBeGreaterThan(1000)
}, 1000)
const clock = mockClock()
registerCleanupCallback(clock.cleanup)
const timer = getTimer()
timer.startTimer()
clock.tick(1000)
timer.stopTimer()
expect(timer.getDuration()).toBe(1000)

Comment on lines +2 to +3
let duration: number
let startTime: number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏those types are weak as they might be undefined when used.

Suggested change
let duration: number
let startTime: number
let duration: number | undefined
let startTime: number | undefined

}

function getDuration() {
return duration ? duration : 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏I don't think duration should be 0 if the timer has not be stopped. If the timer is used incorrectly, we might report unexpected durations, and this could have an impact on data analysis. Maybe return undefined instead?

Suggested change
return duration ? duration : 0
return duration

Comment on lines +31 to +35
isFirstRender: isFirstRender.current,
renderPhaseDuration: renderDuration,
effectPhaseDuration: effectDuration,
layoutEffectPhaseDuration: layoutEffectDuration,
componentName,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏properties are usually snake_cased by convention

Suggested change
isFirstRender: isFirstRender.current,
renderPhaseDuration: renderDuration,
effectPhaseDuration: effectDuration,
layoutEffectPhaseDuration: layoutEffectDuration,
componentName,
is_first_render: isFirstRender.current,
render_phase_duration: renderDuration,
effect_phase_duration: effectDuration,
layout_effect_phase_duration: layoutEffectDuration,
component_name: componentName,


const totalRenderTime = renderDuration + effectDuration + layoutEffectDuration

addDurationVital(`${componentName}`, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥜 nitpick: ‏no need to cast this as a string:

Suggested change
addDurationVital(`${componentName}`, {
addDurationVital(componentName, {

Comment on lines +40 to +42
/**
* Send a custom vital tracking this duration
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏remove this comment?

Comment on lines +57 to +74
onRender={() => {
renderTimer.startTimer()
}}
onLayoutEffect={() => {
layoutEffectTimer.startTimer()
}}
onEffect={() => {
effectTimer.startTimer()
}}
/>
{children}
<LifeCycle
onRender={() => {
renderTimer.stopTimer()
}}
onLayoutEffect={() => {
layoutEffectTimer.stopTimer()
}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion:

Suggested change
onRender={() => {
renderTimer.startTimer()
}}
onLayoutEffect={() => {
layoutEffectTimer.startTimer()
}}
onEffect={() => {
effectTimer.startTimer()
}}
/>
{children}
<LifeCycle
onRender={() => {
renderTimer.stopTimer()
}}
onLayoutEffect={() => {
layoutEffectTimer.stopTimer()
}}
onRender={renderTimer.startTimer}
onLayoutEffect={layoutEffectTimer.startTimer}
onEffect={effectTimer.startTimer}
/>
{children}
<LifeCycle
onRender={renderTimer.stopTimer}
onLayoutEffect={layoutEffectTimer.stopTimer}

import { getTimer } from './getTimer'
import { addDurationVital } from './addDurationVital'

export const ReactComponentTracker = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏rename this as UNSTABLE_ReactComponentTracker

}

function stopTimer() {
duration = performance.timeOrigin + performance.now() - startTime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 thought: ‏I was considering to suggest using functions from timeUtils (ex: timeStampNow() etc), similarly to the rest of the SDK including Duration Vitals. But then we would use Date.now() instead of performance.now(), which is somewhat less precise. So using performance.now() here might make sense, but then why not use it in Vitals?

🤔 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏add some unit test for that component

@Miz85 Miz85 force-pushed the nazim/react-timings branch from 5d48861 to 718555e Compare December 19, 2024 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants