Slide 1

Slide 1 text

Profiling ember apps Selva @selvagsz

Slide 2

Slide 2 text

Performance is a inherent feature Failing to deliver; users are gonna churn

Slide 3

Slide 3 text

Load performance 1. TTFP (time taken for first paint; increases perceived performance) 2. TTFI (time taken for first meaningful interaction) Runtime performance 1. Rendering (jank-free 60 fps) 2. Memory monitoring

Slide 4

Slide 4 text

Memory Management JS engine does automatic memory allocation. So, we don’t have any role to play So, we don’t have any role to play

Slide 5

Slide 5 text

Garbage Collection

Slide 6

Slide 6 text

Memory leaks

Slide 7

Slide 7 text

Where to start ? Heap snapshot Task Manager Memory Allocation timeline

Slide 8

Slide 8 text

Everything on memory are not leaks. In ember world, ideally you’d want to look for `destroyed` objects being held Few profiling tips Force garbage collection before taking snapshots Always use Incognito/Guest window Identify & measure the leaks before fixing it

Slide 9

Slide 9 text

One quick demo

Slide 10

Slide 10 text

● Prototypal state leakage ● Event listeners leakage ● Third party libs ● window.set*/Ember.run* timers ● Module scope leakage ● Global leakage In ember.js apps

Slide 11

Slide 11 text

export default Component.extend({ init() { this._super(...arguments); this.set('tags', []); }, actions: { addTag(tagName) { this.tags.pushObject(tagName); }, }, }); export default Component.extend({ tags: [], actions: { addTag(tagName) { this.tags.pushObject(tagName); }, }, });

Slide 12

Slide 12 text

export default Component.extend({ didInsertElement() { this._super(...arguments); this._closeModal = this.closeModal.bind(this); window.addEventListener('keydown', this._closeModal); }, willDestroyElement() { this._super(...arguments); window.removeEventListener('keydown', this._closeModal); }, closeModal(event) { if (event.which === 27) { this.onClose(); } } }); export default Component.extend({ didInsertElement() { this._super(...arguments); window.addEventListener('keydown', this.closeModal.bind(this)); }, willDestroyElement() { this._super(...arguments); window.removeEventListener('keydown', this.closeModal); }, closeModal(event) { if (event.which === 27) { this.onClose(); } } });

Slide 13

Slide 13 text

export default TextField.extend({ didInsertElement() { this._super(...arguments); this.$().selectize({ delimiter: ',', create: function(input) { return { value: input, text: input }; } }); }, }); export default TextField.extend({ didInsertElement() { this._super(...arguments); this.$().selectize({ delimiter: ',', create: function(input) { return { value: input, text: input }; } }); }, willDestroyElement() { this._super(...arguments); this.$()[0].selectize.destroy(); } });

Slide 14

Slide 14 text

export default Component.extend({ didInsertElement() { this._super(...arguments); this.setTimeoutId = setTimeout(() => { // this.doSomeStuffs() }, 1000); this.runTimerId = Ember.run.later(() => { // this.doSomeStuffs() }, 1000); }, willDestroyElement() { this._super(...arguments); clearTimeout(this.setTimeoutId); Ember.run.cancel(this.runTimerId); } }); export default Component.extend({ didInsertElement() { this._super(...arguments); setTimeout(() => { // this.doSomeStuffs() }, 1000); Ember.run.later(() => { // this.doSomeStuffs() }, 1000); }, });

Slide 15

Slide 15 text

const UNIQUE_OPTIONS_COUNTER = new Map() export default Component.extend({ options: UNIQUE_OPTIONS_COUNTER, actions: { addFooToOptions() { this.options.set(this, 'foo') } } }); export default Component.extend({ init() { this._super(...arguments); this.set('options', new Map()); }, actions: { addFooToOptions() { this.options.set(this, 'foo') } } });

Slide 16

Slide 16 text

Summarising - Clear all timers/event listeners/third-party libs - Don’t pass the entire controller/component references around (yield the component’s public api using hash helper) - Do not set non-primitives on prototypes - Don’t increase the scope of your objects

Slide 17

Slide 17 text

Thanks for listening :)