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
5 changes: 1 addition & 4 deletions src/actions/profile-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,7 @@ function getInformationFromTrackReference(
relatedThreadIndex: localTrack.threadIndex,
relatedTab: null,
};
case 'memory':
case 'bandwidth':
case 'process-cpu':
case 'power': {
case 'counter': {
const counterSelectors = getCounterSelectors(localTrack.counterIndex);
const counter = counterSelectors.getCounter(state);
return {
Expand Down
22 changes: 4 additions & 18 deletions src/app-logic/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,10 @@ export const TRACK_NETWORK_ROW_REPEAT = 7;
export const TRACK_NETWORK_HEIGHT =
TRACK_NETWORK_ROW_HEIGHT * TRACK_NETWORK_ROW_REPEAT;

// The following values are for memory track.
export const TRACK_MEMORY_GRAPH_HEIGHT = 25;
export const TRACK_MEMORY_MARKERS_HEIGHT = 15;
export const TRACK_MEMORY_HEIGHT =
TRACK_MEMORY_GRAPH_HEIGHT + TRACK_MEMORY_MARKERS_HEIGHT;
export const TRACK_MEMORY_LINE_WIDTH = 2;

// The following values are for the bandwidth track.
export const TRACK_BANDWIDTH_HEIGHT = 25;
export const TRACK_BANDWIDTH_LINE_WIDTH = 2;
// The following values are for counter tracks (Memory, Power, Bandwidth, etc.).
export const TRACK_COUNTER_GRAPH_HEIGHT = 25;
export const TRACK_COUNTER_MARKERS_HEIGHT = 15;
export const TRACK_COUNTER_LINE_WIDTH = 2;

// The following values are for experimental event delay track.
export const TRACK_EVENT_DELAY_HEIGHT = 40;
Expand All @@ -59,14 +53,6 @@ export const TRACK_PROCESS_BLANK_HEIGHT = 30;
// Height of timeline ruler.
export const TIMELINE_RULER_HEIGHT = 20;

// Height of the power track.
export const TRACK_POWER_HEIGHT = 25;
export const TRACK_POWER_LINE_WIDTH = 2;

// Height of the process cpu track.
export const TRACK_PROCESS_CPU_HEIGHT = 25;
export const TRACK_PROCESS_CPU_LINE_WIDTH = 2;

// JS Tracer has very high fidelity information, and needs a more fine-grained zoom.
export const JS_TRACER_MAXIMUM_CHART_ZOOM = 0.001;

Expand Down
254 changes: 253 additions & 1 deletion src/app-logic/url-handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { StringTable } from 'firefox-profiler/utils/string-table';
import type { ProfileUpgradeInfo } from 'firefox-profiler/profile-logic/processed-profile-versioning';
import type { ProfileAndProfileUpgradeInfo } from 'firefox-profiler/actions/receive-profile';

export const CURRENT_URL_VERSION = 15;
export const CURRENT_URL_VERSION = 16;

/**
* This static piece of state might look like an anti-pattern, but it's a relatively
Expand Down Expand Up @@ -1369,8 +1369,260 @@ const _upgraders: {
.map(mapIndexesInTransform)
.join('~');
},
[16]: (
processedLocation: ProcessedLocationBeforeUpgrade,
profile?: Profile
) => {
// The 'memory', 'power', 'bandwidth', and 'process-cpu' LocalTrack types
// have been collapsed into a single 'counter' type. This moves counter
// tracks into a single group inside each PID's local track array, which
// shifts the track indexes that older URLs recorded. Remap
// localTrackOrderByPid and hiddenLocalTracksByPid so existing URLs keep
// pointing at the same tracks.
const { query } = processedLocation;
if (!profile || !profile.counters || profile.counters.length === 0) {
return;
}
if (!query.localTrackOrderByPid && !query.hiddenLocalTracksByPid) {
return;
}

const oldToNewIndexByPid = _computeV16LocalTrackIndexRemap(profile);

if (query.localTrackOrderByPid) {
query.localTrackOrderByPid = (query.localTrackOrderByPid as string)
.split('~')
.map((pidAndTracks) => {
const dash = pidAndTracks.indexOf('-');
if (dash === -1) {
return pidAndTracks;
}
const pid = pidAndTracks.slice(0, dash);
const encoded = pidAndTracks.slice(dash + 1);
const remap = oldToNewIndexByPid.get(pid);
if (!remap) {
return pidAndTracks;
}
const oldOrder = decodeUintArrayFromUrlComponent(encoded);
const newOrder = [];
for (const oldIndex of oldOrder) {
const newIndex = remap[oldIndex];
if (newIndex !== undefined && newIndex !== null) {
newOrder.push(newIndex);
}
}
return `${pid}-${encodeUintArrayForUrlComponent(newOrder)}`;
})
.join('~');
}

if (query.hiddenLocalTracksByPid) {
query.hiddenLocalTracksByPid = (query.hiddenLocalTracksByPid as string)
.split('~')
.map((pidAndTracks) => {
const dash = pidAndTracks.indexOf('-');
if (dash === -1) {
return pidAndTracks;
}
const pid = pidAndTracks.slice(0, dash);
const encoded = pidAndTracks.slice(dash + 1);
const remap = oldToNewIndexByPid.get(pid);
if (!remap) {
return pidAndTracks;
}
const oldHidden = decodeUintArrayFromUrlComponent(encoded);
const newHidden = new Set<number>();
for (const oldIndex of oldHidden) {
const newIndex = remap[oldIndex];
if (newIndex !== undefined && newIndex !== null) {
newHidden.add(newIndex);
}
}
return `${pid}-${encodeUintSetForUrlComponent(newHidden)}`;
})
.join('~');
}
},
};

/**
* Produce a per-PID mapping from old local-track indexes (using the pre-v16
* LOCAL_TRACK_INDEX_ORDER) to new local-track indexes (post-v16). Used by the
* v16 URL upgrader.
*
* The two layouts share the same set of tracks; only the relative positions of
* counter tracks differ. The helper reconstructs both layouts by simulating
* what computeLocalTracksByPid would have produced under each.
*/
function _computeV16LocalTrackIndexRemap(
profile: Profile
): Map<Pid, Array<number | null>> {
// Pre-v16 LOCAL_TRACK_INDEX_ORDER.
// Note: we don't preserve 'event-delay' and 'process-cpu' in URLs,
// as they are experimental.
const OLD_SLOT = {
thread: 0,
network: 1,
memory: 2,
ipc: 3,
marker: 7,
power: 6,
bandwidth: 8,
Comment thread
fatadel marked this conversation as resolved.
};
// Post-v16 LOCAL_TRACK_INDEX_ORDER.
const NEW_SLOT = {
thread: 0,
network: 1,
counter: 2,
ipc: 3,
marker: 5,
};

type Entry = {
id: string;
oldSlot: number | null;
newSlot: number | null;
};

const entriesByPid = new Map<Pid, Entry[]>();
const ensurePid = (pid: Pid): Entry[] => {
let entries = entriesByPid.get(pid);
if (entries === undefined) {
entries = [];
entriesByPid.set(pid, entries);
}
return entries;
};

const markerSchemasWithGraphs = (profile.meta.markerSchema || []).filter(
(schema) => Array.isArray(schema.graphs) && schema.graphs.length > 0
);

for (
let threadIndex = 0;
threadIndex < profile.threads.length;
threadIndex++
) {
const thread = profile.threads[threadIndex];
const { pid, markers } = thread;

if (!thread.isMainThread) {
ensurePid(pid).push({
id: `t:${threadIndex}`,
oldSlot: OLD_SLOT.thread,
newSlot: NEW_SLOT.thread,
});
}
if (markers.data.some((datum) => datum && datum.type === 'Network')) {
ensurePid(pid).push({
id: `n:${threadIndex}`,
oldSlot: OLD_SLOT.network,
newSlot: NEW_SLOT.network,
});
}
if (markers.data.some((datum) => datum && datum.type === 'IPC')) {
ensurePid(pid).push({
id: `i:${threadIndex}`,
oldSlot: OLD_SLOT.ipc,
newSlot: NEW_SLOT.ipc,
});
}

if (markerSchemasWithGraphs.length > 0) {
const markerTracksBySchemaName: Map<
string,
{ keys: string[]; markerNames: Set<number> }
> = new Map();
for (const markerSchema of markerSchemasWithGraphs) {
markerTracksBySchemaName.set(markerSchema.name, {
keys: (markerSchema.graphs || []).map((graph) => graph.key),
markerNames: new Set(),
});
}
for (let i = 0; i < markers.length; ++i) {
const markerNameIndex = markers.name[i];
const markerData = markers.data[i];
if (markerData && markerData.type) {
const mapEntry = markerTracksBySchemaName.get(markerData.type);
if (mapEntry && mapEntry.keys.every((k) => k in markerData)) {
mapEntry.markerNames.add(markerNameIndex);
}
}
}
for (const [schemaName, { markerNames }] of markerTracksBySchemaName) {
for (const markerName of markerNames) {
ensurePid(pid).push({
id: `m:${threadIndex}:${schemaName}:${markerName}`,
oldSlot: OLD_SLOT.marker,
newSlot: NEW_SLOT.marker,
});
}
}
}
}

const { counters } = profile;
if (counters) {
for (let counterIndex = 0; counterIndex < counters.length; counterIndex++) {
const counter = counters[counterIndex];
const { pid, category, name, samples } = counter;

// OLD behavior: only Memory / Bandwidth / Power produced tracks.
let oldSlot: number | null;
if (category === 'Memory') {
oldSlot = OLD_SLOT.memory;
} else if (category === 'Bandwidth') {
oldSlot = OLD_SLOT.bandwidth;
}
// We assumed there was no data when <= 2 samples.
else if (category === 'power' && samples.length > 2) {
oldSlot = OLD_SLOT.power;
} else {
oldSlot = null;
}

// NEW behavior: mirror computeLocalTracksByPid. processCPU counters are
// added later by addProcessCPUTracksForProcess when the experimental
// toggle fires, every other counter becomes a track.
const newSlot: number | null =
category === 'CPU' && name === 'processCPU' ? null : NEW_SLOT.counter;

if (oldSlot === null && newSlot === null) {
continue;
}

ensurePid(pid).push({
id: `c:${counterIndex}`,
oldSlot,
newSlot,
});
}
}

const remapByPid = new Map<Pid, Array<number | null>>();
for (const [pid, entries] of entriesByPid) {
const oldList = entries
.filter((e) => e.oldSlot !== null)
.slice()
.sort((a, b) => (a.oldSlot as number) - (b.oldSlot as number));
const newList = entries
.filter((e) => e.newSlot !== null)
.slice()
.sort((a, b) => (a.newSlot as number) - (b.newSlot as number));

const newIdToIndex = new Map<string, number>();
newList.forEach((entry, i) => newIdToIndex.set(entry.id, i));

const remap: Array<number | null> = oldList.map((entry) => {
const newIndex = newIdToIndex.get(entry.id);
return newIndex === undefined ? null : newIndex;
});
remapByPid.set(pid, remap);
}

return remapByPid;
}

for (let destVersion = 1; destVersion <= CURRENT_URL_VERSION; destVersion++) {
if (!_upgraders[destVersion]) {
throw new Error(`There is no upgrader for version ${destVersion}.`);
Expand Down
20 changes: 4 additions & 16 deletions src/components/timeline/LocalTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread';
import { TimelineTrackThread } from './TrackThread';
import { TrackEventDelay } from './TrackEventDelay';
import { TrackNetwork } from './TrackNetwork';
import { TrackMemory } from './TrackMemory';
import { TrackBandwidth } from './TrackBandwidth';
import { TrackCounter } from './TrackCounter';
import { TrackIPC } from './TrackIPC';
import { TrackProcessCPU } from './TrackProcessCPU';
import { TrackPower } from './TrackPower';
import { getTrackSelectionModifiers } from 'firefox-profiler/utils';
import type {
TrackReference,
Expand Down Expand Up @@ -106,18 +103,12 @@ class LocalTrackComponent extends PureComponent<Props> {
);
case 'network':
return <TrackNetwork threadIndex={localTrack.threadIndex} />;
case 'memory':
return <TrackMemory counterIndex={localTrack.counterIndex} />;
case 'bandwidth':
return <TrackBandwidth counterIndex={localTrack.counterIndex} />;
case 'counter':
return <TrackCounter counterIndex={localTrack.counterIndex} />;
case 'ipc':
return <TrackIPC threadIndex={localTrack.threadIndex} />;
case 'event-delay':
return <TrackEventDelay threadIndex={localTrack.threadIndex} />;
case 'process-cpu':
return <TrackProcessCPU counterIndex={localTrack.counterIndex} />;
case 'power':
return <TrackPower counterIndex={localTrack.counterIndex} />;
case 'marker':
return (
<TrackCustomMarker
Expand Down Expand Up @@ -241,10 +232,7 @@ export const TimelineLocalTrack = explicitConnect<
'Event Delay of ' + selectors.getThreadProcessDetails(state);
break;
}
case 'memory':
case 'bandwidth':
case 'process-cpu':
case 'power': {
case 'counter': {
titleText = getCounterSelectors(localTrack.counterIndex).getDescription(
state
);
Expand Down
Loading
Loading