feat(runtime-core): config.performance tracing support

This commit is contained in:
Evan You 2020-04-01 21:36:50 -04:00
parent a022b63605
commit e93e426bfa
5 changed files with 126 additions and 24 deletions

View File

@ -18,7 +18,8 @@ module.exports = {
'packages/*/src/**/*.ts', 'packages/*/src/**/*.ts',
'!packages/runtime-test/src/utils/**', '!packages/runtime-test/src/utils/**',
'!packages/template-explorer/**', '!packages/template-explorer/**',
'!packages/size-check/**' '!packages/size-check/**',
'!packages/runtime-core/src/profiling.ts'
], ],
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'], watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],

View File

@ -41,6 +41,7 @@ import {
currentRenderingInstance, currentRenderingInstance,
markAttrsAccessed markAttrsAccessed
} from './componentRenderUtils' } from './componentRenderUtils'
import { startMeasure, endMeasure } from './profiling'
export type Data = { [key: string]: unknown } export type Data = { [key: string]: unknown }
@ -108,6 +109,7 @@ export type RenderFunction = {
} }
export interface ComponentInternalInstance { export interface ComponentInternalInstance {
uid: number
type: Component type: Component
parent: ComponentInternalInstance | null parent: ComponentInternalInstance | null
appContext: AppContext appContext: AppContext
@ -176,6 +178,8 @@ export interface ComponentInternalInstance {
const emptyAppContext = createAppContext() const emptyAppContext = createAppContext()
let uid = 0
export function createComponentInstance( export function createComponentInstance(
vnode: VNode, vnode: VNode,
parent: ComponentInternalInstance | null, parent: ComponentInternalInstance | null,
@ -185,6 +189,7 @@ export function createComponentInstance(
const appContext = const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext (parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = { const instance: ComponentInternalInstance = {
uid: uid++,
vnode, vnode,
parent, parent,
appContext, appContext,
@ -383,7 +388,7 @@ function setupStatefulComponent(
handleSetupResult(instance, setupResult, parentSuspense, isSSR) handleSetupResult(instance, setupResult, parentSuspense, isSSR)
} }
} else { } else {
finishComponentSetup(instance, parentSuspense, isSSR) finishComponentSetup(instance, isSSR)
} }
} }
@ -413,7 +418,7 @@ export function handleSetupResult(
}` }`
) )
} }
finishComponentSetup(instance, parentSuspense, isSSR) finishComponentSetup(instance, isSSR)
} }
type CompileFunction = ( type CompileFunction = (
@ -430,7 +435,6 @@ export function registerRuntimeCompiler(_compile: any) {
function finishComponentSetup( function finishComponentSetup(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
isSSR: boolean isSSR: boolean
) { ) {
const Component = instance.type as ComponentOptions const Component = instance.type as ComponentOptions
@ -442,9 +446,15 @@ function finishComponentSetup(
} }
} else if (!instance.render) { } else if (!instance.render) {
if (compile && Component.template && !Component.render) { if (compile && Component.template && !Component.render) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
Component.render = compile(Component.template, { Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement || NO isCustomElement: instance.appContext.config.isCustomElement || NO
}) })
if (__DEV__ && instance.appContext.config.performance) {
endMeasure(instance, `compile`)
}
// mark the function as runtime compiled // mark the function as runtime compiled
;(Component.render as RenderFunction)._rc = true ;(Component.render as RenderFunction)._rc = true
} }
@ -529,3 +539,23 @@ export function recordInstanceBoundEffect(effect: ReactiveEffect) {
;(currentInstance.effects || (currentInstance.effects = [])).push(effect) ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
} }
} }
const classifyRE = /(?:^|[-_])(\w)/g
const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
export function formatComponentName(
Component: Component,
file?: string
): string {
let name = isFunction(Component)
? Component.displayName || Component.name
: Component.name
if (!name && file) {
const match = file.match(/([^/\\]+)\.vue$/)
if (match) {
name = match[1]
}
}
return name ? classify(name) : 'Anonymous'
}

View File

@ -0,0 +1,42 @@
import { ComponentInternalInstance, formatComponentName } from './component'
let supported: boolean
let perf: any
export function startMeasure(
instance: ComponentInternalInstance,
type: string
) {
if (!instance.appContext) debugger
if (instance.appContext.config.performance && isSupported()) {
perf.mark(`vue-${type}-${instance.uid}`)
}
}
export function endMeasure(instance: ComponentInternalInstance, type: string) {
if (instance.appContext.config.performance && isSupported()) {
const startTag = `vue-${type}-${instance.uid}`
const endTag = startTag + `:end`
perf.mark(endTag)
perf.measure(
`<${formatComponentName(instance.type)}> ${type}`,
startTag,
endTag
)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
}
}
function isSupported() {
if (supported !== undefined) {
return supported
}
if (typeof window !== 'undefined' && window.performance) {
supported = true
perf = window.performance
} else {
supported = false
}
return supported
}

View File

