]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-core): config.performance tracing support
authorEvan You <yyx990803@gmail.com>
Thu, 2 Apr 2020 01:36:50 +0000 (21:36 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 2 Apr 2020 01:36:50 +0000 (21:36 -0400)
jest.config.js
packages/runtime-core/src/component.ts
packages/runtime-core/src/profiling.ts [new file with mode: 0644]
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/warning.ts

index aeda080216d85ea6fab76db32631a893e5d72de3..622dd1c9609b56e26d7a755b0629174fb1fdacca 100644 (file)
@@ -18,7 +18,8 @@ module.exports = {
     'packages/*/src/**/*.ts',
     '!packages/runtime-test/src/utils/**',
     '!packages/template-explorer/**',
-    '!packages/size-check/**'
+    '!packages/size-check/**',
+    '!packages/runtime-core/src/profiling.ts'
   ],
   watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
   moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
index bf5aaeeb7061f46fbea53ec0924f18c49a162307..6bed917082aee3034ad1ef40a19f3c25e8691048 100644 (file)
@@ -41,6 +41,7 @@ import {
   currentRenderingInstance,
   markAttrsAccessed
 } from './componentRenderUtils'
+import { startMeasure, endMeasure } from './profiling'
 
 export type Data = { [key: string]: unknown }
 
@@ -108,6 +109,7 @@ export type RenderFunction = {
 }
 
 export interface ComponentInternalInstance {
+  uid: number
   type: Component
   parent: ComponentInternalInstance | null
   appContext: AppContext
@@ -176,6 +178,8 @@ export interface ComponentInternalInstance {
 
 const emptyAppContext = createAppContext()
 
+let uid = 0
+
 export function createComponentInstance(
   vnode: VNode,
   parent: ComponentInternalInstance | null,
@@ -185,6 +189,7 @@ export function createComponentInstance(
   const appContext =
     (parent ? parent.appContext : vnode.appContext) || emptyAppContext
   const instance: ComponentInternalInstance = {
+    uid: uid++,
     vnode,
     parent,
     appContext,
@@ -383,7 +388,7 @@ function setupStatefulComponent(
       handleSetupResult(instance, setupResult, parentSuspense, isSSR)
     }
   } else {
-    finishComponentSetup(instance, parentSuspense, isSSR)
+    finishComponentSetup(instance, isSSR)
   }
 }
 
@@ -413,7 +418,7 @@ export function handleSetupResult(
       }`
     )
   }
-  finishComponentSetup(instance, parentSuspense, isSSR)
+  finishComponentSetup(instance, isSSR)
 }
 
 type CompileFunction = (
@@ -430,7 +435,6 @@ export function registerRuntimeCompiler(_compile: any) {
 
 function finishComponentSetup(
   instance: ComponentInternalInstance,
-  parentSuspense: SuspenseBoundary | null,
   isSSR: boolean
 ) {
   const Component = instance.type as ComponentOptions
@@ -442,9 +446,15 @@ function finishComponentSetup(
     }
   } else if (!instance.render) {
     if (compile && Component.template && !Component.render) {
+      if (__DEV__) {
+        startMeasure(instance, `compile`)
+      }
       Component.render = compile(Component.template, {
         isCustomElement: instance.appContext.config.isCustomElement || NO
       })
+      if (__DEV__ && instance.appContext.config.performance) {
+        endMeasure(instance, `compile`)
+      }
       // mark the function as runtime compiled
       ;(Component.render as RenderFunction)._rc = true
     }
@@ -529,3 +539,23 @@ export function recordInstanceBoundEffect(effect: ReactiveEffect) {
     ;(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'
+}
diff --git a/packages/runtime-core/src/profiling.ts b/packages/runtime-core/src/profiling.ts
new file mode 100644 (file)
index 0000000..d841cd8
--- /dev/null
@@ -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
+}
index d774b473c39abeb280c5ffee73df9b901a8c918a..6b571358675153db9ec2778785a49c6c1875bd5d 100644 (file)
@@ -68,6 +68,7 @@ import {
 } from './errorHandling'
 import { createHydrationFunctions, RootHydrateFunction } from './hydration'
 import { invokeDirectiveHook } from './directives'
+import { startMeasure, endMeasure } from './profiling'
 
 const __HMR__ = __BUNDLER__ && __DEV__
 
@@ -1031,6 +1032,7 @@ function baseCreateRenderer(
 
     if (__DEV__) {
       pushWarningContext(initialVNode)
+      startMeasure(instance, `mount`)
     }
 
     // inject renderer internals for keepAlive
@@ -1041,7 +1043,13 @@ function baseCreateRenderer(
     }
 
     // resolve props and slots for setup context
+    if (__DEV__) {
+      startMeasure(instance, `init`)
+    }
     setupComponent(instance, parentSuspense)
+    if (__DEV__) {
+      endMeasure(instance, `init`)
+    }
 
     // setup() is async. This component relies on async logic to be resolved
     // before proceeding
@@ -1072,6 +1080,7 @@ function baseCreateRenderer(
 
     if (__DEV__) {
       popWarningContext()
+      endMeasure(instance, `mount`)
     }
   }
 
@@ -1089,7 +1098,13 @@ function baseCreateRenderer(
         let vnodeHook: VNodeHook | null | undefined
         const { el, props } = initialVNode
         const { bm, m, a, parent } = instance
+        if (__DEV__) {
+          startMeasure(instance, `render`)
+        }
         const subTree = (instance.subTree = renderComponentRoot(instance))
+        if (__DEV__) {
+          endMeasure(instance, `render`)
+        }
         // beforeMount hook
         if (bm) {
           invokeHooks(bm)
@@ -1099,6 +1114,9 @@ function baseCreateRenderer(
           invokeVNodeHook(vnodeHook, parent, initialVNode)
         }
         if (el && hydrateNode) {
+          if (__DEV__) {
+            startMeasure(instance, `hydrate`)
+          }
           // vnode has adopted host node - perform hydration instead of mount.
           hydrateNode(
             initialVNode.el as Node,
@@ -1106,7 +1124,13 @@ function baseCreateRenderer(
             instance,
             parentSuspense
           )
+          if (__DEV__) {
+            endMeasure(instance, `hydrate`)
+          }
         } else {
+          if (__DEV__) {
+            startMeasure(instance, `patch`)
+          }
           patch(
             null,
             subTree,
@@ -1116,6 +1140,9 @@ function baseCreateRenderer(
             parentSuspense,
             isSVG
           )
+          if (__DEV__) {
+            endMeasure(instance, `patch`)
+          }
           initialVNode.el = subTree.el
         }
         // mounted hook
@@ -1151,7 +1178,13 @@ function baseCreateRenderer(
         } else {
           next = vnode
         }
+        if (__DEV__) {
+          startMeasure(instance, `render`)
+        }
         const nextTree = renderComponentRoot(instance)
+        if (__DEV__) {
+          endMeasure(instance, `render`)
+        }
         const prevTree = instance.subTree
         instance.subTree = nextTree
         next.el = vnode.el
@@ -1168,6 +1201,9 @@ function baseCreateRenderer(
         if (instance.refs !== EMPTY_OBJ) {
           instance.refs = {}
         }
+        if (__DEV__) {
+          startMeasure(instance, `patch`)
+        }
         patch(
           prevTree,
           nextTree,
@@ -1179,6 +1215,9 @@ function baseCreateRenderer(
           parentSuspense,
           isSVG
         )
+        if (__DEV__) {
+          endMeasure(instance, `patch`)
+        }
         next.el = nextTree.el
         if (next === null) {
           // self-triggered update. In case of HOC, update parent component
index ef6168d242b04f3715675da2e40818f34e43963f..c31ecfed912264437e4236df2e4c5d0f380fccb0 100644 (file)
@@ -1,5 +1,10 @@
 import { VNode } from './vnode'
-import { Data, ComponentInternalInstance, Component } from './component'
+import {
+  Data,
+  ComponentInternalInstance,
+  Component,
+  formatComponentName
+} from './component'
 import { isString, isFunction } from '@vue/shared'
 import { toRaw, isRef, pauseTracking, resetTracking } from '@vue/reactivity'
 import { callWithErrorHandling, ErrorCodes } from './errorHandling'
@@ -43,7 +48,10 @@ export function warn(msg: string, ...args: any[]) {
         msg + args.join(''),
         instance && instance.proxy,
         trace
-          .map(({ vnode }) => `at <${formatComponentName(vnode)}>`)
+          .map(
+            ({ vnode }) =>
+              `at <${formatComponentName(vnode.type as Component)}>`
+          )
           .join('\n'),
         trace
       ]
@@ -111,24 +119,6 @@ function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
     : [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[] {
   const res: any[] = []
   const keys = Object.keys(props)