From 44aab64888cd7805fac18b5ab0b2a58191ce02f1 Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Wed, 11 Jul 2018 11:51:28 -0500 Subject: [PATCH] Increase flow coverage of gecko profile fixtures --- src/profile-logic/profile-data.js | 13 +- src/test/fixtures/profiles/gecko-profile.js | 690 ++++++++++-------- src/test/fixtures/profiles/make-profile.js | 5 +- src/test/fixtures/profiles/timings-with-js.js | 159 ++-- src/types/gecko-profile.js | 58 +- src/types/markers.js | 9 + src/utils/flow.js | 20 + 7 files changed, 528 insertions(+), 426 deletions(-) diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index a689d1bc4f..15bb1943a0 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -31,6 +31,7 @@ import type { import type { BailoutPayload } from '../types/markers'; import { CURRENT_VERSION as GECKO_PROFILE_VERSION } from './gecko-profile-versioning'; import { CURRENT_VERSION as PROCESSED_PROFILE_VERSION } from './processed-profile-versioning'; +import { getNumberPropertyOrNull } from '../utils/flow'; import type { Milliseconds, StartEndRange } from '../types/units'; import { timeCode } from '../utils/time-code'; @@ -1379,9 +1380,15 @@ export function getTracingMarkers(thread: Thread): TracingMarker[] { } tracingMarkers.push(marker); } - } else if ('startTime' in data && 'endTime' in data) { - const { startTime, endTime } = data; - if (typeof startTime === 'number' && typeof endTime === 'number') { + } else { + // `data` here is a union of different shaped objects, that may or not have + // certain properties. Flow doesn't like us arbitrarily accessing properties + // that may not exist, so use a utility function to generically get the data out. + const startTime = getNumberPropertyOrNull(data, 'startTime'); + const endTime = getNumberPropertyOrNull(data, 'endTime'); + + // Now construct a tracing marker if these properties existed. + if (startTime !== null && endTime !== null) { const name = stringTable.getString(markers.name[i]); const duration = endTime - startTime; tracingMarkers.push({ diff --git a/src/test/fixtures/profiles/gecko-profile.js b/src/test/fixtures/profiles/gecko-profile.js index 6ba5d4842a..eccf22534b 100644 --- a/src/test/fixtures/profiles/gecko-profile.js +++ b/src/test/fixtures/profiles/gecko-profile.js @@ -1,347 +1,399 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// @flow -import cloneDeep from 'lodash.clonedeep'; +import type { Lib } from '../../../types/profile'; + +import type { + GeckoProfile, + GeckoProfileMeta, + GeckoThread, + GeckoMarkerStack, +} from '../../../types/gecko-profile'; /** * export defaults one object that is an example profile, in the Gecko format, * i.e. the format that nsIProfiler.getProfileDataAsync outputs. */ - -const parentProcessBinary = { - breakpadId: 'F1D957D30B413D55A539BBA06F90DD8F0', - debugName: 'firefox', - name: 'firefox', - path: '/Applications/FirefoxNightly.app/Contents/MacOS/firefox', - debugPath: '/Applications/FirefoxNightly.app/Contents/MacOS/firefox', - start: 0x100000000, - end: 0x100000000 + 10000, - arch: 'x86_64', -}; - -const contentProcessBinary = { - breakpadId: '9F950E2CE3CD3E1ABD06D80788B606E60', - debugName: 'firefox-webcontent', - name: 'firefox-webcontent', - path: - '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-webcontent.app/Contents/MacOS/firefox-webcontent', - debugPath: - '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-webcontent.app/Contents/MacOS/firefox-webcontent', - start: 0x100000000, - end: 0x100000000 + 10000, - arch: 'x86_64', -}; - -const extraBinaries = [ - { - breakpadId: '1000000000000000000000000000000A1', - debugName: 'examplebinary', - name: 'examplebinary', - path: '/tmp/examplebinary', - debugPath: '/tmp/examplebinary', - start: 0x200000000, - end: 0x200000000 + 20, +export default function createGeckoProfile(): GeckoProfile { + const parentProcessBinary: Lib = { + breakpadId: 'F1D957D30B413D55A539BBA06F90DD8F0', + debugName: 'firefox', + name: 'firefox', + path: '/Applications/FirefoxNightly.app/Contents/MacOS/firefox', + debugPath: '/Applications/FirefoxNightly.app/Contents/MacOS/firefox', + offset: 0, + start: 0x100000000, + end: 0x100000000 + 10000, arch: 'x86_64', - }, - { - breakpadId: '100000000000000000000000000000A27', - debugName: 'examplebinary2.pdb', - name: 'examplebinary2', - path: 'C:\\examplebinary2', - debugPath: 'C:\\examplebinary2.pdb', - start: 0x200000000 + 20, - end: 0x200000000 + 40, + }; + + const contentProcessBinary: Lib = { + breakpadId: '9F950E2CE3CD3E1ABD06D80788B606E60', + debugName: 'firefox-webcontent', + name: 'firefox-webcontent', + path: + '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-webcontent.app/Contents/MacOS/firefox-webcontent', + debugPath: + '/Applications/FirefoxNightly.app/Contents/MacOS/firefox-webcontent.app/Contents/MacOS/firefox-webcontent', + offset: 0, + start: 0x100000000, + end: 0x100000000 + 10000, arch: 'x86_64', - }, -]; + }; -const thread = { - samples: { - schema: { - stack: 0, - time: 1, - responsiveness: 2, - rss: 3, - uss: 4, - frameNumber: 5, - power: 6, + const extraBinaries: Lib[] = [ + { + breakpadId: '1000000000000000000000000000000A1', + debugName: 'examplebinary', + name: 'examplebinary', + path: '/tmp/examplebinary', + debugPath: '/tmp/examplebinary', + offset: 0, + start: 0x200000000, + end: 0x200000000 + 20, + arch: 'x86_64', + }, + { + breakpadId: '100000000000000000000000000000A27', + debugName: 'examplebinary2.pdb', + name: 'examplebinary2', + path: 'C:\\examplebinary2', + debugPath: 'C:\\examplebinary2.pdb', + offset: 0, + start: 0x200000000 + 20, + end: 0x200000000 + 40, + arch: 'x86_64', }, - data: [ - [1, 0, 0], // (root), 0x100000f84 - [2, 1, 0], // (root), 0x100000f84, 0x100001a45 - [2, 2, 0], // (root), 0x100000f84, 0x100001a45 - [3, 3, 0], // (root), 0x100000f84, Startup::XRE_Main - [0, 4, 0], // (root) - [1, 5, 0], // (root), 0x100000f84 - [4, 6, 0], // (root), 0x100000f84, frobnicate + ]; + + const parentProcessMeta: GeckoProfileMeta = { + abi: 'x86_64-gcc3', + interval: 1, + misc: 'rv:48.0', + oscpu: 'Intel Mac OS X 10.11', + platform: 'Macintosh', + processType: 0, + product: 'Firefox', + stackwalk: 1, + startTime: 1460221352723.438, + shutdownTime: 1560221352723, + toolkit: 'cocoa', + version: 5, + categories: [], + }; + + const contentProcessMeta: GeckoProfileMeta = { + ...parentProcessMeta, + processType: 2, + // content process was launched 1 second after parent process: + startTime: parentProcessMeta.startTime + 1000, + }; + + const contentProcessProfile: GeckoProfile = { + meta: contentProcessMeta, + pausedRanges: [], + libs: [contentProcessBinary].concat(extraBinaries), // libs are stringified in the Gecko profile + threads: [ + { + ..._createGeckoThread(), + name: 'GeckoMain', + processType: 'tab', + }, ], - }, - stackTable: { - schema: { prefix: 0, frame: 1 }, - data: [ - [null, 0], // (root) - [0, 1], // (root), 0x100000f84 - [1, 2], // (root), 0x100000f84, 0x100001a45 - [1, 3], // (root), 0x100000f84, Startup::XRE_Main - [1, 4], // (root), 0x100000f84, frobnicate + processes: [], + }; + + return { + meta: parentProcessMeta, + libs: [parentProcessBinary].concat(extraBinaries), + pausedRanges: [], + threads: [ + { + ..._createGeckoThread(), + name: 'GeckoMain', + processType: 'default', + }, + { + ..._createGeckoThread(), + name: 'Compositor', + processType: 'default', + }, ], - }, - frameTable: { - schema: { - location: 0, - implementation: 1, - optimizations: 2, - line: 3, - category: 4, + processes: [contentProcessProfile], + }; +} + +function _createGeckoThread(): GeckoThread { + return { + name: 'Unnamed', + registerTime: 0, + processType: 'default', + unregisterTime: 100, + tid: 1111, + pid: 2222, + samples: { + schema: { + stack: 0, + time: 1, + responsiveness: 2, + rss: 3, + uss: 4, + frameNumber: 5, + power: 6, + }, + data: [ + [1, 0, 0, null, null], // (root), 0x100000f84 + [2, 1, 0, null, null], // (root), 0x100000f84, 0x100001a45 + [2, 2, 0, null, null], // (root), 0x100000f84, 0x100001a45 + [3, 3, 0, null, null], // (root), 0x100000f84, Startup::XRE_Main + [0, 4, 0, null, null], // (root) + [1, 5, 0, null, null], // (root), 0x100000f84 + [4, 6, 0, null, null], // (root), 0x100000f84, frobnicate + ], }, - data: [ - [0], // (root) - [1], // 0x100000f84 - [2], // 0x100001a45 - [3, null, null, 4391, 16], // Startup::XRE_Main, line 4391, category 16 - [7, 6, null, 34], // frobnicate, implementation 'baseline', line 34 - ], - }, - markers: { - schema: { name: 0, time: 1, data: 2 }, - data: [ - // Please keep the next marker at the start if you add more markers in - // this structure. - // We want to test tracing markers with the missing "start" marker. So - // here is only the "end" marker without a matching "start" marker. - [ - 10, // Rasterize - 1, - { - category: 'Paint', - interval: 'end', - type: 'tracing', - }, + stackTable: { + schema: { prefix: 0, frame: 1 }, + data: [ + [null, 0], // (root) + [0, 1], // (root), 0x100000f84 + [1, 2], // (root), 0x100000f84, 0x100001a45 + [1, 3], // (root), 0x100000f84, Startup::XRE_Main + [1, 4], // (root), 0x100000f84, frobnicate ], - // This marker is filtered out - [4, 2, { category: 'VsyncTimestamp', vsync: 0 }], - [ - 5, // Reflow - 3, - { - category: 'Paint', - interval: 'start', - stack: { - markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, - name: 'SyncProfile', - samples: { - schema: { - stack: 0, - time: 1, - responsiveness: 2, - rss: 3, - uss: 4, - frameNumber: 5, - power: 6, + }, + frameTable: { + schema: { + location: 0, + implementation: 1, + optimizations: 2, + line: 3, + category: 4, + }, + data: [ + [0, null, null, null, null], // (root) + [1, null, null, null, null], // 0x100000f84 + [2, null, null, null, null], // 0x100001a45 + [3, null, null, 4391, 16], // Startup::XRE_Main, line 4391, category 16 + [7, 6, null, 34, null], // frobnicate, implementation 'baseline', line 34 + ], + }, + markers: { + schema: { name: 0, time: 1, data: 2 }, + data: [ + // Please keep the next marker at the start if you add more markers in + // this structure. + // We want to test tracing markers with the missing "start" marker. So + // here is only the "end" marker without a matching "start" marker. + [ + 10, // Rasterize + 1, + { + category: 'Paint', + interval: 'end', + type: 'tracing', + }, + ], + // This marker is filtered out + [4, 2, { type: undefined, category: 'VsyncTimestamp', vsync: 0 }], + [ + 5, // Reflow + 3, + { + category: 'Paint', + interval: 'start', + stack: ({ + registerTime: null, + unregisterTime: null, + processType: 'default', + tid: 1111, + pid: 2222, + markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, + name: 'SyncProfile', + samples: { + schema: { + stack: 0, + time: 1, + responsiveness: 2, + rss: 3, + uss: 4, + frameNumber: 5, + power: 6, + }, + data: [[2, 1, 0, null, null]], // (root), 0x100000f84, 0x100001a45 }, - data: [[2, 1]], // (root), 0x100000f84, 0x100001a45 - }, + }: GeckoMarkerStack), + type: 'tracing', }, - type: 'tracing', - }, - ], - [ - 10, // Rasterize - 4, - { - category: 'Paint', - interval: 'start', - type: 'tracing', - }, - ], - [ - 10, // Rasterize - 5, - { - category: 'Paint', - interval: 'end', - type: 'tracing', - }, - ], - [ - 5, // Reflow - 8, - { - category: 'Paint', - interval: 'end', - type: 'tracing', - }, - ], - [ - 9, - 11, // Note: this marker is out of order on purpose, to test we correctly sort - { - // MinorGC at time 11ms from 11ms to 12ms - startTime: 11, - endTime: 12, - }, - ], - [ - 8, - 9, - { - // DOMEvent at time 9ms from 9ms to 10ms - startTime: 9, - endTime: 10, - timeStamp: 1, - type: 'mouseout', - phase: 3, - }, - ], - [ - 11, // UserTiming - 12, - { - startTime: 12, - endTime: 13, - type: 'UserTiming', - name: 'processing-thread', - entryType: 'measure', - }, - ], - // Nested reflows - [ - 5, // Reflow - 13, - { - category: 'Paint', - interval: 'start', - stack: { - markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, - name: 'SyncProfile', - samples: { - schema: { - stack: 0, - time: 1, - responsiveness: 2, - rss: 3, - uss: 4, - frameNumber: 5, - power: 6, + ], + [ + 10, // Rasterize + 4, + { + category: 'Paint', + interval: 'start', + type: 'tracing', + }, + ], + [ + 10, // Rasterize + 5, + { + category: 'Paint', + interval: 'end', + type: 'tracing', + }, + ], + [ + 5, // Reflow + 8, + { + category: 'Paint', + interval: 'end', + type: 'tracing', + }, + ], + [ + 9, + 11, // Note: this marker is out of order on purpose, to test we correctly sort + { + // MinorGC at time 11ms from 11ms to 12ms + type: 'GCMinor', + startTime: 11, + endTime: 12, + }, + ], + [ + 8, + 9, + // This doesn't really match the current marker payloads, so coerce + // it to any: + ({ + // DOMEvent at time 9ms from 9ms to 10ms + startTime: 9, + endTime: 10, + timeStamp: 1, + type: 'mouseout', + phase: 3, + }: any), + ], + [ + 11, // UserTiming + 12, + { + startTime: 12, + endTime: 13, + type: 'UserTiming', + name: 'processing-thread', + entryType: 'measure', + }, + ], + // Nested reflows + [ + 5, // Reflow + 13, + { + category: 'Paint', + interval: 'start', + stack: { + markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, + name: 'SyncProfile', + registerTime: null, + unregisterTime: null, + processType: 'default', + tid: 1111, + pid: 2222, + samples: { + schema: { + stack: 0, + time: 1, + responsiveness: 2, + rss: 3, + uss: 4, + frameNumber: 5, + power: 6, + }, + data: [[2, 1, 0, null, null]], // (root), 0x100000f84, 0x100001a45 }, - data: [[2, 1]], // (root), 0x100000f84, 0x100001a45 }, + type: 'tracing', }, - type: 'tracing', - }, - ], - [ - 5, // Reflow - 14, - { - category: 'Paint', - interval: 'start', - stack: { - markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, - name: 'SyncProfile', - samples: { - schema: { - stack: 0, - time: 1, - responsiveness: 2, - rss: 3, - uss: 4, - frameNumber: 5, - power: 6, + ], + [ + 5, // Reflow + 14, + { + category: 'Paint', + interval: 'start', + stack: { + markers: { schema: { name: 0, time: 1, data: 2 }, data: [] }, + name: 'SyncProfile', + registerTime: null, + unregisterTime: null, + processType: 'default', + tid: 1111, + pid: 2222, + samples: { + schema: { + stack: 0, + time: 1, + responsiveness: 2, + rss: 3, + uss: 4, + frameNumber: 5, + power: 6, + }, + data: [[2, 1, 0, null, null]], // (root), 0x100000f84, 0x100001a45 }, - data: [[2, 1]], // (root), 0x100000f84, 0x100001a45 }, + type: 'tracing', }, - type: 'tracing', - }, - ], - [ - 5, // Reflow - 15, - { - category: 'Paint', - interval: 'end', - type: 'tracing', - }, - ], - [ - 5, // Reflow - 18, - { - category: 'Paint', - interval: 'end', - type: 'tracing', - }, - ], - // Starting a tracing but never finishing it. - // Please keep it at the end if you add more markers in this structure - [ - 10, // Rasterize - 20, - { - category: 'Paint', - interval: 'start', - type: 'tracing', - }, + ], + [ + 5, // Reflow + 15, + { + category: 'Paint', + interval: 'end', + type: 'tracing', + }, + ], + [ + 5, // Reflow + 18, + { + category: 'Paint', + interval: 'end', + type: 'tracing', + }, + ], + // Starting a tracing but never finishing it. + // Please keep it at the end if you add more markers in this structure + [ + 10, // Rasterize + 20, + { + category: 'Paint', + interval: 'start', + type: 'tracing', + }, + ], ], + }, + stringTable: [ + '(root)', + '0x100000f84', + '0x100001a45', + 'Startup::XRE_Main', + 'VsyncTimestamp', + 'Reflow', + 'baseline', + 'frobnicate (chrome://blargh:34)', + 'DOMEvent', + 'MinorGC', + 'Rasterize', + 'UserTiming', ], - }, - stringTable: [ - '(root)', - '0x100000f84', - '0x100001a45', - 'Startup::XRE_Main', - 'VsyncTimestamp', - 'Reflow', - 'baseline', - 'frobnicate (chrome://blargh:34)', - 'DOMEvent', - 'MinorGC', - 'Rasterize', - 'UserTiming', - ], -}; - -const parentProcessMeta = { - abi: 'x86_64-gcc3', - interval: 1, - misc: 'rv:48.0', - oscpu: 'Intel Mac OS X 10.11', - platform: 'Macintosh', - processType: 0, - product: 'Firefox', - stackwalk: 1, - startTime: 1460221352723.438, - toolkit: 'cocoa', - version: 5, -}; - -const contentProcessMeta = Object.assign({}, parentProcessMeta, { - processType: 2, - startTime: parentProcessMeta.startTime + 1000, // content process was launched 1 second after parent process -}); - -const contentProcessProfile = { - meta: contentProcessMeta, - libs: [contentProcessBinary].concat(extraBinaries), // libs are stringified in the Gecko profile - threads: [Object.assign({ name: 'GeckoMain', processType: 'tab' }, thread)], - processes: [], -}; - -const profile = { - meta: parentProcessMeta, - libs: [parentProcessBinary].concat(extraBinaries), - threads: [ - Object.assign({ name: 'GeckoMain', processType: 'default' }, thread), - Object.assign( - { name: 'Compositor', processType: 'default' }, - cloneDeep(thread) - ), - ], - processes: [contentProcessProfile], -}; - -export default function createProfile() { - return cloneDeep(profile); + }; } diff --git a/src/test/fixtures/profiles/make-profile.js b/src/test/fixtures/profiles/make-profile.js index 06011bf755..c0241f1304 100644 --- a/src/test/fixtures/profiles/make-profile.js +++ b/src/test/fixtures/profiles/make-profile.js @@ -5,6 +5,7 @@ // @flow import { getEmptyProfile } from '../../../profile-logic/profile-data'; import { UniqueStringArray } from '../../../utils/unique-string-array'; +import { ensureIsNumber } from '../../../utils/flow'; import type { Profile, Thread } from '../../../types/profile'; import type { MarkerPayload } from '../../../types/markers'; import type { Milliseconds } from '../../../types/units'; @@ -31,8 +32,8 @@ export function addMarkersToThreadWithCorrespondingSamples( if (data && !data.type) { data = { type: 'DummyForTests', - startTime: data.startTime, - endTime: data.endTime, + startTime: ensureIsNumber((data: Object).startTime), + endTime: ensureIsNumber((data: Object).endTime), }; } markersTable.name.push(stringTable.indexForString(name)); diff --git a/src/test/fixtures/profiles/timings-with-js.js b/src/test/fixtures/profiles/timings-with-js.js index 523fe3d280..a638baa12d 100644 --- a/src/test/fixtures/profiles/timings-with-js.js +++ b/src/test/fixtures/profiles/timings-with-js.js @@ -1,89 +1,100 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// @flow import getGeckoProfile from './gecko-profile'; +import type { GeckoProfile, GeckoThread } from '../../../types/gecko-profile'; -const thread = { - samples: { - schema: { stack: 0, time: 1, responsiveness: 2, rss: 3, uss: 4 }, - data: [ - [1, 0, 0], // (root), 0x100000f84 - [2, 10, 0], // (root), 0x100000f84, 0x100001a45 - [2, 20, 0], // (root), 0x100000f84, 0x100001a45 - [3, 30, 0], // (root), 0x100000f84, Startup::XRE_Main - [0, 40, 0], // (root) - [1, 50, 0], // (root), 0x100000f84 - [4, 60, 0], // (root), 0x100000f84, javascriptOne - [5, 70, 0], // (root), 0x100000f84, javascriptOne, javascriptTwo - [8, 80, 0], // (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe, javascriptThree - [4, 90, 0], // (root), 0x100000f84, javascriptOne - ], - }, - stackTable: { - schema: { prefix: 0, frame: 1 }, - data: [ - [null, 0], // 0: (root) - [0, 1], // 1: (root), 0x100000f84 - [1, 2], // 2: (root), 0x100000f84, 0x100001a45 - [1, 3], // 3: (root), 0x100000f84, Startup::XRE_Main - [1, 4], // 4: (root), 0x100000f84, javascriptOne - [4, 5], // 5: (root), 0x100000f84, javascriptOne, javascriptTwo - [5, 6], // 6: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0 - [6, 7], // 7: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe - [7, 8], // 8: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe, javascriptThree - ], - }, - frameTable: { - schema: { - location: 0, - implementation: 1, - optimizations: 2, - line: 3, - category: 4, - }, - data: [ - [0], // 0: (root) - [1], // 1: 0x100000f84 - [2], // 2: 0x100001a45 - [3, null, null, 4391, 16], // 3: Startup::XRE_Main, line 4391, category 16 - [7, 6, null, 1], // 4: javascriptOne, implementation 'baseline', line 1 - [8, 6, null, 2], // 5: javascriptTwo, implementation 'baseline', line 2 - [9], // 6: 0x10000f0f0 - [10], // 7: 0x100fefefe - [11, null, null, 3], // 8: javascriptThree, implementation null, line 3 - ], - }, - markers: { - schema: { name: 0, time: 1, data: 2 }, - data: [], - }, - stringTable: [ - '(root)', // 0 - '0x100000f84', // 1 - '0x100001a45', // 2 - 'Startup::XRE_Main', // 3 - 'VsyncTimestamp', // 4 - 'Reflow', // 5 - 'baseline', // 6 - 'javascriptOne (http://js.com/foobar:1)', // 7 - 'javascriptTwo (http://js.com/foobar:2)', // 8 - '0x10000f0f0', // 9 - '0x100fefefe', // 10 - 'javascriptThree (http://js.com/foobar:3)', // 11 - ], -}; - -export default function createProfile() { +export default function createGeckoProfile(): GeckoProfile { const geckoProfile = getGeckoProfile(); return { meta: geckoProfile.meta, libs: geckoProfile.libs, + pausedRanges: [], threads: [ - Object.assign({ name: 'GeckoMain' }, thread), - Object.assign({ name: 'Compositor' }, thread), - Object.assign({ name: 'GeckoMain' }, thread), + _createGeckoThread('GeckoMain'), + _createGeckoThread('Compositor'), + _createGeckoThread('GeckoMain'), ], processes: [], }; } + +function _createGeckoThread(name: string): GeckoThread { + return { + name, + registerTime: 0, + processType: 'default', + unregisterTime: 100, + tid: 1111, + pid: 2222, + samples: { + schema: { stack: 0, time: 1, responsiveness: 2, rss: 3, uss: 4 }, + data: [ + [1, 0, 0, null, null], // (root), 0x100000f84 + [2, 10, 0, null, null], // (root), 0x100000f84, 0x100001a45 + [2, 20, 0, null, null], // (root), 0x100000f84, 0x100001a45 + [3, 30, 0, null, null], // (root), 0x100000f84, Startup::XRE_Main + [0, 40, 0, null, null], // (root) + [1, 50, 0, null, null], // (root), 0x100000f84 + [4, 60, 0, null, null], // (root), 0x100000f84, javascriptOne + [5, 70, 0, null, null], // (root), 0x100000f84, javascriptOne, javascriptTwo + [8, 80, 0, null, null], // (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe, javascriptThree + [4, 90, 0, null, null], // (root), 0x100000f84, javascriptOne + ], + }, + stackTable: { + schema: { prefix: 0, frame: 1 }, + data: [ + [null, 0], // 0: (root) + [0, 1], // 1: (root), 0x100000f84 + [1, 2], // 2: (root), 0x100000f84, 0x100001a45 + [1, 3], // 3: (root), 0x100000f84, Startup::XRE_Main + [1, 4], // 4: (root), 0x100000f84, javascriptOne + [4, 5], // 5: (root), 0x100000f84, javascriptOne, javascriptTwo + [5, 6], // 6: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0 + [6, 7], // 7: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe + [7, 8], // 8: (root), 0x100000f84, javascriptOne, javascriptTwo, 0x10000f0f0, 0x100fefefe, javascriptThree + ], + }, + frameTable: { + schema: { + location: 0, + implementation: 1, + optimizations: 2, + line: 3, + category: 4, + }, + data: [ + [0, null, null, null, null], // 0: (root) + [1, null, null, null, null], // 1: 0x100000f84 + [2, null, null, null, null], // 2: 0x100001a45 + [3, null, null, 4391, 16], // 3: Startup::XRE_Main, line 4391, category 16 + [7, 6, null, 1, null], // 4: javascriptOne, implementation 'baseline', line 1 + [8, 6, null, 2, null], // 5: javascriptTwo, implementation 'baseline', line 2 + [9, null, null, null, null], // 6: 0x10000f0f0 + [10, null, null, null, null], // 7: 0x100fefefe + [11, null, null, 3, null], // 8: javascriptThree, implementation null, line 3 + ], + }, + markers: { + schema: { name: 0, time: 1, data: 2 }, + data: [], + }, + stringTable: [ + '(root)', // 0 + '0x100000f84', // 1 + '0x100001a45', // 2 + 'Startup::XRE_Main', // 3 + 'VsyncTimestamp', // 4 + 'Reflow', // 5 + 'baseline', // 6 + 'javascriptOne (http://js.com/foobar:1)', // 7 + 'javascriptTwo (http://js.com/foobar:2)', // 8 + '0x10000f0f0', // 9 + '0x100fefefe', // 10 + 'javascriptThree (http://js.com/foobar:3)', // 11 + ], + }; +} diff --git a/src/types/gecko-profile.js b/src/types/gecko-profile.js index 1ef70686e8..fed65ed2ef 100644 --- a/src/types/gecko-profile.js +++ b/src/types/gecko-profile.js @@ -120,10 +120,10 @@ export type GeckoFrameStruct = { export type GeckoStackTable = { schema: { - frame: 0, - prefix: 1, + prefix: 0, + frame: 1, }, - data: Array<[IndexIntoGeckoFrameTable, IndexIntoGeckoStackTable | null]>, + data: Array<[IndexIntoGeckoStackTable | null, IndexIntoGeckoFrameTable]>, }; export type GeckoStackStruct = { @@ -155,32 +155,34 @@ export type GeckoExtensionMeta = {| data: Array<[string, string, string]>, |}; +export type GeckoProfileMeta = {| + interval: Milliseconds, + startTime: Milliseconds, + shutdownTime: Milliseconds | null, + abi: string, + // The extensions property landed in Firefox 60, and is only optional because + // older profile versions may not have it. No upgrader was written for this change. + extensions?: GeckoExtensionMeta, + categories: CategoryList, + misc: string, + oscpu: string, + platform: string, + processType: number, + product: string, + stackwalk: number, + toolkit: string, + version: number, + // The appBuildID, sourceURL, physicalCPUs and logicalCPUs properties landed + // in Firefox 62, and are only optional because older processed profile + // versions may not have them. No upgrader was written for this change. + appBuildID?: string, + sourceURL?: string, + physicalCPUs?: number, + logicalCPUs?: number, +|}; + export type GeckoProfile = {| - meta: {| - interval: Milliseconds, - startTime: Milliseconds, - shutdownTime: Milliseconds | null, - abi: string, - // The extensions property landed in Firefox 60, and is only optional because - // older profile versions may not have it. No upgrader was written for this change. - extensions?: GeckoExtensionMeta, - categories: CategoryList, - misc: string, - oscpu: string, - platform: string, - processType: number, - product: string, - stackwalk: number, - toolkit: string, - version: number, - // The appBuildID, sourceURL, physicalCPUs and logicalCPUs properties landed - // in Firefox 62, and are only optional because older processed profile - // versions may not have them. No upgrader was written for this change. - appBuildID?: string, - sourceURL?: string, - physicalCPUs?: number, - logicalCPUs?: number, - |}, + meta: GeckoProfileMeta, libs: Lib[], threads: GeckoThread[], pausedRanges: PausedRange[], diff --git a/src/types/markers.js b/src/types/markers.js index ddd03075c6..c1dc18141d 100644 --- a/src/types/markers.js +++ b/src/types/markers.js @@ -329,6 +329,13 @@ type StyleMarkerPayload_Shared = { stylesReused: number, }; +type VsyncTimestampPayload = {| + // The "type" property doesn't exist, but is required to make Flow typing work. + type: void, + category: 'VsyncTimestamp', + vsync: 0, +|}; + /** * The payload for Styles. */ @@ -369,6 +376,7 @@ export type MarkerPayload = | GCSliceMarkerPayload | StyleMarkerPayload | BHRMarkerPayload + | VsyncTimestampPayload | DummyForTestsMarkerPayload | null; @@ -383,4 +391,5 @@ export type MarkerPayload_Gecko = | GCSliceMarkerPayload_Gecko | StyleMarkerPayload_Gecko | DummyForTestsMarkerPayload + | VsyncTimestampPayload | null; diff --git a/src/utils/flow.js b/src/utils/flow.js index 491de4ec47..bb7ff6b5a1 100644 --- a/src/utils/flow.js +++ b/src/utils/flow.js @@ -142,3 +142,23 @@ export function ensureExists(item: ?T, message: ?string): T { } return item; } + +export function ensureIsNumber(item: T): number { + if (typeof item !== 'number') { + throw new Error(`Expected a number, instead received "${typeof item}".`); + } + return item; +} + +/** + * Flow doesn't want us to access potentitally non-existent properties on unions of + * of objects. This function creates a safe interface to access number properties + * if they might exist. + */ +export function getNumberPropertyOrNull( + data: T, + property: string +): number | null { + const maybeNumber = (data: Object)[property]; + return typeof maybeNumber === 'number' ? maybeNumber : null; +}