Skip to content

Commit

Permalink
feat: performance tab (#733)
Browse files Browse the repository at this point in the history
* feat: mvp for framerate timeline

* feat: framerate markers

* fix: framerate graph improvements

* feat: better framerate graph markers design

* feat: UX tweaks

* fix: framerate graph auto scroll

* fix: framrate graph scroll to end when mounted

* feat: component render metrics
  • Loading branch information
Akryum authored Aug 5, 2018
1 parent c5de65f commit 9f0cf3f
Show file tree
Hide file tree
Showing 25 changed files with 1,501 additions and 33 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@vue/ui": "^0.5.1",
"autoprefixer": "^9.0.2",
"circular-json-es6": "^2.0.1",
"d3": "^5.5.0",
"lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0",
"lru-cache": "^4.1.3",
Expand Down
3 changes: 2 additions & 1 deletion shells/createConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module.exports = (config, target = { chrome: 52, firefox: 48 }) => {
alias: {
src: path.resolve(__dirname, '../src'),
views: path.resolve(__dirname, '../src/devtools/views'),
components: path.resolve(__dirname, '../src/devtools/components')
components: path.resolve(__dirname, '../src/devtools/components'),
filters: path.resolve(__dirname, '../src/devtools/filters')
}
},
module: {
Expand Down
6 changes: 6 additions & 0 deletions shells/dev/target/EventChild.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<br>

<button @click="emitManyEvents">Emit a lot of events</button>
<button @click="emitAndCommit">Emit and event and commit a mutation</button>
</div>
</template>

Expand Down Expand Up @@ -45,6 +46,11 @@ export default {
for (let i = 0; i < 10000; i++) {
this.$emit('event', i)
}
},
emitAndCommit () {
this.$emit('event-1', 'foobar')
this.$store.commit('DECREMENT', 'barfoo')
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { highlight, unHighlight, getInstanceOrVnodeRect } from './highlighter'
import { initVuexBackend } from './vuex'
import { initEventsBackend } from './events'
import { initPerfBackend } from './perf'
import { findRelatedComponent } from './utils'
import { stringify, classify, camelize, set, parse, getComponentName } from '../util'
import ComponentSelector from './component-selector'
Expand Down Expand Up @@ -144,7 +145,12 @@ function connect (Vue) {
'background:transparent'
)

setTimeout(scan, 0)
setTimeout(() => {
scan()

// perf
initPerfBackend(Vue, bridge, instanceMap)
}, 0)
}

export function findInstanceOrVnode (id) {
Expand Down
147 changes: 147 additions & 0 deletions src/backend/perf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import SharedData, { watch } from 'src/shared-data'
import { getComponentName } from 'src/util'

const COMPONENT_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroyed',
'destroyed'
]

const RENDER_HOOKS = {
beforeMount: { after: 'mountRender' },
mounted: { before: 'mountRender' },
beforeUpdate: { after: 'updateRender' },
updated: { before: 'updateRender' }
}

let frames = 0
let frameTime
let secondsTimer
let bridge

let componentMetrics

export function initPerfBackend (Vue, _bridge, instanceMap) {
bridge = _bridge

// Global mixin
Vue.mixin({
beforeCreate () {
applyHooks(this)
}
})

// Apply to existing components
instanceMap.forEach(applyHooks)

watch('recordPerf', value => {
if (value) {
startRecording()
} else {
stopRecording()
}
})
}

function startRecording () {
frames = 0
frameTime = performance.now()
secondsTimer = setInterval(frameInterval, 500)
componentMetrics = {}
requestAnimationFrame(frame)
}

function stopRecording () {
clearInterval(secondsTimer)
}

function frame () {
frames++
if (SharedData.recordPerf) {
requestAnimationFrame(frame)
}
}

function frameInterval () {
const metric = {
type: 'fps',
time: Date.now(),
start: frameTime,
end: frameTime = performance.now()
}
metric.value = Math.round(frames / (metric.end - metric.start) * 1000)
frames = 0
bridge.send('perf:add-metric', metric)
}

function applyHooks (vm) {
if (vm.$options.$_devtoolsPerfHooks) return
vm.$options.$_devtoolsPerfHooks = true

const renderMetrics = {}

for (const hook of COMPONENT_HOOKS) {
const renderHook = RENDER_HOOKS[hook]

const handler = function () {
if (SharedData.recordPerf) {
// Before
const time = performance.now()
if (renderHook && renderHook.before) {
// Render hook ends before one hook
const metric = renderMetrics[renderHook.before]
metric.end = time
addComponentMetric(vm.$options, renderHook.before, metric.start, metric.end)
}

// After
this.$once(`hook:${hook}`, () => {
const newTime = performance.now()
addComponentMetric(vm.$options, hook, time, newTime)
if (renderHook && renderHook.after) {
// Render hook starts after one hook
renderMetrics[renderHook.after] = {
start: newTime,
end: 0
}
}
})
}
}
const currentValue = vm.$options[hook]
if (Array.isArray(currentValue)) {
vm.$options[hook] = [handler, ...currentValue]
} else if (typeof currentValue === 'function') {
vm.$options[hook] = [handler, currentValue]
} else {
vm.$options[hook] = [handler]
}
}
}

function addComponentMetric (options, type, start, end) {
const duration = end - start
const name = getComponentName(options)

const metric = componentMetrics[name] = componentMetrics[name] || {
id: name,
hooks: {},
totalTime: 0
}

const hook = metric.hooks[type] = metric.hooks[type] || {
count: 0,
totalTime: 0
}
hook.count++
hook.totalTime += duration

metric.totalTime += duration

bridge.send('perf:upsert-metric', { type: 'componentRender', data: metric })
}
1 change: 0 additions & 1 deletion src/backend/vuex.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Vue from 'vue'

export function initVuexBackend (hook, bridge) {
const store = hook.store
console.log(store)

let originalVm = store._vm
const snapshotsVm = new Vue({
Expand Down
17 changes: 15 additions & 2 deletions src/devtools/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@
>
Events
</VueGroupButton>
<VueGroupButton
v-tooltip="$t('App.perf.tooltip')"
:class="{
'icon-button': !$responsive.wide
}"
value="perf"
icon-left="assessment"
class="settings-tab flat"
>
Performance
</VueGroupButton>
<VueGroupButton
v-tooltip="$t('App.settings.tooltip')"
:class="{
Expand Down Expand Up @@ -150,6 +161,9 @@ export default {
this.$router.push({ name: 'events' })
return false
} else if (code === 'Digit4') {
this.$router.push({ name: 'perf' })
return false
} else if (code === 'Digit5') {
this.$router.push({ name: 'settings' })
return false
} else if (key === 'p' || code === 'KeyP') {
Expand All @@ -173,7 +187,7 @@ export default {
},
routeModel: {
get () { return this.$route.name },
get () { return this.$route.matched[0].name },
set (value) {
this.$router.push({ name: value })
}
Expand Down Expand Up @@ -247,7 +261,6 @@ export default {
.header
display flex
align-items center
border-bottom 1px solid $border-color
box-shadow 0 0 8px rgba(0, 0, 0, 0.15)
font-size 14px
position relative
Expand Down
10 changes: 9 additions & 1 deletion src/devtools/components/ActionHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<style lang="stylus" scoped>
.action-header
display flex
align-items stretch
align-items center
padding 0 10px
font-size 12px
border-bottom 1px solid $border-color
Expand All @@ -17,6 +17,10 @@
height 50px
.vue-ui-dark-mode &
border-bottom 1px solid $dark-border-color
&.no-search
.button
&:first-of-type
margin-left 0
.title
display flex
Expand Down Expand Up @@ -71,6 +75,10 @@
@media (min-width: $wide)
display inline
.vue-ui-button
&:not(:last-child)
margin-right 6px
.search
display flex
align-items center
Expand Down
3 changes: 3 additions & 0 deletions src/devtools/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function formatTime (timestamp) {
return (new Date(timestamp)).toString().match(/\d\d:\d\d:\d\d/)[0]
}
13 changes: 13 additions & 0 deletions src/devtools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import * as filters from './filters'
import './plugins'
import { parse } from '../util'
import { isChrome, initEnv } from './env'
Expand All @@ -10,6 +11,10 @@ import storage from './storage'
import { snapshotsCache } from './views/vuex/cache'
import VuexResolve from './views/vuex/resolve'

for (const key in filters) {
Vue.filter(key, filters[key])
}

// UI

let panelShown = !isChrome
Expand Down Expand Up @@ -174,6 +179,14 @@ function initApp (shell) {
})
})

bridge.on('perf:add-metric', data => {
store.commit('perf/ADD_METRIC', data)
})

bridge.on('perf:upsert-metric', ({ type, data }) => {
store.commit('perf/UPSERT_METRIC', { type, data })
})

initEnv(Vue)

app = new Vue({
Expand Down
5 changes: 4 additions & 1 deletion src/devtools/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export default {
refresh: {
tooltip: '[[{{keys.ctrl}}]] + [[{{keys.alt}}]] + [[R]] Force Refresh'
},
perf: {
tooltip: '[[{{keys.ctrl}}]] + [[4]] Switch to Performance'
},
settings: {
tooltip: '[[{{keys.ctrl}}]] + [[4]] Switch to Settings'
tooltip: '[[{{keys.ctrl}}]] + [[5]] Switch to Settings'
},
vuex: {
tooltip: '[[{{keys.ctrl}}]] + [[2]] Switch to Vuex'
Expand Down
20 changes: 20 additions & 0 deletions src/devtools/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import VueRouter from 'vue-router'
import ComponentsTab from './views/components/ComponentsTab.vue'
import VuexTab from './views/vuex/VuexTab.vue'
import EventsTab from './views/events/EventsTab.vue'
import PerfTab from './views/perf/PerfTab.vue'
import ComponentRenderStats from './views/perf/ComponentRenderStats.vue'
import FramerateGraph from './views/perf/FramerateGraph.vue'
import SettingsTab from './views/settings/SettingsTab.vue'

Vue.use(VueRouter)
Expand All @@ -28,6 +31,23 @@ const routes = [
name: 'events',
component: EventsTab
},
{
path: '/perf',
component: PerfTab,
name: 'perf',
children: [
{
path: 'fps',
name: 'fps',
component: FramerateGraph
},
{
path: 'component-render',
name: 'component-render',
component: ComponentRenderStats
}
]
},
{
path: '/settings',
name: 'settings',
Expand Down
Loading

0 comments on commit 9f0cf3f

Please sign in to comment.