Skip to content
Open
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
54 changes: 54 additions & 0 deletions src/actions/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getIsEventDelayTracksEnabled,
getIsExperimentalCPUGraphsEnabled,
getIsExperimentalProcessCPUTracksEnabled,
getIsExperimentalSamplingIntervalTracksEnabled,
} from 'firefox-profiler/selectors/app';
import {
getLocalTracksByPid,
Expand All @@ -30,6 +31,7 @@ import {
addEventDelayTracksForThreads,
initializeLocalTrackOrderByPid,
addProcessCPUTracksForProcess,
addSamplingIntervalTracksForProcess,
} from 'firefox-profiler/profile-logic/tracks';
import { selectedThreadSelectors } from 'firefox-profiler/selectors/per-thread';
import {
Expand Down Expand Up @@ -369,6 +371,58 @@ export function enableExperimentalProcessCPUTracks(): ThunkAction<boolean> {
};
}

/*
* This action enables the sampling interval tracks. They are hidden by default
* and are useful for visualizing the actual sampling intervals per process.
* There is no UI that triggers this action in the profiler interface. Instead,
* users have to enable this from the developer console by writing this line:
* `experimental.enableSamplingIntervalTracks()`
*/
export function enableExperimentalSamplingIntervalTracks(): ThunkAction<boolean> {
return (dispatch, getState) => {
if (getIsExperimentalSamplingIntervalTracksEnabled(getState())) {
console.error(
'Tried to enable the sampling interval tracks, but they are already enabled.'
);
return false;
}

const profile = getProfile(getState());

// Check if there are any threads with samples
const hasThreadsWithSamples = profile.threads.some(
(thread) => thread.samples.length > 0
);

if (!hasThreadsWithSamples) {
console.error(
'Tried to enable the sampling interval tracks, but this profile does not have any threads with samples.'
);
return false;
}

const oldLocalTracks = getLocalTracksByPid(getState());
const localTracksByPid = addSamplingIntervalTracksForProcess(
profile,
oldLocalTracks
);
const localTrackOrderByPid = initializeLocalTrackOrderByPid(
getLocalTrackOrderByPid(getState()),
localTracksByPid,
null,
profile
);

dispatch({
type: 'ENABLE_EXPERIMENTAL_SAMPLING_INTERVAL_TRACKS',
localTracksByPid,
localTrackOrderByPid,
});

return true;
};
}

/**
* This caches the profile data in the local state for synchronous access.
*/
Expand Down
18 changes: 18 additions & 0 deletions src/actions/profile-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getLocalTracksByPid,
getThreads,
getLastNonShiftClick,
getProfile,
} from 'firefox-profiler/selectors/profile';
import {
getThreadSelectors,
Expand Down Expand Up @@ -359,6 +360,23 @@ function getInformationFromTrackReference(
relatedTab: null,
};
}
case 'sampling-interval': {
// Find the first thread for this PID to use as related thread.
// If no thread is found, use the first thread in the profile as fallback.
const profile = getProfile(state);
const pidThread = profile.threads.find(
(thread: { pid: Pid }) => thread.pid === localTrack.pid
);
const relatedThreadIndex = pidThread
? profile.threads.indexOf(pidThread)
: 0;
return {
...commonLocalProperties,
threadIndex: null,
relatedThreadIndex,
relatedTab: null,
};
}
default:
throw assertExhaustiveCheck(localTrack, `Unhandled LocalTrack type.`);
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/timeline/LocalTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { TrackBandwidth } from './TrackBandwidth';
import { TrackIPC } from './TrackIPC';
import { TrackProcessCPU } from './TrackProcessCPU';
import { TrackPower } from './TrackPower';
import { TrackSamplingInterval } from './TrackSamplingInterval';
import { getTrackSelectionModifiers } from 'firefox-profiler/utils';
import type {
TrackReference,
Expand Down Expand Up @@ -118,6 +119,8 @@ class LocalTrackComponent extends PureComponent<Props> {
return <TrackProcessCPU counterIndex={localTrack.counterIndex} />;
case 'power':
return <TrackPower counterIndex={localTrack.counterIndex} />;
case 'sampling-interval':
return <TrackSamplingInterval pid={localTrack.pid} />;
case 'marker':
return (
<TrackCustomMarker
Expand Down Expand Up @@ -250,6 +253,10 @@ export const TimelineLocalTrack = explicitConnect<
);
break;
}
case 'sampling-interval': {
titleText = 'Sampling Intervals';
break;
}
default:
throw assertExhaustiveCheck(localTrack, `Unhandled LocalTrack type.`);
}
Expand Down
44 changes: 44 additions & 0 deletions src/components/timeline/TrackSamplingInterval.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* 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/. */

.timelineTrackSamplingInterval {
position: relative;
}

.timelineTrackSamplingIntervalCanvas {
width: 100%;
height: var(--graph-height);
}

.timelineTrackSamplingIntervalGraph {
position: relative;
height: 100%;
cursor: default;
}

.timelineTrackSamplingIntervalTooltip {
display: grid;
gap: 2px 5px;
grid-template-columns: min-content minmax(0, 1fr);
}

.timelineTrackSamplingIntervalTooltipLabel {
color: var(--grey-50);
text-align: right;
white-space: nowrap;
}

.timelineTrackSamplingIntervalTooltipValue {
font-variant-numeric: tabular-nums;
}

.timelineTrackSamplingIntervalGraphDot {
position: absolute;
width: 6px;
height: 6px;
border-radius: 3px;
margin-top: -3px;
margin-left: -3px;
pointer-events: none;
}
78 changes: 78 additions & 0 deletions src/components/timeline/TrackSamplingInterval.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* 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/. */

import * as React from 'react';
import explicitConnect from 'firefox-profiler/utils/connect';
import { getCommittedRange } from 'firefox-profiler/selectors/profile';
import { updatePreviewSelection } from 'firefox-profiler/actions/profile-view';
import { TrackSamplingIntervalGraph } from './TrackSamplingIntervalGraph';
import {
TRACK_PROCESS_CPU_HEIGHT,
TRACK_PROCESS_CPU_LINE_WIDTH,
} from 'firefox-profiler/app-logic/constants';

import type { Pid, Milliseconds } from 'firefox-profiler/types';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';

import './TrackSamplingInterval.css';

type OwnProps = {
readonly pid: Pid;
};

type StateProps = {
readonly rangeStart: Milliseconds;
readonly rangeEnd: Milliseconds;
};

type DispatchProps = {
updatePreviewSelection: typeof updatePreviewSelection;
};

type Props = ConnectedProps<OwnProps, StateProps, DispatchProps>;

type State = {};

export class TrackSamplingIntervalImpl extends React.PureComponent<
Props,
State
> {
override render() {
const { pid } = this.props;
return (
<div
className="timelineTrackSamplingInterval"
style={
{
height: TRACK_PROCESS_CPU_HEIGHT,
'--graph-height': `${TRACK_PROCESS_CPU_HEIGHT}px`,
} as React.CSSProperties
}
>
<TrackSamplingIntervalGraph
pid={pid}
lineWidth={TRACK_PROCESS_CPU_LINE_WIDTH}
graphHeight={TRACK_PROCESS_CPU_HEIGHT}
/>
</div>
);
}
}

export const TrackSamplingInterval = explicitConnect<
OwnProps,
StateProps,
DispatchProps
>({
mapStateToProps: (state) => {
const { start, end } = getCommittedRange(state);
return {
rangeStart: start,
rangeEnd: end,
};
},
mapDispatchToProps: { updatePreviewSelection },
component: TrackSamplingIntervalImpl,
});
Loading