Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
feat(Provider): Rendering performance telemetry (#2079)
Browse files Browse the repository at this point in the history
* feat(Provider): Measure rendering performance stats

* - reset, enable, disable stats

* - rename PerformanceStats to Telemetry

* add min and max per component

* update changelog
  • Loading branch information
miroslavstastny authored and levithomason committed Nov 9, 2019
1 parent 8097e43 commit b02b1f6
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Performance
- Add rendering performance telemetry @miroslavstastny ([#2079](https://github.com/microsoft/fluent-ui-react/pull/2079))

<!--------------------------------[ v0.40.2 ]------------------------------- -->
## [v0.40.2](https://github.com/stardust-ui/react/tree/v0.40.2) (2019-10-30)
[Compare changes](https://github.com/stardust-ui/react/compare/v0.40.1...v0.40.2)
Expand Down
36 changes: 34 additions & 2 deletions docs/src/prototypes/customToolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useSelectKnob,
KnobInspector,
} from '@stardust-ui/docs-components'
import { Provider, Flex, themes, mergeThemes } from '@stardust-ui/react'
import { Provider, Flex, themes, mergeThemes, Telemetry } from '@stardust-ui/react'

import { darkThemeOverrides } from './darkThemeOverrides'
import { highContrastThemeOverrides } from './highContrastThemeOverrides'
Expand Down Expand Up @@ -58,21 +58,53 @@ const CustomToolbarPrototype: React.FunctionComponent = () => {
const [currentSlide, setCurrentSlide] = React.useState(23)
const totalSlides = 34

const [telemetryEnabled] = useBooleanKnob({ name: 'telemetryEnabled', initialValue: true })
const telemetryRef = React.useRef<Telemetry>()

let theme = {}
if (themeName === 'teamsDark') {
theme = mergeThemes(themes.teamsDark, darkThemeOverrides)
} else if (themeName === 'teamsHighContrast') {
theme = mergeThemes(themes.teamsHighContrast, darkThemeOverrides, highContrastThemeOverrides)
}

React.useEffect(() => {
performance.measure('render-custom-toolbar', 'render-custom-toolbar')
const telemetry = telemetryRef.current
if (!telemetryEnabled || !telemetry) {
return
}

telemetry.enabled = false

const totals = _.reduce(
telemetry.performance,
(acc, next) => {
acc.count += next.count
acc.msTotal += next.msTotal
return acc
},
{ count: 0, msTotal: 0 },
)

console.log(`Rendered ${totals.count} Stardust components in ${totals.msTotal} ms`)
console.table(telemetry.performance)
})

if (telemetryRef.current) {
telemetryRef.current.enabled = telemetryEnabled
telemetryRef.current.reset()
}
performance.mark('render-custom-toolbar')

return (
<div style={{ height: '100vh' }}>
<Flex column fill>
<KnobsSnippet>
<KnobInspector />
</KnobsSnippet>

<Provider theme={theme} rtl={rtl}>
<Provider theme={theme} rtl={rtl} {...(telemetryEnabled ? { telemetryRef } : undefined)}>
<Flex
hAlign="center"
styles={{
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/components/Provider/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IStyle } from 'fela'
import * as _ from 'lodash'
import * as customPropTypes from '@stardust-ui/react-proptypes'
import * as PropTypes from 'prop-types'
import * as React from 'react'
// @ts-ignore
Expand Down Expand Up @@ -28,6 +29,7 @@ import {
withSafeTypeForAs,
} from '../../types'
import mergeContexts from '../../lib/mergeProviderContexts'
import Telemetry from '../../lib/Telemetry'

export interface ProviderProps extends ChildrenComponentProps {
renderer?: Renderer
Expand All @@ -37,6 +39,7 @@ export interface ProviderProps extends ChildrenComponentProps {
target?: Document
theme?: ThemeInput
variables?: ComponentVariablesInput
telemetryRef?: React.Ref<Telemetry>
}

/**
Expand Down Expand Up @@ -76,6 +79,7 @@ class Provider extends React.Component<WithAsProp<ProviderProps>> {
disableAnimations: PropTypes.bool,
children: PropTypes.node.isRequired,
target: PropTypes.object,
telemetryRef: customPropTypes.ref,
}

static defaultProps = {
Expand All @@ -89,6 +93,8 @@ class Provider extends React.Component<WithAsProp<ProviderProps>> {
outgoingContext: ProviderContextPrepared
staticStylesRendered: boolean = false

telemetry: Telemetry

renderStaticStyles = (renderer: Renderer, mergedTheme: ThemePrepared) => {
const { siteVariables } = mergedTheme
const { staticStyles } = this.props.theme
Expand Down Expand Up @@ -153,14 +159,27 @@ class Provider extends React.Component<WithAsProp<ProviderProps>> {
target,
theme,
variables,
telemetryRef,
...unhandledProps
} = this.props

if (telemetryRef) {
if (!this.telemetry) {
this.telemetry = new Telemetry()
}

telemetryRef['current'] = this.telemetry
} else if (this.telemetry) {
delete this.telemetry
}

const inputContext: ProviderContextInput = {
theme,
rtl,
disableAnimations,
renderer,
target,
telemetry: this.telemetry,
}

const incomingContext: ProviderContextPrepared = overwrite ? {} : this.context
Expand Down
20 changes: 20 additions & 0 deletions packages/react/src/lib/Telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type ComponentPerfStats = {
count: number
msTotal: number
msMin: number
msMax: number
}

export default class Telemetry {
performance: Record<string, ComponentPerfStats>
enabled: boolean

constructor() {
this.performance = {}
this.enabled = true
}

reset() {
this.performance = {}
}
}
1 change: 1 addition & 0 deletions packages/react/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export * from './commonPropInterfaces'
export { commonPropTypes }

export { default as withDebugId } from './withDebugId'
export { default as Telemetry } from './Telemetry'
8 changes: 2 additions & 6 deletions packages/react/src/lib/mergeProviderContexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ const mergeProviderContexts = (
},
rtl: false,
disableAnimations: false,
originalThemes: [],
target: document, // eslint-disable-line no-undef
telemetry: undefined,
_internal_resolvedComponentVariables: {},
renderer: undefined,
}
Expand Down Expand Up @@ -84,11 +84,7 @@ const mergeProviderContexts = (
acc.disableAnimations = mergedDisableAnimations
}

const contextOriginalThemes = (next as ProviderContextPrepared).originalThemes
? (next as ProviderContextPrepared).originalThemes
: [next.theme]

acc.originalThemes = [...acc.originalThemes, ...contextOriginalThemes]
acc.telemetry = next.telemetry || acc.telemetry

return acc
},
Expand Down
35 changes: 33 additions & 2 deletions packages/react/src/lib/renderComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import createAnimationStyles from './createAnimationStyles'
import { isEnabled as isDebugEnabled } from './debug/debugEnabled'
import { DebugData } from './debug/debugData'
import withDebugId from './withDebugId'
import Telemetry from './Telemetry'

export interface RenderResultConfig<P> {
ElementType: React.ElementType<P>
Expand Down Expand Up @@ -175,9 +176,12 @@ const renderComponent = <P extends {}>(
renderer = null,
rtl = false,
theme = emptyTheme,
telemetry = undefined as Telemetry,
_internal_resolvedComponentVariables: resolvedComponentVariables = {},
} = context || {}

const startTime = telemetry && telemetry.enabled ? performance.now() : 0

const ElementType = getElementType(props) as React.ReactType<P>
const stateAndProps = { ...state, ...props }

Expand Down Expand Up @@ -296,11 +300,38 @@ const renderComponent = <P extends {}>(
})
}

let result
if (accessibility.focusZone) {
return renderWithFocusZone(render, accessibility.focusZone, resolvedConfig)
result = renderWithFocusZone(render, accessibility.focusZone, resolvedConfig)
} else {
result = render(resolvedConfig)
}

if (telemetry && telemetry.enabled) {
const duration = performance.now() - startTime

if (telemetry.performance[displayName]) {
telemetry.performance[displayName].count++
telemetry.performance[displayName].msTotal += duration
telemetry.performance[displayName].msMin = Math.min(
duration,
telemetry.performance[displayName].msMin,
)
telemetry.performance[displayName].msMax = Math.max(
duration,
telemetry.performance[displayName].msMax,
)
} else {
telemetry.performance[displayName] = {
count: 1,
msTotal: duration,
msMin: duration,
msMax: duration,
}
}
}

return render(resolvedConfig)
return result
}

export default renderComponent
4 changes: 3 additions & 1 deletion packages/react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import * as React from 'react'
import { ThemeInput, Renderer, ThemePrepared } from './themes/types'
import Telemetry from './lib/Telemetry'

export type Extendable<T, V = any> = T & {
[key: string]: V
Expand Down Expand Up @@ -159,6 +160,7 @@ export interface ProviderContextInput {
disableAnimations?: boolean
target?: Document
theme?: ThemeInput
telemetry?: Telemetry
}

export interface ProviderContextPrepared {
Expand All @@ -167,6 +169,6 @@ export interface ProviderContextPrepared {
disableAnimations: boolean
target: Document
theme: ThemePrepared
originalThemes: (ThemeInput | undefined)[]
telemetry: Telemetry | undefined
_internal_resolvedComponentVariables: Record<string, object>
}

0 comments on commit b02b1f6

Please sign in to comment.