Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/components/app/BottomBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ import {
getSourceViewCode,
getAssemblyViewCode,
} from 'firefox-profiler/selectors/code';
import { getSourceViewFile } from 'firefox-profiler/selectors/profile';
import {
getSourceViewFile,
getSourceViewStartLine,
} from 'firefox-profiler/selectors/profile';
import explicitConnect from 'firefox-profiler/utils/connect';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';
Expand All @@ -55,6 +58,7 @@ type StateProps = {
readonly sourceViewScrollGeneration: number;
readonly sourceViewScrollToLineNumber?: number;
readonly sourceViewHighlightedLine: number | null;
readonly sourceViewStartLine: number;
readonly globalLineTimings: LineTimings;
readonly assemblyViewIsOpen: boolean;
readonly assemblyViewNativeSymbol: NativeSymbolInfo | null;
Expand Down Expand Up @@ -161,6 +165,7 @@ class BottomBoxImpl extends React.PureComponent<Props> {
sourceViewScrollGeneration,
sourceViewScrollToLineNumber,
sourceViewHighlightedLine,
sourceViewStartLine,
assemblyViewIsOpen,
assemblyViewScrollGeneration,
assemblyViewScrollToInstructionAddress,
Expand Down Expand Up @@ -233,6 +238,7 @@ class BottomBoxImpl extends React.PureComponent<Props> {
scrollGeneration={sourceViewScrollGeneration}
scrollToLineNumber={sourceViewScrollToLineNumber}
highlightedLine={sourceViewHighlightedLine}
startLine={sourceViewStartLine}
ref={this._sourceView}
/>
) : null}
Expand Down Expand Up @@ -303,6 +309,7 @@ export const BottomBox = explicitConnect<{}, StateProps, DispatchProps>({
sourceViewScrollGeneration: getSourceViewScrollGeneration(state),
sourceViewScrollToLineNumber: getSourceViewScrollToLineNumber(state),
sourceViewHighlightedLine: getSourceViewHighlightedLine(state),
sourceViewStartLine: getSourceViewStartLine(state),
assemblyViewNativeSymbol: getAssemblyViewNativeSymbol(state),
assemblyViewCode: getAssemblyViewCode(state),
globalAddressTimings:
Expand Down
24 changes: 23 additions & 1 deletion src/components/shared/SourceView-codemirror.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const languageConf = new Compartment();
// This "compartment" allows us to swap the highlighted line when it changes.
const highlightedLineConf = new Compartment();

// This "compartment" allows us to reconfigure the line number formatter when
// startLine changes.
const lineNumbersConf = new Compartment();

// Detect the right language based on the file extension.
function _languageExtForPath(
path: string | null
Expand Down Expand Up @@ -85,6 +89,16 @@ const codeViewerExtension = [
EditorView.contentAttributes.of({ tabindex: '0' }),
];

// Creates a lineNumbers extension that displays line numbers offset by startLine - 1.
function _lineNumbersForStartLine(startLine: number) {
if (startLine <= 1) {
return lineNumbers();
}
return lineNumbers({
formatNumber: (n) => String(n + startLine - 1),
});
}

export class SourceViewEditor {
_view: EditorView;

Expand All @@ -94,13 +108,14 @@ export class SourceViewEditor {
path: string,
timings: LineTimings,
highlightedLine: number | null,
startLine: number,
domParent: Element
) {
let state = EditorState.create({
doc: initialText,
extensions: [
timingsExtension,
lineNumbers(),
lineNumbersConf.of(_lineNumbersForStartLine(startLine)),
languageConf.of(_languageExtForPath(path)),
highlightedLineConf.of(createHighlightedLineExtension(highlightedLine)),
syntaxHighlighting(classHighlighter),
Expand Down Expand Up @@ -152,6 +167,13 @@ export class SourceViewEditor {
});
}

setStartLine(startLine: number) {
// Reconfigure the line numbers extension to display the new offset.
this._view.dispatch({
effects: lineNumbersConf.reconfigure(_lineNumbersForStartLine(startLine)),
});
}

scrollToLine(lineNumber: number) {
// Clamp the line number to the document's line count.
lineNumber = clamp(lineNumber, 1, this._view.state.doc.lines);
Expand Down
82 changes: 70 additions & 12 deletions src/components/shared/SourceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,33 @@ type SourceViewProps = {
readonly scrollGeneration: number;
readonly scrollToLineNumber?: number;
readonly highlightedLine: number | null;
// 1-based line number for the start of the source in the full document.
readonly startLine: number;
};

let editorModulePromise: Promise<any> | null = null;

// Remap absolute line timings to document-relative (1-based) line numbers.
// Timings keys are absolute line numbers; CodeMirror uses doc-relative line numbers.
function remapTimingsToRelative(
timings: LineTimings,
startLine: number
): LineTimings {
if (startLine <= 1) {
return timings;
}
const offset = startLine - 1;
const totalLineHits = new Map<number, number>();
for (const [line, hits] of timings.totalLineHits) {
totalLineHits.set(line - offset, hits);
}
const selfLineHits = new Map<number, number>();
for (const [line, hits] of timings.selfLineHits) {
selfLineHits.set(line - offset, hits);
}
return { totalLineHits, selfLineHits };
}

export class SourceView extends React.PureComponent<SourceViewProps> {
_ref = React.createRef<HTMLDivElement>();
_editor: SourceViewEditor | null = null;
Expand All @@ -57,8 +80,28 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
}
}

_toRelativeLine(absoluteLine: number): number {
return Math.max(1, absoluteLine - (this.props.startLine - 1));
}

// Convert an absolute scroll-to line number to a doc-relative line number.
_getRelativeScrollToLineNumber(): number | undefined {
const { scrollToLineNumber } = this.props;
return scrollToLineNumber === undefined
? undefined
: this._toRelativeLine(scrollToLineNumber);
}

// Convert an absolute highlighted line number to a doc-relative line number.
_getRelativeHighlightedLine(): number | null {
const { highlightedLine } = this.props;
return highlightedLine === null
? null
: this._toRelativeLine(highlightedLine);
}

_getMaxLineNumber() {
const { sourceCode, timings } = this.props;
const { sourceCode, timings, startLine } = this.props;
const sourceLines = sourceCode.split('\n');
let maxLineNumber = sourceLines.length;
if (maxLineNumber <= 1) {
Expand All @@ -69,7 +112,9 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
// isn't too constrained - if the last known line is chosen as the "hot spot",
// this extra space allows us to display it in the top half of the viewport,
// if the viewport is small enough.
maxLineNumber = Math.max(1, ...timings.totalLineHits.keys()) + 10;
// timings keys are absolute line numbers; convert to doc-relative count.
const maxAbsoluteLine = Math.max(1, ...timings.totalLineHits.keys());
maxLineNumber = Math.max(1, maxAbsoluteLine - startLine + 1) + 10;
}
return maxLineNumber;
}
Expand Down Expand Up @@ -108,14 +153,16 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
const editor = new SourceViewEditor(
this._getSourceCodeOrFallback(),
this.props.filePath,
this.props.timings,
this.props.highlightedLine,
remapTimingsToRelative(this.props.timings, this.props.startLine),
this._getRelativeHighlightedLine(),
this.props.startLine,
domParent
);
this._editor = editor;
// If an explicit line number is provided, scroll to it. Otherwise, scroll to the hotspot.
if (this.props.scrollToLineNumber !== undefined) {
this._scrollToLine(Math.max(1, this.props.scrollToLineNumber - 5));
const relativeScrollTo = this._getRelativeScrollToLineNumber();
if (relativeScrollTo !== undefined) {
this._scrollToLine(Math.max(1, relativeScrollTo - 5));
}
})();
}
Expand All @@ -131,6 +178,11 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
this._editor.updateLanguageForFilePath(this.props.filePath);
}

const startLineChanged = this.props.startLine !== prevProps.startLine;
if (startLineChanged) {
this._editor.setStartLine(this.props.startLine);
}

let contentsChanged = false;
if (
this.props.sourceCode !== prevProps.sourceCode ||
Expand All @@ -147,17 +199,23 @@ export class SourceView extends React.PureComponent<SourceViewProps> {
this.props.scrollGeneration !== prevProps.scrollGeneration
) {
// If an explicit line number is provided, scroll to it. Otherwise, scroll to the hotspot.
if (this.props.scrollToLineNumber !== undefined) {
this._scrollToLine(Math.max(1, this.props.scrollToLineNumber - 5));
const relativeScrollTo = this._getRelativeScrollToLineNumber();
if (relativeScrollTo !== undefined) {
this._scrollToLine(Math.max(1, relativeScrollTo - 5));
}
}

if (this.props.timings !== prevProps.timings) {
this._editor.setTimings(this.props.timings);
if (this.props.timings !== prevProps.timings || startLineChanged) {
this._editor.setTimings(
remapTimingsToRelative(this.props.timings, this.props.startLine)
);
}

if (this.props.highlightedLine !== prevProps.highlightedLine) {
this._editor.setHighlightedLine(this.props.highlightedLine);
if (
this.props.highlightedLine !== prevProps.highlightedLine ||
startLineChanged
) {
this._editor.setHighlightedLine(this._getRelativeHighlightedLine());
}
}
}
15 changes: 0 additions & 15 deletions src/profile-logic/data-structures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
RawSamplesTable,
FrameTable,
RawStackTable,
StackTable,
FuncTable,
RawMarkerTable,
JsAllocationsTable,
Expand All @@ -33,20 +32,6 @@ import type {
* This module collects all of the creation of new empty profile data structures.
*/

export function getEmptyStackTable(): StackTable {
return {
// Important!
// If modifying this structure, please update all callers of this function to ensure
// that they are pushing on correctly to the data structure. These pushes may not
// be caught by the type system.
frame: [],
prefix: [],
category: new Uint8Array(),
subcategory: new Uint8Array(),
length: 0,
};
}

export function getEmptySamplesTable(): RawSamplesTable {
return {
// Important!
Expand Down
29 changes: 29 additions & 0 deletions src/profile-logic/profile-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2714,6 +2714,34 @@ export function createThreadFromDerivedTables(
return thread;
}

/**
* Throws if the column lengths of a StackTable don't match stackTable.length.
* Call this after constructing a new StackTable to catch bugs early.
*/
export function validateStackTableShape(stackTable: StackTable): void {
const { length, frame, prefix, category, subcategory } = stackTable;
if (frame.length !== length) {
throw new Error(
`StackTable frame column length ${frame.length} does not match stackTable.length ${length}`
);
}
if (prefix.length !== length) {
throw new Error(
`StackTable prefix column length ${prefix.length} does not match stackTable.length ${length}`
);
}
if (category.length !== length) {
throw new Error(
`StackTable category column length ${category.length} does not match stackTable.length ${length}`
);
}
if (subcategory.length !== length) {
throw new Error(
`StackTable subcategory column length ${subcategory.length} does not match stackTable.length ${length}`
);
}
}

/**
* Sometimes we want to update the stacks for a thread, for instance while searching
* for a text string, or doing a call tree transformation. This function abstracts
Expand All @@ -2738,6 +2766,7 @@ export function updateThreadStacksByGeneratingNewStackColumns(
markerData: Array<MarkerPayload | null>
) => Array<MarkerPayload | null>
): Thread {
validateStackTableShape(newStackTable);
const { jsAllocations, nativeAllocations, samples, markers } = thread;

const newSamples = {
Expand Down
Loading