diff --git a/locales/de/app.ftl b/locales/de/app.ftl index 62e72d124f..d0c5c54e61 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
{ -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — devtools.performance.recording.ui-base-url. Si tu vole capturar nove profilos con iste instantia, e dar a illo le controlo programmatic del button de menu profilator, tu pote ir a about:config e cambiar le preferentia. Home--record-instructions = - Pro initiar profilar, clicca sur le button profila o usa le - vias breve de claviero. Le icone es blau quando un profilo es in registration. + Pro comenciar le profilage, clicca sur le button de profilage o usa le + claves de accesso directe. Le icone es blau quando un profilo se registra. Pulsa Capturar pro cargar le datos in profiler.firefox.com. Home--instructions-content = Registrar profilos de prestation require { -firefox-brand-name }. @@ -356,7 +366,7 @@ Home--load-files-from-other-tools2 = proprie importator. Home--install-chrome-extension = Installar le extension de Chrome Home--chrome-extension-instructions = Usa le extension de { -profiler-brand-name } pro Chrome pro capturar profilos de prestation in Chrome e analysar los in le { -profiler-brand-name }. -Home--chrome-extension-recording-instructions = Installar le extension ab le Boteca web de Chrome. Un vice installate, usar le icone barra del instrumentos del extensiones o le vias breve pro cessar de profilar. Tu alsi pote exportar profilos e cargar los ci pro analyse detaliate. +Home--chrome-extension-recording-instructions = Un vice installate, usar le icone de barra de instrumentos del extension o le accessos directe pro comenciar e terminar le profilage. Tu pote etiam exportar profilos e cargar los ci pro un analyse detaliate. ## IdleSearchField ## The component that is used for all the search inputs in the application. @@ -571,6 +581,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Sin limite MenuButtons--metaInfo--application = Application MenuButtons--metaInfo--name-and-version = Nomine e version: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = Tempore de activitate: MenuButtons--metaInfo--update-channel = Canal de actualisation: MenuButtons--metaInfo--build-id = ID de version: MenuButtons--metaInfo--build-type = Typo de compilation: diff --git a/locales/it/app.ftl b/locales/it/app.ftl index 4a9763e083..f0d95939cf 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
{ -profiler-brand-name } – { -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — { -profiler-brand-name } — a - b); + if ( + nativeSymbolsForCallNodeArr.length !== 0 && + initialNativeSymbol === null + ) { + initialNativeSymbol = nativeSymbolsForCallNodeArr[0]; + } - // If we have at least one native symbol to show assembly for, pick - // the first one arbitrarily. - // TODO: If the we have more than one native symbol, pick the one - // with the highest total sample count. - const initialNativeSymbol = nativeSymbolsForCallNode.length !== 0 ? 0 : null; - - const nativeSymbolInfosForCallNode = nativeSymbolsForCallNode.map( + const nativeSymbolInfosForCallNode = nativeSymbolsForCallNodeArr.map( (nativeSymbolIndex) => getNativeSymbolInfo( nativeSymbolIndex, @@ -91,9 +111,7 @@ export function getBottomBoxInfoForCallNode( samples, callNodeFramePerStack, frameTable, - initialNativeSymbol !== null - ? nativeSymbolsForCallNode[initialNativeSymbol] - : null + initialNativeSymbol ); const hottestInstructionAddress = mapGetKeyWithMaxValue(addressTimings); @@ -101,7 +119,10 @@ export function getBottomBoxInfoForCallNode( libIndex, sourceIndex, nativeSymbols: nativeSymbolInfosForCallNode, - initialNativeSymbol, + initialNativeSymbol: + initialNativeSymbol !== null + ? nativeSymbolsForCallNodeArr.indexOf(initialNativeSymbol) + : null, scrollToLineNumber: hottestLine, scrollToInstructionAddress: hottestInstructionAddress, highlightedLineNumber: null, diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index dfb9994e8e..acf6018fab 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -10,7 +10,6 @@ import { import type { RawThread, RawSamplesTable, - SamplesTable, FrameTable, RawStackTable, StackTable, @@ -93,26 +92,6 @@ export function getEmptySamplesTableWithEventDelay(): RawSamplesTable { }; } -/** - * Returns an empty samples table with responsiveness field instead of eventDelay. - * responsiveness is the older field and replaced with eventDelay. We should - * account for older profiles and use both of the flavors if needed. - */ -export function getEmptySamplesTableWithResponsiveness(): SamplesTable { - 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. - weightType: 'samples', - weight: null, - responsiveness: [], - stack: [], - time: [], - length: 0, - }; -} - export function getEmptyFrameTable(): FrameTable { return { // Important! diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 6e247fad55..ceb60a8e11 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -4032,8 +4032,8 @@ export function calculateFunctionSizeLowerBound( export function getNativeSymbolsForCallNode( callNodeFramePerStack: Int32Array, frameTable: FrameTable -): IndexIntoNativeSymbolTable[] { - const set: Set = new Set(); +): Set { + const set = new Set(); for ( let stackIndex = 0; stackIndex < callNodeFramePerStack.length; @@ -4047,7 +4047,47 @@ export function getNativeSymbolsForCallNode( } } } - return [...set]; + return set; +} + +/** + * Return the total of the sample weights per native symbol, by + * accumulating the weight from samples which contribute to the + * call node of interest's total time. + * callNodeFramePerStack needs to be a mapping from stackIndex to the + * corresponding frame in the call node of interest. + */ +export function getTotalNativeSymbolTimingsForCallNode( + samples: SamplesLikeTable, + callNodeFramePerStack: Int32Array, + frameTable: FrameTable +): Map { + const totalPerNativeSymbol = new Map(); + for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { + const stack = samples.stack[sampleIndex]; + if (stack === null) { + continue; + } + const callNodeFrame = callNodeFramePerStack[stack]; + if (callNodeFrame === -1) { + // This sample does not contribute to the call node's total. Ignore. + continue; + } + + const nativeSymbol = frameTable.nativeSymbol[callNodeFrame]; + if (nativeSymbol === null) { + continue; + } + + const sampleWeight = + samples.weight !== null ? samples.weight[sampleIndex] : 1; + totalPerNativeSymbol.set( + nativeSymbol, + (totalPerNativeSymbol.get(nativeSymbol) ?? 0) + sampleWeight + ); + } + + return totalPerNativeSymbol; } /** diff --git a/src/test/components/TrackBandwidth.test.tsx b/src/test/components/TrackBandwidth.test.tsx index 3f208ec09e..b8109c8693 100644 --- a/src/test/components/TrackBandwidth.test.tsx +++ b/src/test/components/TrackBandwidth.test.tsx @@ -164,7 +164,7 @@ describe('TrackBandwidth', function () { screen.getByText('Transfer speed for this sample:') ).toBeInTheDocument(); expect(screen.getByText(/speed/).nextSibling).toHaveTextContent( - '4.66GB\u2069 per second' + '5.00GB\u2069 per second' ); expect( @@ -176,14 +176,14 @@ describe('TrackBandwidth', function () { screen.getByText('Data transferred up to this time:') ).toBeInTheDocument(); expect(screen.getByText(/transferred up to/).nextSibling).toHaveTextContent( - /6.86MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /7.19MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); expect( screen.getByText('Data transferred in the visible range:') ).toBeInTheDocument(); expect(screen.getByText(/visible range/).nextSibling).toHaveTextContent( - /7.97MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /8.35MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); }); @@ -224,13 +224,13 @@ describe('TrackBandwidth', function () { moveMouseAtCounter(3, 0); // Note: Fluent adds isolation characters \u2068 and \u2069 around variables. expect(screen.getByText(/speed/).nextSibling).toHaveTextContent( - '95.4MB\u2069 per second' + '100MB\u2069 per second' ); expect(screen.getByText(/visible range:/).nextSibling).toHaveTextContent( - /7.97MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /8.35MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); expect( screen.getByText(/current selection:/).nextSibling - ).toHaveTextContent(/4.77MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/); + ).toHaveTextContent(/5.00MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/); }); }); diff --git a/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap b/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap index 0b2b81311d..011f064231 100644 --- a/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap +++ b/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap @@ -342,7 +342,7 @@ exports[`TooltipMarker renders properly network markers where content type is bl Requested bytes : - 45.9KB + 47.0KB
@@ -538,7 +538,7 @@ exports[`TooltipMarker renders properly network markers where content type is mi Requested bytes :
- 45.9KB + 47.0KB
@@ -744,7 +744,7 @@ exports[`TooltipMarker renders properly network markers with a preconnect part 2 Requested bytes :
- 45.9KB + 47.0KB
@@ -1014,7 +1014,7 @@ exports[`TooltipMarker renders properly network markers with a preconnect part c Requested bytes :
- 45.9KB + 47.0KB
@@ -1210,7 +1210,7 @@ exports[`TooltipMarker renders properly normal network markers 1`] = ` Requested bytes :
- 45.9KB + 47.0KB
@@ -3216,14 +3216,14 @@ exports[`TooltipMarker renders tooltips for various markers: GCMajor-16.5 1`] = GC heap size (pre - post) :
- 46.1MB - 36.3MB + 48.4MB - 38.1MB
Malloc heap size (pre - post) :
- 23.1MB - 12.1MB + 24.2MB - 12.7MB
@@ -3362,7 +3362,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Bytes tenured :
- 1.30MB / 1.97MB (66%) + 1.37MB / 2.06MB (66%)
@@ -3376,7 +3376,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Bytes used :
- 1.97MB / 16.0MB (12%) + 2.06MB / 16.8MB (12%)
@@ -3397,7 +3397,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Tenured allocation rate :
- 181MB/s + 190MB/s
@@ -3501,7 +3501,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCSlice-17.5 1`] = Trigger (amt/trig) :
- 266MB / 245MB + 279MB / 257MB
diff --git a/src/test/unit/format-numbers.test.ts b/src/test/unit/format-numbers.test.ts index a6c47fbd08..19a0795245 100644 --- a/src/test/unit/format-numbers.test.ts +++ b/src/test/unit/format-numbers.test.ts @@ -28,27 +28,27 @@ describe('formatGigaBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatGigaBytes(1234567890123)).toBe('1,150GB'); + expect(formatGigaBytes(1234567890123)).toBe('1,235GB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatGigaBytes(1234567890)).toBe('1.15GB'); + expect(formatGigaBytes(1234567890)).toBe('1.23GB'); }); it('can return values with byte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1)).toBe('1GB 153MB 384KB 722B'); + expect(formatGigaBytes(1234567890, 3, 2, 1)).toBe('1GB 234MB 567KB 890B'); }); it('can return values with kilobyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024)).toBe('1GB 153MB 385KB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000)).toBe('1GB 234MB 568KB'); }); it('can return values with megabyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024 ** 2)).toBe('1GB 153MB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000 ** 2)).toBe('1GB 235MB'); }); it('can return values with gigabyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024 ** 3)).toBe('1GB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000 ** 3)).toBe('1GB'); }); }); @@ -58,23 +58,23 @@ describe('formatMegaBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatMegaBytes(1234567890)).toBe('1,177MB'); + expect(formatMegaBytes(1234567890)).toBe('1,235MB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatMegaBytes(1234567)).toBe('1.18MB'); + expect(formatMegaBytes(1234567)).toBe('1.23MB'); }); it('can return values with byte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1)).toBe('1MB 181KB 647B'); + expect(formatMegaBytes(1234567, 3, 2, 1)).toBe('1MB 234KB 567B'); }); it('can return values with kilobyte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1024)).toBe('1MB 182KB'); + expect(formatMegaBytes(1234567, 3, 2, 1000)).toBe('1MB 235KB'); }); it('can return values with megabyte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1024 ** 2)).toBe('1MB'); + expect(formatMegaBytes(1234567, 3, 2, 1000 ** 2)).toBe('1MB'); }); }); @@ -84,19 +84,19 @@ describe('formatKiloBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatKiloBytes(1234567)).toBe('1,206KB'); + expect(formatKiloBytes(1234567)).toBe('1,235KB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatKiloBytes(1234)).toBe('1.21KB'); + expect(formatKiloBytes(1234)).toBe('1.23KB'); }); it('can return values with byte precision', () => { - expect(formatKiloBytes(1234, 3, 2, 1)).toBe('1KB 210B'); + expect(formatKiloBytes(1234, 3, 2, 1)).toBe('1KB 234B'); }); it('can return values with kilobyte precision', () => { - expect(formatKiloBytes(1234, 3, 2, 1024)).toBe('1KB'); + expect(formatKiloBytes(1234, 3, 2, 1000)).toBe('1KB'); }); }); @@ -110,40 +110,53 @@ describe('formatBytes', () => { }); it('can return values with the kilobyte unit', () => { - expect(formatBytes(12345)).toBe('12.1KB'); + expect(formatBytes(12345)).toBe('12.3KB'); }); it('can return values with the megabyte unit', () => { - expect(formatBytes(1234567)).toBe('1.18MB'); + expect(formatBytes(1234567)).toBe('1.23MB'); }); it('can return values with the gigabyte unit', () => { - expect(formatBytes(1234567890)).toBe('1.15GB'); + expect(formatBytes(1234567890)).toBe('1.23GB'); }); it('can return values with byte precision', () => { - expect(formatBytes(12345, 3, 2, 1)).toBe('12KB 57B'); + expect(formatBytes(12345, 3, 2, 1)).toBe('12KB 345B'); }); it('can return values with kilobyte precision', () => { - expect(formatBytes(12345, 3, 2, 1024)).toBe('12KB'); + expect(formatBytes(12345, 3, 2, 1000)).toBe('12KB'); }); }); describe('findRoundBytesValueGreaterOrEqualTo', () => { - const expectedValues = [0, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000]; - - it('rounds small bytes values using base 10 round value', () => { + const expectedValues = [ + // minValue, expected + // minValue <= 1000 + [0, 0], + [3, 5], + [63, 100], + [511, 1000], + [1000, 2000], + + // 1000 < minValue <= 1000^4 + [2047, 4000], + [9999, 16000], + [123456, 128000], + [999999, 1000000], + [1_234_567, 2_000_000], + [1_234_567_891, 2_000_000_000], + + // minValue > 1000^4 + [1_234_567_891_234, 2_000_000_000_000], + [1_234_567_891_234_567, 2_000_000_000_000_000], + ]; + + it('correctly rounds bytes values using base 10 round value', () => { for (let i = 0; i < expectedValues.length; ++i) { - expect(findRoundBytesValueGreaterOrEqualTo(2 ** i - 1)).toBe( - expectedValues[i] - ); - } - }); - - it('rounds large bytes values using base 2 round value', () => { - for (let i = expectedValues.length; i < 40; ++i) { - expect(findRoundBytesValueGreaterOrEqualTo(2 ** i - 1)).toBe(2 ** i); + const [input, expected] = expectedValues[i]; + expect(findRoundBytesValueGreaterOrEqualTo(input)).toBe(expected); } }); }); diff --git a/src/test/unit/marker-schema.test.ts b/src/test/unit/marker-schema.test.ts index 5291f5b8c4..1fd6a8da0c 100644 --- a/src/test/unit/marker-schema.test.ts +++ b/src/test/unit/marker-schema.test.ts @@ -357,9 +357,9 @@ describe('marker schema formatting', function () { "bytes - 0B", "bytes - 10B", "bytes - 12B", - "bytes - 121KB", - "bytes - 118MB", - "bytes - 115GB", + "bytes - 123KB", + "bytes - 123MB", + "bytes - 123GB", "bytes - 0.000B", "integer - 0", "integer - 10", diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index 96b85e6d94..e28ce2de6d 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -26,6 +26,7 @@ import { getNativeSymbolInfo, computeTimeColumnForRawSamplesTable, getCallNodeFramePerStack, + getTotalNativeSymbolTimingsForCallNode, } from '../../profile-logic/profile-data'; import { resourceTypes } from '../../profile-logic/data-structures'; import { @@ -60,6 +61,8 @@ import type { RawProfileSharedData, IndexIntoFrameTable, IndexIntoSourceTable, + IndexIntoCategoryList, + IndexIntoNativeSymbolTable, } from 'firefox-profiler/types'; describe('string-table', function () { @@ -1481,7 +1484,7 @@ describe('getNativeSymbolsForCallNode', function () { ); expect( getNativeSymbolsForCallNode(callNodeFramePerStackAB, thread.frameTable) - ).toEqual([symB]); + ).toEqual(new Set([symB])); const callNodeFramePerStackABC = getCallNodeFramePerStack( ensureExists(abc), @@ -1490,7 +1493,7 @@ describe('getNativeSymbolsForCallNode', function () { ); expect( getNativeSymbolsForCallNode(callNodeFramePerStackABC, thread.frameTable) - ).toEqual([symB]); + ).toEqual(new Set([symB])); }); it('finds multiple symbols', function () { @@ -1531,13 +1534,195 @@ describe('getNativeSymbolsForCallNode', function () { thread.stackTable ); expect( - new Set( - getNativeSymbolsForCallNode(callNodeFramePerStackC, thread.frameTable) - ) + getNativeSymbolsForCallNode(callNodeFramePerStackC, thread.frameTable) ).toEqual(new Set([symB, symD])); }); }); +describe('getTotalNativeSymbolTimingsForCallNode', function () { + function getTimings( + thread: Thread, + callNodePath: CallNodePath, + defaultCategory: IndexIntoCategoryList, + isInverted: boolean + ): Map { + const { stackTable, frameTable, funcTable, samples } = thread; + const nonInvertedCallNodeInfo = getCallNodeInfo( + stackTable, + frameTable, + defaultCategory + ); + const callNodeInfo = isInverted + ? getInvertedCallNodeInfo( + nonInvertedCallNodeInfo, + defaultCategory, + funcTable.length + ) + : nonInvertedCallNodeInfo; + const callNodeIndex = ensureExists( + callNodeInfo.getCallNodeIndexFromPath(callNodePath), + 'invalid call node path' + ); + const callNodeFramePerStack = getCallNodeFramePerStack( + callNodeIndex, + callNodeInfo, + stackTable + ); + return getTotalNativeSymbolTimingsForCallNode( + samples, + callNodeFramePerStack, + frameTable + ); + } + + it('passes a basic test', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:file][sym:Asym:20:] + B[lib:file][sym:Bsym:30:] + `); + const [{ A, B }] = funcNamesDictPerThread; + const [{ Asym, Bsym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + // Compute the timings for the root call node. + // One total hit at symbol Asym. + const timingsRoot = getTimings(thread, [A], defaultCategory, false); + expect(timingsRoot.get(Asym)).toBe(1); + expect(timingsRoot.size).toBe(1); // no other hits + + // Compute the timings for the child call node. + // One total hit at symbol Bsym. + const timingsChild = getTimings(thread, [A, B], defaultCategory, false); + expect(timingsChild.get(Bsym)).toBe(1); + expect(timingsChild.size).toBe(1); // no other hits + }); + + it('passes a basic test with recursion', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:file][sym:Asym:20:] + B[lib:file][sym:Bsym:30:] + A[lib:file][sym:A2sym:40:] + `); + + const [{ A, B }] = funcNamesDictPerThread; + const [{ Asym, A2sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + // Compute the timings for the root call node. + // One total hit at symbol Asym. + const timingsRoot = getTimings(thread, [A], defaultCategory, false); + expect(timingsRoot.get(Asym)).toBe(1); + expect(timingsRoot.size).toBe(1); // no other hits + + // Compute the timings for the leaf call node. + // One total hit at symbol A2sym. + // In particular, we shouldn't record a hit for symbol Asym, even though + // the frame in Asym is also in A. But it's in the wrong call node. + const timingsChild = getTimings(thread, [A, B, A], defaultCategory, false); + expect(timingsChild.get(A2sym)).toBe(1); + expect(timingsChild.size).toBe(1); // no other hits + }); + + it('passes a test where the same function is called via different call paths', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] D[lib:one][sym:Dsym:40:] B[lib:one][sym:Bsym:30:] + C[lib:two][sym:Csym:10:] C[lib:two][sym:C2sym:50:] C[lib:two][sym:C3sym:60:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ A, B, C }] = funcNamesDictPerThread; + const [{ Csym, C3sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + const timingsABC = getTimings(thread, [A, B, C], defaultCategory, false); + expect(timingsABC.get(Csym)).toBe(1); + expect(timingsABC.get(C3sym)).toBe(1); + expect(timingsABC.size).toBe(2); // no other hits + }); + + it('passes a test with an inverted thread', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] D[lib:one][sym:Dsym:40:] B[lib:one][sym:Bsym:30:] + D[lib:one][sym:Dsym:40:] D[lib:one][sym:D2sym:50:] C[lib:two][sym:Csym:10:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ C, D }] = funcNamesDictPerThread; + const [{ Csym, Dsym, D2sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + // For the root D of the inverted tree, we have 3 native symbol hits. + const timingsD = getTimings(thread, [D], defaultCategory, true); + expect(timingsD.get(Dsym)).toBe(2); + expect(timingsD.get(D2sym)).toBe(1); + expect(timingsD.size).toBe(2); // no other hits + + // For the C call node which is a child (direct caller) of D, we have + // one hit at symbol Csym. + const timingsDC = getTimings(thread, [D, C], defaultCategory, true); + expect(timingsDC.get(Csym)).toBe(1); + expect(timingsDC.size).toBe(1); // no other hits + }); + + it('passes a test where a function is present in two different native symbols', function () { + // The funky part here is that the targeted call node has frames from two different native + // symbols: Two from native symbol Bsym, and one from native symbol Asym. That's + // because B is present both as its own native symbol (separate outer function) + // and as an inlined call from A. In other words, C has been inlined both into + // a standalone B and also into another copy of B which was inlined into A. + // + // This means that, if the user double clicks call node [A, B, C], there are two + // different symbols for which we may want to display the assembly code, and we + // should compute how much time is spent in each. + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] B[lib:one][sym:Asym:20:][inl:1] B[lib:one][sym:Bsym:30:] E[lib:one][sym:Esym:30:] + C[lib:one][sym:Bsym:30:][inl:1] C[lib:one][sym:Asym:20:][inl:2] C[lib:one][sym:Bsym:30:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ A, B, C }] = funcNamesDictPerThread; + const [{ Asym, Bsym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + const timingsABCForBsym = getTimings( + thread, + [A, B, C], + defaultCategory, + false + ); + expect(timingsABCForBsym.get(Asym)).toBe(1); + expect(timingsABCForBsym.get(Bsym)).toBe(2); + expect(timingsABCForBsym.size).toBe(2); // no other hits + }); +}); + describe('getNativeSymbolInfo', function () { it('calculates the correct native symbol info', function () { const { profile, nativeSymbolsDictPerThread } = getProfileFromTextSamples(` diff --git a/src/test/unit/window-console.test.ts b/src/test/unit/window-console.test.ts index d2e1d84847..82c939f7c3 100644 --- a/src/test/unit/window-console.test.ts +++ b/src/test/unit/window-console.test.ts @@ -79,6 +79,39 @@ describe('console-accessible values on the window object', function () { `); }); + it('can extract gecko logs with log level already in module', function () { + const profile = getProfileWithMarkers([ + [ + 'LogMessages', + 170, + null, + { + type: 'Log', + module: 'D/nsHttp', + name: 'ParentChannelListener::ParentChannelListener [this=7fb5e19b98d0, next=7fb5f48f2320]', + }, + ], + [ + 'LogMessages', + 190, + null, + { + type: 'Log', + name: 'nsJARChannel::nsJARChannel [this=0x87f1ec80]\n', + module: 'D/nsJarProtocol', + }, + ], + ]); + const store = storeWithProfile(profile); + const target: MixedObject = {}; + addDataToWindowObject(store.getState, store.dispatch, target); + const result = (target as any).extractGeckoLogs(); + expect(result).toBe(stripIndent` + 1970-01-01 00:00:00.170000000 UTC - [Unknown Process 0: Empty]: D/nsHttp ParentChannelListener::ParentChannelListener [this=7fb5e19b98d0, next=7fb5f48f2320] + 1970-01-01 00:00:00.190000000 UTC - [Unknown Process 0: Empty]: D/nsJarProtocol nsJARChannel::nsJARChannel [this=0x87f1ec80] + `); + }); + describe('totalMarkerDuration', function () { function setup(): ExtraPropertiesOnWindowForConsole { jest.spyOn(console, 'log').mockImplementation(() => {}); diff --git a/src/utils/colors.ts b/src/utils/colors.ts index 98c8be73e0..1fc7bd19ce 100644 --- a/src/utils/colors.ts +++ b/src/utils/colors.ts @@ -101,6 +101,10 @@ const DEFAULT_STYLE: ColorStyles = { gravity: 0, }; +// Colors based on photon colors. +export const PURPLE_55 = '#8a00eb'; +export const YELLOW_65 = '#be9b00'; + const PSEUDO_TRANSPARENT_STYLE: ColorStyles = { ...DEFAULT_STYLE, _selectedFillStyle: [GREY_30, GREY_70], @@ -170,15 +174,15 @@ const STYLE_MAP: { [key: string]: ColorStyles } = { }, green: { ...DEFAULT_STYLE, - _selectedFillStyle: [GREEN_60, GREEN_80], - _unselectedFillStyle: [GREEN_60 + '60', GREEN_80 + '60'], + _selectedFillStyle: [GREEN_60, GREEN_70], + _unselectedFillStyle: [GREEN_60 + '60', GREEN_70 + '60'], _selectedTextColor: '#fff', gravity: 4, }, purple: { ...DEFAULT_STYLE, - _selectedFillStyle: [PURPLE_70, PURPLE_60], - _unselectedFillStyle: [PURPLE_70 + '60', PURPLE_60 + '60'], + _selectedFillStyle: [PURPLE_70, PURPLE_55], + _unselectedFillStyle: [PURPLE_70 + '60', PURPLE_55 + '70'], _selectedTextColor: '#fff', gravity: 5, }, @@ -187,9 +191,9 @@ const STYLE_MAP: { [key: string]: ColorStyles } = { _selectedFillStyle: [ // This yellow has more contrast than YELLOW_50. '#ffe129', - YELLOW_70, + YELLOW_65, ], - _unselectedFillStyle: [YELLOW_50 + '70', YELLOW_60 + '70'], + _unselectedFillStyle: [YELLOW_50 + '70', YELLOW_65 + '85'], _selectedTextColor: ['#000', GREY_20], gravity: 6, }, diff --git a/src/utils/format-numbers.ts b/src/utils/format-numbers.ts index 77ecb185a7..7592599a16 100644 --- a/src/utils/format-numbers.ts +++ b/src/utils/format-numbers.ts @@ -180,7 +180,7 @@ export function formatGigaBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerGigabyte = 1024 ** 3; + const bytesPerGigabyte = 1000 ** 3; if (precision === Infinity) { return ( formatNumber( @@ -210,7 +210,7 @@ export function formatMegaBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerMegabyte = 1024 ** 2; + const bytesPerMegabyte = 1000 ** 2; if (precision === Infinity) { return ( formatNumber( @@ -240,7 +240,7 @@ export function formatKiloBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerKilobyte = 1024; + const bytesPerKilobyte = 1000; if (precision === Infinity) { return ( formatNumber( @@ -274,14 +274,14 @@ export function formatBytes( // Use singles up to 10,000. I think 9,360B looks nicer than 9.36KB. // We use "0" for significantDigits because bytes will always be integers. return formatNumber(bytes, 0) + 'B'; - } else if (bytes < 1024 ** 2) { + } else if (bytes < 1000 ** 2) { return formatKiloBytes( bytes, significantDigits, maxFractionalDigits, precision ); - } else if (bytes < 1024 ** 3) { + } else if (bytes < 1000 ** 3) { return formatMegaBytes( bytes, significantDigits, @@ -542,10 +542,10 @@ function _findRoundValueGreaterOrEqualTo(minValue: number): number { export function findRoundBytesValueGreaterOrEqualTo(minValue: number): number { // Special case KB, MB, GB. - if (minValue > 1024 && minValue <= 1024 ** 4) { + if (minValue > 1000 && minValue <= 1000 ** 4) { for (const power of [1, 2, 3]) { for (const value of [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]) { - const number = value * 1024 ** power; + const number = value * 1000 ** power; if (minValue <= number) { return number; } diff --git a/src/utils/window-console.ts b/src/utils/window-console.ts index 12ad179482..0beb4651fd 100644 --- a/src/utils/window-console.ts +++ b/src/utils/window-console.ts @@ -323,8 +323,12 @@ export function addDataToWindowObject( ) { const strTimestamp = d2s(profile.meta.startTime + markerStartTime); const processName = thread.processName ?? 'Unknown Process'; - // TODO: lying about the log level as it's not available yet in the markers - const statement = `${strTimestamp} - [${processName} ${thread.pid}: ${thread.name}]: D/${(data as any).module} ${(data as any).name.trim()}`; + + // The log module may contain the log level for profiles captured after bug 1995503. + // If the log module does not contain /, we fake it to D/module + const logModule = (data as any).module; + const prefix = logModule.includes('/') ? '' : 'D/'; + const statement = `${strTimestamp} - [${processName} ${thread.pid}: ${thread.name}]: ${prefix}${logModule} ${(data as any).name.trim()}`; logs.push(statement); } }