@ -68,6 +68,7 @@ import {
} from './errorHandling' } from './errorHandling'
import { createHydrationFunctions, RootHydrateFunction } from './hydration' import { createHydrationFunctions, RootHydrateFunction } from './hydration'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { startMeasure, endMeasure } from './profiling'
const __HMR__ = __BUNDLER__ && __DEV__ const __HMR__ = __BUNDLER__ && __DEV__
@ -1031,6 +1032,7 @@ function baseCreateRenderer(
if (__DEV__) { if (__DEV__) {
pushWarningContext(initialVNode) pushWarningContext(initialVNode)
startMeasure(instance, `mount`)
} }
// inject renderer internals for keepAlive // inject renderer internals for keepAlive
@ -1041,7 +1043,13 @@ function baseCreateRenderer(
} }
// resolve props and slots for setup context // resolve props and slots for setup context
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance, parentSuspense) setupComponent(instance, parentSuspense)
if (__DEV__) {
endMeasure(instance, `init`)
}
// setup() is async. This component relies on async logic to be resolved // setup() is async. This component relies on async logic to be resolved
// before proceeding // before proceeding
@ -1072,6 +1080,7 @@ function baseCreateRenderer(
if (__DEV__) { if (__DEV__) {
popWarningContext() popWarningContext()
endMeasure(instance, `mount`)
} }
} }
@ -1089,7 +1098,13 @@ function baseCreateRenderer(
let vnodeHook: VNodeHook | null | undefined let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode const { el, props } = initialVNode
const { bm, m, a, parent } = instance const { bm, m, a, parent } = instance
if (__DEV__) {
startMeasure(instance, `render`)
}
const subTree = (instance.subTree = renderComponentRoot(instance)) const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
// beforeMount hook // beforeMount hook
if (bm) { if (bm) {
invokeHooks(bm) invokeHooks(bm)
@ -1099,6 +1114,9 @@ function baseCreateRenderer(
invokeVNodeHook(vnodeHook, parent, initialVNode) invokeVNodeHook(vnodeHook, parent, initialVNode)
} }
if (el && hydrateNode) { if (el && hydrateNode) {
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
// vnode has adopted host node - perform hydration instead of mount. // vnode has adopted host node - perform hydration instead of mount.
hydrateNode( hydrateNode(
initialVNode.el as Node, initialVNode.el as Node,
@ -1106,7 +1124,13 @@ function baseCreateRenderer(
instance, instance,
parentSuspense parentSuspense
) )
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
} else { } else {
if (__DEV__) {
startMeasure(instance, `patch`)
}
patch( patch(
null, null,
subTree, subTree,
@ -1116,6 +1140,9 @@ function baseCreateRenderer(
parentSuspense, parentSuspense,
isSVG isSVG
) )
if (__DEV__) {
endMeasure(instance, `patch`)
}
initialVNode.el = subTree.el initialVNode.el = subTree.el
} }
// mounted hook // mounted hook
@ -1151,7 +1178,13 @@ function baseCreateRenderer(
} else { } else {
next = vnode next = vnode
} }
if (__DEV__) {
startMeasure(instance, `render`)
}
const nextTree = renderComponentRoot(instance) const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree const prevTree = instance.subTree
instance.subTree = nextTree instance.subTree = nextTree
next.el = vnode.el next.el = vnode.el
@ -1168,6 +1201,9 @@ function baseCreateRenderer(
if (instance.refs !== EMPTY_OBJ) { if (instance.refs !== EMPTY_OBJ) {
instance.refs = {} instance.refs = {}
} }
if (__DEV__) {
startMeasure(instance, `patch`)
}
patch( patch(
prevTree, prevTree,
nextTree, nextTree,
@ -1179,6 +1215,9 @@ function baseCreateRenderer(
parentSuspense, parentSuspense,
isSVG isSVG
) )
if (__DEV__) {
endMeasure(instance, `patch`)
}
next.el = nextTree.el next.el = nextTree.el
if (next === null) { if (next === null) {
// self-triggered update. In case of HOC, update parent component // self-triggered update. In case of HOC, update parent component

View File

@ -1,5 +1,10 @@
import { VNode } from './vnode' import { VNode } from './vnode'
import { Data, ComponentInternalInstance, Component } from './component' import {
Data,
ComponentInternalInstance,
Component,
formatComponentName
} from './component'
import { isString, isFunction } from '@vue/shared' import { isString, isFunction } from '@vue/shared'
import { toRaw, isRef, pauseTracking, resetTracking } from '@vue/reactivity' import { toRaw, isRef, pauseTracking, resetTracking } from '@vue/reactivity'
import { callWithErrorHandling, ErrorCodes } from './errorHandling' import { callWithErrorHandling, ErrorCodes } from './errorHandling'
@ -43,7 +48,10 @@ export function warn(msg: string, ...args: any[]) {
msg + args.join(''), msg + args.join(''),
instance && instance.proxy, instance && instance.proxy,
trace trace
.map(({ vnode }) => `at <${formatComponentName(vnode)}>`) .map(
({ vnode }) =>
`at <${formatComponentName(vnode.type as Component)}>`
)
.join('\n'), .join('\n'),
trace trace
] ]
@ -111,24 +119,6 @@ function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
: [open + close, rootLabel] : [open + close, rootLabel]
} }
const classifyRE = /(?:^|[-_])(\w)/g
const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
function formatComponentName(vnode: ComponentVNode, file?: string): string {
const Component = vnode.type as Component
let name = isFunction(Component)
? Component.displayName || Component.name
: Component.name
if (!name && file) {
const match = file.match(/([^/\\]+)\.vue$/)
if (match) {
name = match[1]
}
}
return name ? classify(name) : 'Anonymous'
}
function formatProps(props: Data): any[] { function formatProps(props: Data): any[] {
const res: any[] = [] const res: any[] = []
const keys = Object.keys(props) const keys = Object.keys(props)