From 65511737ac58e2a16548937012b4e0265fb5bced Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Wed, 1 Feb 2017 12:57:17 -0600 Subject: [PATCH] Add Flow Types (#135) * Address flow review comments --- .eslintrc.js | 15 ++- .flowconfig | 1 + circle.yml | 1 + package.json | 34 ++++--- src/common/summarize-profile.js | 137 +++++++++++++++++----------- src/common/time-code.js | 4 +- src/common/types/profile-derived.js | 9 ++ src/common/types/profile.js | 113 +++++++++++++++++++++++ src/content/profile-data.js | 101 ++++++++++++-------- 9 files changed, 306 insertions(+), 109 deletions(-) create mode 100644 .flowconfig create mode 100644 src/common/types/profile-derived.js create mode 100644 src/common/types/profile.js diff --git a/.eslintrc.js b/.eslintrc.js index 924eda1a44..5c08dc60f8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { "es6": true, "node": true }, + "parser": "babel-eslint", "extends": ["eslint:recommended", "plugin:react/recommended"], "parserOptions": { "ecmaFeatures": { @@ -13,9 +14,18 @@ module.exports = { "sourceType": "module" }, "plugins": [ - "react" + "react", + "flowtype" ], "rules": { + // Flow type rules: + "flowtype/define-flow-type": 1, + "flowtype/use-flow-type": 1, + "flowtype/generic-spacing": [2, "never"], + "flowtype/space-before-type-colon": [ 2, "never" ], + "flowtype/space-after-type-colon": [ 2, "always" ], + + // JS Rules: "indent": [ "error", 2, @@ -28,7 +38,6 @@ module.exports = { "comma-dangle": [ "error", "always-multiline" ], "no-console": [ "error", { allow: ["log", "warn", "error"] } ], "eqeqeq": "error", - "valid-jsdoc": "error", "consistent-return": "error", "curly": ["error", "all" ], "dot-location": ["error", "property"], @@ -92,4 +101,4 @@ module.exports = { "version": "15.0" } } -}; \ No newline at end of file +}; diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000000..38b71ea407 --- /dev/null +++ b/.flowconfig @@ -0,0 +1 @@ +[ignore] diff --git a/circle.yml b/circle.yml index 7eb27c0480..c1e445a022 100644 --- a/circle.yml +++ b/circle.yml @@ -4,6 +4,7 @@ machine: test: pre: - npm run eslint + - npm run flow post: - npm run test-report - bash <(curl -s https://codecov.io/bash) diff --git a/package.json b/package.json index 08796df251..917061a0fa 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,23 @@ "description": "perf.html, the gecko profiler UI", "main": "src/content/index.js", "scripts": { - "test": "npm run build && npm run test-all", - "test-all": "NODE_ENV=test nyc mocha --compilers js:babel-core/register 'test/**/*.js' 'src/**/test/*.js'", - "test-file": "NODE_ENV=test mocha --compilers js:babel-core/register", - "test-report": "nyc report --reporter=text-lcov > coverage.lcov", + "build": "npm run build:clean && NODE_ENV=development webpack --progress", + "build:clean": "rimraf dist && mkdirp dist && cp res/.htaccess dist/ && cp res/zee-worker.js dist/", + "build-prod": "npm run build:clean && NODE_ENV=production webpack -p --progress", + "build-prod-readable": "npm run build:clean && NODE_ENV=production webpack --progress", + "eslint": "eslint index.js src", + "eslint-fix": "eslint --fix index.js src", + "flow": "flow", + "publish": "rimraf public_html && cp -r dist public_html", + "serve-static": "ws -d dist/ -s index.html -p 4242", "start": "mkdir -p dist && cp res/zee-worker.js dist/ && NODE_ENV=development node server.js", "start-no-hot": "npm run start", "start-prod": "npm run build-prod && npm run serve-static", "start-prod-readable": "npm run build-prod-readable && npm run serve-static", - "serve-static": "ws -d dist/ -s index.html -p 4242", - "build": "npm run build:clean && NODE_ENV=development webpack --progress", - "build-prod-readable": "npm run build:clean && NODE_ENV=production webpack --progress", - "build-prod": "npm run build:clean && NODE_ENV=production webpack -p --progress", - "build:clean": "rimraf dist && mkdirp dist && cp res/.htaccess dist/ && cp res/zee-worker.js dist/", - "publish": "rimraf public_html && cp -r dist public_html", - "eslint": "eslint src", - "eslint-fix": "eslint --fix src", - "documentation": "documentation" + "test": "npm run build && npm run test-all", + "test-all": "NODE_ENV=test nyc mocha --compilers js:babel-core/register 'test/**/*.js' 'src/**/test/*.js'", + "test-file": "NODE_ENV=test mocha --compilers js:babel-core/register", + "test-report": "nyc report --reporter=text-lcov > coverage.lcov" }, "author": "Markus Stange ", "license": "MPL-2.0", @@ -33,7 +33,9 @@ "array-range": "^1.0.1", "babel-cli": "^6.2.0", "babel-core": "^6.2.1", + "babel-eslint": "^7.1.1", "babel-loader": "^6.2.4", + "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-polyfill": "^6.2.0", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.5.0", @@ -44,13 +46,14 @@ "clamp": "^1.0.1", "classnames": "^2.2.5", "css-loader": "^0.26.0", - "documentation": "^4.0.0-beta2", "eslint": "^3.10.2", "eslint-config-google": "^0.6.0", + "eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-react": "^6.4.0", "express": "^4.13.4", "fetch-jsonp": "^1.0.2", "file-loader": "^0.9.0", + "flow-bin": "^0.37.0", "html-webpack-plugin": "^2.24.1", "http-server": "^0.9.0", "json-loader": "^0.5.4", @@ -86,6 +89,9 @@ "presets": [ "react", "es2015" + ], + "plugins": [ + "transform-flow-strip-types" ] } } diff --git a/src/common/summarize-profile.js b/src/common/summarize-profile.js index b7a035fdd0..8086e1a3f6 100644 --- a/src/common/summarize-profile.js +++ b/src/common/summarize-profile.js @@ -1,14 +1,27 @@ +// @flow import { timeCode } from './time-code'; +import type { Profile, Thread, IndexIntoStackTable } from './types/profile'; + +type MatchingFunction = (string, string) => boolean; +type Summary = { [id: string]: number }; +type StacksInCategory = { [id: string]: { [id: string]: number } } +type SummarySegment = { + percentage: {[id: string]: number}, + samples: {[id: string]: number} +} +type RollingSummary = SummarySegment[]; +type Categories = string[]; +type ThreadCategories = Categories[]; /** * A list of strategies for matching sample names to patterns. */ -const match = { +const match: {[id: string]: MatchingFunction} = { exact: (symbol, pattern) => symbol === pattern, prefix: (symbol, pattern) => symbol.startsWith(pattern), substring: (symbol, pattern) => symbol.includes(pattern), stem: (symbol, pattern) => { - return symbol === pattern || (symbol && symbol.startsWith(pattern + '(')); + return symbol === pattern || symbol.startsWith(pattern + '('); }, }; @@ -23,6 +36,7 @@ const match = { * category, // The category to finally label the sample. * ] */ + const categories = [ [match.exact, 'js::RunScript', 'script'], [match.stem, 'js::Nursery::collect', 'GC'], @@ -70,11 +84,11 @@ const categories = [ [match.prefix, 'Interpret(', 'script.execute.interpreter'], ]; -export function summarizeProfile(profile) { +export function summarizeProfile(profile: Profile) { return timeCode('summarizeProfile', () => { - const categories = categorizeThreadSamples(profile); - const rollingSummaries = calculateRollingSummaries(profile, categories); - const summaries = summarizeCategories(profile, categories); + const threadCategories: ThreadCategories = categorizeThreadSamples(profile); + const rollingSummaries:RollingSummary[] = calculateRollingSummaries(profile, threadCategories); + const summaries = summarizeCategories(profile, threadCategories); return profile.threads.map((thread, i) => ({ threadIndex: i, @@ -115,10 +129,10 @@ function functionNameCategorizer() { * @param {object} thread Thread from a profile. * @return {function} Sample stack categorizer. */ -function sampleCategorizer(thread) { +function sampleCategorizer(thread: Thread): (stackIndex: IndexIntoStackTable) => string { const categorizeFuncName = functionNameCategorizer(); - function computeCategory(stackIndex) { + function computeCategory(stackIndex: IndexIntoStackTable): string { if (stackIndex === null) { return 'uncategorized'; } @@ -151,8 +165,9 @@ function sampleCategorizer(thread) { return prefixCategory; } - const stackCategoryCache = new Map(); - function categorizeSampleStack(stackIndex) { + const stackCategoryCache: Map = new Map(); + + function categorizeSampleStack(stackIndex: IndexIntoStackTable): string { let category = stackCategoryCache.get(stackIndex); if (category !== undefined) { return category; @@ -174,7 +189,7 @@ function sampleCategorizer(thread) { * @param {string} fullCategoryName - The name of the category. * @returns {object} summary */ -function summarizeSampleCategories(summary, fullCategoryName) { +function summarizeSampleCategories(summary: Summary, fullCategoryName: string): Summary { const categories = fullCategoryName.split('.'); while (categories.length > 0) { @@ -191,10 +206,10 @@ function summarizeSampleCategories(summary, fullCategoryName) { * @param {object} summary - The object that summarizes the times of the samples. * @return {array} The summary with percentages. */ -function calculateSummaryPercentages(summary) { - const rows = Object.entries(summary); +function calculateSummaryPercentages(summary: Summary) { + const rows = objectEntries(summary); - const sampleCount = rows.reduce((sum, [name, count]) => { + const sampleCount = rows.reduce((sum: number, [name: string, count: number]) => { // Only count the sample if it's not a sub-category. For instance "script.link" // is a sub-category of "script". return sum + (name.includes('.') ? 0 : count); @@ -208,20 +223,19 @@ function calculateSummaryPercentages(summary) { .sort((a, b) => b.samples - a.samples); } -function logStacks(samples, maxLogLength = 10) { - const entries = Object.entries(samples); +function logStacks(stacksInCategory: StacksInCategory, maxLogLength = 10) { + const entries = objectEntries(stacksInCategory); + const data = entries + .sort(([, {total: a}], [, {total: b}]) => b - a) + .slice(0, Math.min(maxLogLength, entries.length)); + /* eslint-disable no-console */ console.log(`Top ${maxLogLength} stacks in selected category`); - const log = typeof console.table === 'function' ? console.table : console.log; - log( - entries - .sort(([, {total: a}], [, {total: b}]) => b - a) - .slice(0, Math.min(maxLogLength, entries.length)) - ); + console.log(data); /* eslint-enable no-console */ } -function stackToString(stackIndex, thread) { +function stackToString(stackIndex: IndexIntoStackTable, thread: Thread): string { const { stackTable, frameTable, funcTable, stringTable } = thread; const stack = []; let nextStackIndex = stackIndex; @@ -235,26 +249,28 @@ function stackToString(stackIndex, thread) { return stack.join('\n'); } -function incrementPerThreadCount(container, key, threadName) { +function incrementPerThreadCount(container: StacksInCategory, key: string, threadName: string) { const count = container[key] || { total: 0, [threadName]: 0 }; count.total++; count[threadName]++; container[key] = count; } -function countStacksInCategory(profile, summaries, category = 'uncategorized') { - const uncategorized = {}; +function countStacksInCategory( + profile: Profile, threadCategories: ThreadCategories, category: string = 'uncategorized' +): StacksInCategory { + const stacksInCategory = {}; profile.threads.forEach((thread, i) => { - const threadSummary = summaries[i]; + const categories = threadCategories[i]; const { samples } = thread; for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { - if (threadSummary[sampleIndex] === category) { - const stringCallStack = stackToString(samples.stack[sampleIndex], thread); - incrementPerThreadCount(uncategorized, stringCallStack, thread.name); + if (categories[sampleIndex] === category) { + const stringCallStack: string = stackToString(samples.stack[sampleIndex], thread); + incrementPerThreadCount(stacksInCategory, stringCallStack, thread.name); } } }); - return uncategorized; + return stacksInCategory; } /** @@ -263,22 +279,26 @@ function countStacksInCategory(profile, summaries, category = 'uncategorized') { * @param {array} profile - The current profile. * @returns {array} Stacks mapped to categories. */ -export function categorizeThreadSamples(profile) { +export function categorizeThreadSamples(profile: Profile): ThreadCategories { return timeCode('categorizeThreadSamples', () => { - const summaries = profile.threads.map(thread => { - const categorizer = sampleCategorizer(thread); - return thread.samples.stack.map(categorizer); - }); + const threadCategories = mapProfileToThreadCategories(profile); if (process.env.NODE_ENV === 'development') { // Change the constant to display the top stacks of a different category. const categoryToDump = 'uncategorized'; - const stacks = countStacksInCategory(profile, summaries, categoryToDump); + const stacks: StacksInCategory = countStacksInCategory(profile, threadCategories, categoryToDump); console.log(`${Object.keys(stacks).length} stacks labeled '${categoryToDump}'`); logStacks(stacks); } - return summaries; + return threadCategories; + }); +} + +function mapProfileToThreadCategories(profile: Profile): ThreadCategories { + return profile.threads.map(thread => { + const categorizer = sampleCategorizer(thread); + return thread.samples.stack.map(categorizer); }); } @@ -289,7 +309,7 @@ export function categorizeThreadSamples(profile) { * @param {object} threadCategories - Each thread's categories for the samples. * @returns {object} The summaries of each thread. */ -export function summarizeCategories(profile, threadCategories) { +export function summarizeCategories(profile: Profile, threadCategories: ThreadCategories) { return threadCategories.map(categories => ( categories.reduce(summarizeSampleCategories, {}) )) @@ -298,7 +318,9 @@ export function summarizeCategories(profile, threadCategories) { // .sort((a, b) => Object.keys(b.summary).length - Object.keys(a.summary).length); } -export function calculateRollingSummaries(profile, threadCategories, segmentCount = 40, rolling = 4) { +export function calculateRollingSummaries( + profile: Profile, threadCategories: ThreadCategories, segmentCount: number = 40, rolling: number = 4 +): RollingSummary[] { const [minTime, maxTime] = profile.threads.map(thread => { return [thread.samples.time[0], thread.samples.time[thread.samples.time.length - 1]]; }) @@ -314,12 +336,13 @@ export function calculateRollingSummaries(profile, threadCategories, segmentCoun return profile.threads.map((thread, threadIndex) => { const categories = threadCategories[threadIndex]; + const rollingSummary: RollingSummary = []; - return times(segmentCount, segmentIndex => { + for (let i = 0; i < segmentCount; i++) { let samplesInRange = 0; const samples = {}; - const rollingMinTime = minTime + (segmentIndex * segmentLength) + segmentHalfLength - rollingHalfLength; + const rollingMinTime = minTime + (i * segmentLength) + segmentHalfLength - rollingHalfLength; const rollingMaxTime = rollingMinTime + rollingLength; for (let sampleIndex = 0; sampleIndex < thread.samples.time.length; sampleIndex++) { @@ -334,20 +357,14 @@ export function calculateRollingSummaries(profile, threadCategories, segmentCoun } } - return { + rollingSummary.push({ samples, percentage: mapObj(samples, count => count / samplesInRange), - }; - }); - }); -} + }); + } -function times(n, fn) { - const results = Array(n); - for (let i = 0; i < n; i++) { - results[i] = fn(i); - } - return results; + return rollingSummary; + }); } function mapObj(object, fn) { @@ -361,3 +378,17 @@ function mapObj(object, fn) { } return mappedObj; } + +/** + * Flow requires a type-safe implementation of Object.entries(). + * See: https://github.com/facebook/flow/issues/2174 + */ +function objectEntries(object: { [id: string]: T }): Array<[string, T]> { + const entries = []; + for (const key in object) { + if (object.hasOwnProperty(key)) { + entries.push([key, object[key]]); + } + } + return entries; +} diff --git a/src/common/time-code.js b/src/common/time-code.js index 45276c56bf..309b9c4aaf 100644 --- a/src/common/time-code.js +++ b/src/common/time-code.js @@ -1,4 +1,6 @@ -export function timeCode(label, codeAsACallback) { +// @flow + +export function timeCode(label: string, codeAsACallback: any => any): any { if (typeof performance !== 'undefined' && process.env.NODE_ENV === 'development') { const start = performance.now(); diff --git a/src/common/types/profile-derived.js b/src/common/types/profile-derived.js new file mode 100644 index 0000000000..dce48cac78 --- /dev/null +++ b/src/common/types/profile-derived.js @@ -0,0 +1,9 @@ +// @flow +export type IndexIntoFuncStack = number; + +export type FuncStackTable = { + prefix: Int32Array, + func: Int32Array, + depth: number[], + length: number, +} diff --git a/src/common/types/profile.js b/src/common/types/profile.js new file mode 100644 index 0000000000..1bdc466453 --- /dev/null +++ b/src/common/types/profile.js @@ -0,0 +1,113 @@ +// @flow +export type IndexIntoStackTable = number; +export type IndexIntoSamplesTable = number; +export type IndexIntoMarkersTable = number; +export type IndexIntoFrameTable = number; +export type IndexIntoStringTable = number; +export type IndexIntoFuncTable = number; +export type IndexIntoResourceTable = number; +export type IndexIntoLibs = number; +export type categoryBitMask = number; +export type resourceTypeEnum = number; + +export type StackTable = { + frame: number[], + length: number, + prefix: number[], +}; + +export type SamplesTable = { + frameNumber: IndexIntoFrameTable[], + responsiveness: number[], + stack: IndexIntoStackTable[], + time: number[], + rss: any, // TODO + uss: any, // TODO + length: number, +}; + +export type Marker = { + category?: string, + interval?: string, + type?: string, + dur?: number, + title?: string, + start?: number, + name?: string +}; + +export type MarkersTable = { + data: Marker[], + name: IndexIntoStringTable[], + time: number[], + length: number, +}; + +export type FrameTable = { + address: IndexIntoStringTable[], + category: (categoryBitMask | null)[], + func: IndexIntoFuncTable[], + implementation: (IndexIntoStringTable | null)[], + line: (number | null)[], + optimizations: ({} | null)[], + length: number +}; + +export type StringTable = { + _array: string, + _stringToIndex: Map, + getString: number => string, + indexForString: string => number, + serializeToArray: () => string[], +}; + +export type FuncTable = { + address: IndexIntoStringTable[], + libs: { + breakpadId: string, + end: number, + name: string, + offset: number, + pdbName: string, + start: number, + }[], + isJS: boolean[], + length: number, + name: IndexIntoStringTable[], + resource: IndexIntoResourceTable[], +} + +export type ResourceTable = { + addonId: [any], + icon: [any], + length: number, + lib: IndexIntoLibs[], + name: IndexIntoStringTable, + type: resourceTypeEnum +} + +export type Thread = { + processType: string, + name: string, + tid: number, + samples: SamplesTable, + markers: MarkersTable, + stackTable: StackTable, + frameTable: FrameTable, + stringTable: StringTable, + libs: [], + funcTable: FuncTable, + resourceTable: {} +}; + +export type ProfileMeta = { + interval: number +}; + +export type TaskTracer = {}; + +export type Profile = { + meta: ProfileMeta, + tasktracer: TaskTracer, + threads: [Thread], +}; diff --git a/src/content/profile-data.js b/src/content/profile-data.js index bfaa9b2fdd..86ac3e5fdc 100644 --- a/src/content/profile-data.js +++ b/src/content/profile-data.js @@ -1,3 +1,16 @@ +// @flow +import type { + SamplesTable, + StackTable, + FrameTable, + FuncTable, + IndexIntoFuncTable, + Thread, + Profile, + MarkersTable, + Marker, +} from '../common/types/profile'; +import type { FuncStackTable, IndexIntoFuncStack } from '../common/types/profile-derived'; import { timeCode } from '../common/time-code'; /** @@ -23,22 +36,29 @@ export const resourceTypes = { * @param {Object} funcTable The thread's funcTable. * @return {Object} The funcStackTable and the stackIndexToFuncStackIndex map. */ -export function getFuncStackInfo(stackTable, frameTable, funcTable) { +export function getFuncStackInfo(stackTable: StackTable, frameTable: FrameTable, funcTable: FuncTable) { return timeCode('getFuncStackInfo', () => { const stackIndexToFuncStackIndex = new Uint32Array(stackTable.length); const funcCount = funcTable.length; const prefixFuncStackAndFuncToFuncStackMap = new Map(); // prefixFuncStack * funcCount + func => funcStack - const funcStackTable = { length: 0, prefix: [], func: [], depth: [] }; - function addFuncStack(prefix, func) { - const index = funcStackTable.length++; - funcStackTable.prefix[index] = prefix; - funcStackTable.func[index] = func; - if (prefix === -1) { - funcStackTable.depth[index] = 0; + + // The funcStackTable components. + const prefix: Array = []; + const func: Array = []; + const depth: Array = []; + let length = 0; + + function addFuncStack(prefixIndex, funcIndex) { + const index = length++; + prefix[index] = prefixIndex; + func[index] = funcIndex; + if (prefixIndex === -1) { + depth[index] = 0; } else { - funcStackTable.depth[index] = funcStackTable.depth[prefix] + 1; + depth[index] = depth[prefixIndex] + 1; } } + for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { const prefixStack = stackTable.prefix[stackIndex]; // assert(prefixStack === null || prefixStack < stackIndex); @@ -49,35 +69,36 @@ export function getFuncStackInfo(stackTable, frameTable, funcTable) { const prefixFuncStackAndFuncIndex = prefixFuncStack * funcCount + funcIndex; let funcStackIndex = prefixFuncStackAndFuncToFuncStackMap.get(prefixFuncStackAndFuncIndex); if (funcStackIndex === undefined) { - funcStackIndex = funcStackTable.length; + funcStackIndex = length; addFuncStack(prefixFuncStack, funcIndex); prefixFuncStackAndFuncToFuncStackMap.set(prefixFuncStackAndFuncIndex, funcStackIndex); } stackIndexToFuncStackIndex[stackIndex] = funcStackIndex; } - funcStackTable.prefix = new Int32Array(funcStackTable.prefix); - funcStackTable.func = new Int32Array(funcStackTable.func); - funcStackTable.depth = funcStackTable.depth; - return { - funcStackTable, - stackIndexToFuncStackIndex, + const funcStackTable: FuncStackTable = { + prefix: new Int32Array(prefix), + func: new Int32Array(func), + depth, + length, }; + + return { funcStackTable, stackIndexToFuncStackIndex }; }); } -export function getSampleFuncStacks(samples, stackIndexToFuncStackIndex) { +export function getSampleFuncStacks(samples: SamplesTable, stackIndexToFuncStackIndex: { [key: number]: number }) { return samples.stack.map(stack => stackIndexToFuncStackIndex[stack]); } -function getTimeRangeForThread(thread, interval) { +function getTimeRangeForThread(thread: Thread, interval: number) { if (thread.samples.length === 0) { return { start: Infinity, end: -Infinity }; } return { start: thread.samples.time[0], end: thread.samples.time[thread.samples.length - 1] + interval}; } -export function getTimeRangeIncludingAllThreads(profile) { +export function getTimeRangeIncludingAllThreads(profile: Profile) { const completeRange = { start: Infinity, end: -Infinity }; profile.threads.forEach(thread => { const threadRange = getTimeRangeForThread(thread, profile.meta.interval); @@ -87,7 +108,7 @@ export function getTimeRangeIncludingAllThreads(profile) { return completeRange; } -export function defaultThreadOrder(threads) { +export function defaultThreadOrder(threads: Thread[]) { // Put the compositor thread last. const threadOrder = threads.map((thread, i) => i); threadOrder.sort((a, b) => { @@ -101,7 +122,7 @@ export function defaultThreadOrder(threads) { return threadOrder; } -export function filterThreadToJSOnly(thread) { +export function filterThreadToJSOnly(thread: Thread) { return timeCode('filterThreadToJSOnly', () => { const { stackTable, funcTable, frameTable, samples } = thread; @@ -152,7 +173,7 @@ export function filterThreadToJSOnly(thread) { }); } -export function filterThreadToSearchString(thread, searchString) { +export function filterThreadToSearchString(thread: Thread, searchString: string) { return timeCode('filterThreadToSearchString', () => { if (searchString === '') { return thread; @@ -209,7 +230,7 @@ export function filterThreadToSearchString(thread, searchString) { * @param {bool} matchJSOnly Ignore non-JS frames during matching. * @return {object} The filtered thread. */ -export function filterThreadToPrefixStack(thread, prefixFuncs, matchJSOnly) { +export function filterThreadToPrefixStack(thread: Thread, prefixFuncs: IndexIntoFuncTable[], matchJSOnly: boolean) { return timeCode('filterThreadToPrefixStack', () => { const { stackTable, frameTable, funcTable, samples } = thread; const prefixDepth = prefixFuncs.length; @@ -272,7 +293,7 @@ export function filterThreadToPrefixStack(thread, prefixFuncs, matchJSOnly) { * @param {bool} matchJSOnly Ignore non-JS frames during matching. * @return {object} The filtered thread. */ -export function filterThreadToPostfixStack(thread, postfixFuncs, matchJSOnly) { +export function filterThreadToPostfixStack(thread: Thread, postfixFuncs: IndexIntoFuncTable[], matchJSOnly: boolean) { return timeCode('filterThreadToPostfixStack', () => { const postfixDepth = postfixFuncs.length; const { stackTable, frameTable, funcTable, samples } = thread; @@ -312,7 +333,7 @@ export function filterThreadToPostfixStack(thread, postfixFuncs, matchJSOnly) { }); } -function getSampleIndexRangeForSelection(samples, rangeStart, rangeEnd) { +function getSampleIndexRangeForSelection(samples: SamplesTable, rangeStart: number, rangeEnd: number) { // TODO: This should really use bisect. samples.time is sorted. const firstSample = samples.time.findIndex(t => t >= rangeStart); if (firstSample === -1) { @@ -325,7 +346,7 @@ function getSampleIndexRangeForSelection(samples, rangeStart, rangeEnd) { return [firstSample, firstSample + afterLastSample]; } -function getMarkerIndexRangeForSelection(markers, rangeStart, rangeEnd) { +function getMarkerIndexRangeForSelection(markers: MarkersTable, rangeStart: number, rangeEnd: number) { // TODO: This should really use bisect. samples.time is sorted. const firstMarker = markers.time.findIndex(t => t >= rangeStart); if (firstMarker === -1) { @@ -338,7 +359,7 @@ function getMarkerIndexRangeForSelection(markers, rangeStart, rangeEnd) { return [firstMarker, firstMarker + afterLastSample]; } -export function filterThreadToRange(thread, rangeStart, rangeEnd) { +export function filterThreadToRange(thread: Thread, rangeStart: number, rangeEnd: number) { const { samples, markers } = thread; const [sBegin, sEnd] = getSampleIndexRangeForSelection(samples, rangeStart, rangeEnd); const newSamples = { @@ -363,7 +384,7 @@ export function filterThreadToRange(thread, rangeStart, rangeEnd) { }); } -export function getFuncStackFromFuncArray(funcArray, funcStackTable) { +export function getFuncStackFromFuncArray(funcArray: IndexIntoFuncTable[], funcStackTable: FuncStackTable) { let fs = -1; for (let i = 0; i < funcArray.length; i++) { const func = funcArray[i]; @@ -383,7 +404,7 @@ export function getFuncStackFromFuncArray(funcArray, funcStackTable) { return fs; } -export function getStackAsFuncArray(funcStackIndex, funcStackTable) { +export function getStackAsFuncArray(funcStackIndex: IndexIntoFuncStack, funcStackTable: FuncStackTable) { if (funcStackIndex === null) { return []; } @@ -391,7 +412,7 @@ export function getStackAsFuncArray(funcStackIndex, funcStackTable) { console.log('bad funcStackIndex in getStackAsFuncArray:', funcStackIndex); return []; } - const funcArray = []; + const funcArray: IndexIntoFuncTable[] = []; let fs = funcStackIndex; while (fs !== -1) { funcArray.push(funcStackTable.func[fs]); @@ -401,7 +422,7 @@ export function getStackAsFuncArray(funcStackIndex, funcStackTable) { return funcArray; } -export function invertCallstack(thread) { +export function invertCallstack(thread: Thread) { return timeCode('invertCallstack', () => { const { stackTable, frameTable, samples } = thread; @@ -452,7 +473,7 @@ export function invertCallstack(thread) { }); } -export function getSampleIndexClosestToTime(samples, time) { +export function getSampleIndexClosestToTime(samples: SamplesTable, time: number) { // TODO: This should really use bisect. samples.time is sorted. for (let i = 0; i < samples.length; i++) { if (samples.time[i] >= time) { @@ -467,7 +488,7 @@ export function getSampleIndexClosestToTime(samples, time) { return samples.length - 1; } -export function getJankInstances(samples, processType, thresholdInMs) { +export function getJankInstances(samples: SamplesTable, processType: string, thresholdInMs: number) { let lastResponsiveness = 0; let lastTimestamp = 0; const jankInstances = []; @@ -497,10 +518,10 @@ export function getJankInstances(samples, processType, thresholdInMs) { return jankInstances; } -export function getTracingMarkers(thread, markers) { - const tracingMarkers = []; +export function getTracingMarkers(thread: Thread, markers: MarkersTable) { const { stringTable } = thread; - const openMarkers = []; + const tracingMarkers: Marker[] = []; + const openMarkers: Marker[] = []; for (let i = 0; i < markers.length; i++) { const data = markers.data[i]; if (!data || data.type !== 'tracing') { @@ -520,8 +541,12 @@ export function getTracingMarkers(thread, markers) { if (marker === undefined) { continue; } - marker.dur = time - marker.start; - marker.title = `${marker.name} for ${marker.dur.toFixed(2)}ms`; + if (marker.start !== undefined) { + marker.dur = time - marker.start; + } + if (marker.name !== undefined && marker.dur !== undefined) { + marker.title = `${marker.name} for ${marker.dur.toFixed(2)}ms`; + } tracingMarkers.push(marker); } }