import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api' import type { App, ComponentInstance } from '@vue/devtools-api' import { BridgeSubscriptions, SharedData, raf } from '@vue-devtools/shared-utils' import { addTimelineEvent } from './timeline' import { getAppRecord } from './app' import { getComponentId, sendComponentTreeData } from './component' import { isSubscribed } from './util/subscriptions' const markEndQueue = new Map() export async function performanceMarkStart( app: App, uid: number, instance: ComponentInstance, type: string, time: number, ctx: BackendContext, ) { try { if (!SharedData.performanceMonitoringEnabled) { return } const appRecord = await getAppRecord(app, ctx) if (!appRecord) { return } const componentName = await appRecord.backend.api.getComponentName(instance) const groupId = ctx.perfUniqueGroupId++ const groupKey = `${uid}-${type}` appRecord.perfGroupIds.set(groupKey, { groupId, time }) await addTimelineEvent({ layerId: 'performance', event: { time, data: { component: componentName, type, measure: 'start', }, title: componentName, subtitle: type, groupId, }, }, app, ctx) if (markEndQueue.has(groupKey)) { const { app, uid, instance, type, time, } = markEndQueue.get(groupKey) markEndQueue.delete(groupKey) await performanceMarkEnd( app, uid, instance, type, time, ctx, ) } } catch (e) { if (SharedData.debugInfo) { console.error(e) } } } export async function performanceMarkEnd( app: App, uid: number, instance: ComponentInstance, type: string, time: number, ctx: BackendContext, ) { try { if (!SharedData.performanceMonitoringEnabled) { return } const appRecord = await getAppRecord(app, ctx) if (!appRecord) { return } const componentName = await appRecord.backend.api.getComponentName(instance) const groupKey = `${uid}-${type}` const groupInfo = appRecord.perfGroupIds.get(groupKey) if (!groupInfo) { markEndQueue.set(groupKey, { app, uid, instance, type, time, }) return } const { groupId, time: startTime } = groupInfo const duration = time - startTime await addTimelineEvent({ layerId: 'performance', event: { time, data: { component: componentName, type, measure: 'end', duration: { _custom: { type: 'Duration', value: duration, display: `${duration} ms`, }, }, }, title: componentName, subtitle: type, groupId, }, }, app, ctx) // Mark on component const tooSlow = duration > 10 if (tooSlow || instance.__VUE_DEVTOOLS_SLOW__) { let change = false if (tooSlow && !instance.__VUE_DEVTOOLS_SLOW__) { instance.__VUE_DEVTOOLS_SLOW__ = { duration: null, measures: {}, } } const data = instance.__VUE_DEVTOOLS_SLOW__ if (tooSlow && (data.duration == null || data.duration < duration)) { data.duration = duration change = true } if (data.measures[type] == null || data.measures[type] < duration) { data.measures[type] = duration change = true } if (change) { // Update component tree const id = await getComponentId(app, uid, instance, ctx) if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) { raf(() => { sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, false, ctx) }) } } } } catch (e) { if (SharedData.debugInfo) { console.error(e) } } } export function handleAddPerformanceTag(backend: DevtoolsBackend, _ctx: BackendContext) { backend.api.on.visitComponentTree((payload) => { if (payload.componentInstance.__VUE_DEVTOOLS_SLOW__) { const { duration, measures } = payload.componentInstance.__VUE_DEVTOOLS_SLOW__ let tooltip = '
' for (const type in measures) { const d = measures[type] tooltip += `
${type}
${Math.round(d * 1000) / 1000} ms
` } tooltip += '
' payload.treeNode.tags.push({ backgroundColor: duration > 30 ? 0xF87171 : 0xFBBF24, textColor: 0x000000, label: `${Math.round(duration * 1000) / 1000} ms`, tooltip, }) } }) }