diff --git a/static/app/components/events/interfaces/spans/profilingMeasurements.tsx b/static/app/components/events/interfaces/spans/profilingMeasurements.tsx
index 8b9adaf9e95864..a504fe32f42c4c 100644
--- a/static/app/components/events/interfaces/spans/profilingMeasurements.tsx
+++ b/static/app/components/events/interfaces/spans/profilingMeasurements.tsx
@@ -211,7 +211,7 @@ function ProfilingMeasurements({
onMouseDown={onStartWindowSelection}
>
diff --git a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx
index b52c6d6f27f23b..c87fbf429d9be5 100644
--- a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx
+++ b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx
@@ -46,16 +46,19 @@ import {
initializeFlamegraphRenderer,
useResizeCanvasObserver,
} from 'sentry/utils/profiling/gl/utils';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {ContinuousProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
import {FlamegraphRenderer2D} from 'sentry/utils/profiling/renderers/flamegraphRenderer2D';
import {FlamegraphRendererWebGL} from 'sentry/utils/profiling/renderers/flamegraphRendererWebGL';
import {Rect} from 'sentry/utils/profiling/speedscope';
import {UIFrames} from 'sentry/utils/profiling/uiFrames';
-import {fromNanoJoulesToWatts} from 'sentry/utils/profiling/units/units';
+import {
+ fromNanoJoulesToWatts,
+ type ProfilingFormatterUnit,
+} from 'sentry/utils/profiling/units/units';
import {useDevicePixelRatio} from 'sentry/utils/useDevicePixelRatio';
import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
import {useContinuousProfile} from 'sentry/views/profiling/continuousProfileProvider';
-import {useProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
+import {useContinuousProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
import {FlamegraphDrawer} from './flamegraphDrawer/flamegraphDrawer';
import {FlamegraphWarnings} from './flamegraphOverlays/FlamegraphWarnings';
@@ -64,7 +67,7 @@ import {FlamegraphChart} from './flamegraphChart';
import {FlamegraphLayout} from './flamegraphLayout';
import {FlamegraphUIFrames} from './flamegraphUIFrames';
-function getMaxConfigSpace(profileGroup: ProfileGroup): Rect {
+function getMaxConfigSpace(profileGroup: ContinuousProfileGroup): Rect {
// We have a transaction, so we should do our best to align the profile
// with the transaction's timeline.
const maxProfileDuration = Math.max(...profileGroup.profiles.map(p => p.duration));
@@ -123,7 +126,7 @@ export function ContinuousFlamegraph(): ReactElement {
const dispatch = useDispatchFlamegraphState();
const profiles = useContinuousProfile();
- const profileGroup = useProfileGroup();
+ const profileGroup = useContinuousProfileGroup();
const flamegraphTheme = useFlamegraphTheme();
const position = useFlamegraphZoomPosition();
@@ -229,7 +232,11 @@ export function ContinuousFlamegraph(): ReactElement {
}
return new UIFrames(
{
+ // @TODO
+ // @ts-expect-error
slow: profileGroup.measurements?.slow_frame_renders,
+ // @TODO
+ // @ts-expect-error
frozen: profileGroup.measurements?.frozen_frame_renders,
},
{unit: flamegraph.profile.unit},
@@ -251,25 +258,31 @@ export function ContinuousFlamegraph(): ReactElement {
for (const key in profileGroup.measurements) {
if (key === 'cpu_energy_usage') {
- measures.push({
- ...profileGroup.measurements[key]!,
- values: profileGroup.measurements[key]!.values.map(v => {
- return {
- elapsed_since_start_ns: v.elapsed_since_start_ns,
- value: fromNanoJoulesToWatts(v.value, 0.1),
- };
- }),
- // some versions of cocoa send byte so we need to correct it to watt
- unit: 'watt',
- name: 'CPU energy usage',
- });
+ const measurements = profileGroup.measurements[key]!;
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ let offset = 0;
+ for (let i = 0; i < measurements.values.length; i++) {
+ const value = measurements.values[i];
+ const next = measurements.values[i + 1] ?? value;
+ offset += (next.timestamp - value.timestamp) * 1e3;
+
+ values.push({
+ value: fromNanoJoulesToWatts(value.value, 0.1),
+ elapsed: offset,
+ });
+ }
+
+ // some versions of cocoa send byte so we need to correct it to watt
+ measures.push({name: 'CPU energy usage', unit: 'watt', values});
}
}
return new FlamegraphChartModel(
Rect.From(flamegraph.configSpace),
measures.length > 0 ? measures : [],
- flamegraphTheme.COLORS.BATTERY_CHART_COLORS
+ flamegraphTheme.COLORS.BATTERY_CHART_COLORS,
+ {timelineUnit: 'milliseconds'}
);
}, [profileGroup.measurements, flamegraph.configSpace, flamegraphTheme, hasCPUChart]);
@@ -278,7 +291,7 @@ export function ContinuousFlamegraph(): ReactElement {
return LOADING_OR_FALLBACK_CPU_CHART;
}
- const measures: ProfileSeriesMeasurement[] = [];
+ const cpuMeasurements: ProfileSeriesMeasurement[] = [];
for (const key in profileGroup.measurements) {
if (key.startsWith('cpu_usage')) {
@@ -286,14 +299,31 @@ export function ContinuousFlamegraph(): ReactElement {
key === 'cpu_usage'
? 'Average CPU usage'
: `CPU Core ${key.replace('cpu_usage_', '')}`;
- measures.push({...profileGroup.measurements[key]!, name});
+
+ const measurements = profileGroup.measurements[key]!;
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ let offset = 0;
+ for (let i = 0; i < measurements.values.length; i++) {
+ const value = measurements.values[i];
+ const next = measurements.values[i + 1] ?? value;
+ offset += (next.timestamp - value.timestamp) * 1e3;
+
+ values.push({
+ value: value.value,
+ elapsed: offset,
+ });
+ }
+
+ cpuMeasurements.push({name, unit: measurements?.unit, values});
}
}
return new FlamegraphChartModel(
Rect.From(flamegraph.configSpace),
- measures.length > 0 ? measures : [],
- flamegraphTheme.COLORS.CPU_CHART_COLORS
+ cpuMeasurements.length > 0 ? cpuMeasurements : [],
+ flamegraphTheme.COLORS.CPU_CHART_COLORS,
+ {timelineUnit: 'milliseconds'}
);
}, [profileGroup.measurements, flamegraph.configSpace, flamegraphTheme, hasCPUChart]);
@@ -306,17 +336,47 @@ export function ContinuousFlamegraph(): ReactElement {
const memory_footprint = profileGroup.measurements?.memory_footprint;
if (memory_footprint) {
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ let offset = 0;
+ for (let i = 0; i < memory_footprint.values.length; i++) {
+ const value = memory_footprint.values[i];
+ const next = memory_footprint.values[i + 1] ?? value;
+ offset += (next.timestamp - value.timestamp) * 1e3;
+
+ values.push({
+ value: value.value,
+ elapsed: offset,
+ });
+ }
+
measures.push({
- ...memory_footprint!,
+ unit: memory_footprint.unit,
name: 'Heap Usage',
+ values,
});
}
const native_memory_footprint = profileGroup.measurements?.memory_native_footprint;
if (native_memory_footprint) {
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ let offset = 0;
+ for (let i = 0; i < native_memory_footprint.values.length; i++) {
+ const value = native_memory_footprint.values[i];
+ const next = native_memory_footprint.values[i + 1] ?? value;
+ offset += (next.timestamp - value.timestamp) * 1e3;
+
+ values.push({
+ value: value.value,
+ elapsed: offset,
+ });
+ }
+
measures.push({
- ...native_memory_footprint!,
+ unit: native_memory_footprint.unit,
name: 'Native Heap Usage',
+ values,
});
}
@@ -324,7 +384,7 @@ export function ContinuousFlamegraph(): ReactElement {
Rect.From(flamegraph.configSpace),
measures.length > 0 ? measures : [],
flamegraphTheme.COLORS.MEMORY_CHART_COLORS,
- {type: 'area'}
+ {type: 'area', timelineUnit: 'milliseconds'}
);
}, [
profileGroup.measurements,
@@ -1080,7 +1140,7 @@ export function ContinuousFlamegraph(): ReactElement {
batteryChart={
hasBatteryChart ? (
{
return {
- elapsed_since_start_ns: v.elapsed_since_start_ns,
+ elapsed: v.elapsed_since_start_ns,
value: fromNanoJoulesToWatts(v.value, 0.1),
};
}),
@@ -414,7 +414,7 @@ function Flamegraph(): ReactElement {
return LOADING_OR_FALLBACK_CPU_CHART;
}
- const measures: ProfileSeriesMeasurement[] = [];
+ const cpuMeasurements: ProfileSeriesMeasurement[] = [];
for (const key in profileGroup.measurements) {
if (key.startsWith('cpu_usage')) {
@@ -422,13 +422,24 @@ function Flamegraph(): ReactElement {
key === 'cpu_usage'
? 'Average CPU usage'
: `CPU Core ${key.replace('cpu_usage_', '')}`;
- measures.push({...profileGroup.measurements[key]!, name});
+
+ const measurements = profileGroup.measurements[key]!;
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ for (let i = 0; i < measurements.values.length; i++) {
+ const value = measurements.values[i];
+ values.push({
+ value: value.value,
+ elapsed: value.elapsed_since_start_ns,
+ });
+ }
+ cpuMeasurements.push({name, unit: measurements?.unit, values});
}
}
return new FlamegraphChartModel(
Rect.From(flamegraph.configSpace),
- measures.length > 0 ? measures : [],
+ cpuMeasurements.length > 0 ? cpuMeasurements : [],
flamegraphTheme.COLORS.CPU_CHART_COLORS
);
}, [profileGroup.measurements, flamegraph.configSpace, flamegraphTheme, hasCPUChart]);
@@ -442,17 +453,39 @@ function Flamegraph(): ReactElement {
const memory_footprint = profileGroup.measurements?.memory_footprint;
if (memory_footprint) {
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ for (let i = 0; i < memory_footprint.values.length; i++) {
+ const value = memory_footprint.values[i];
+ values.push({
+ value: value.value,
+ elapsed: value.elapsed_since_start_ns,
+ });
+ }
+
measures.push({
- ...memory_footprint!,
+ unit: memory_footprint.unit,
name: 'Heap Usage',
+ values,
});
}
const native_memory_footprint = profileGroup.measurements?.memory_native_footprint;
if (native_memory_footprint) {
+ const values: ProfileSeriesMeasurement['values'] = [];
+
+ for (let i = 0; i < native_memory_footprint.values.length; i++) {
+ const value = native_memory_footprint.values[i];
+ values.push({
+ value: value.value,
+ elapsed: value.elapsed_since_start_ns,
+ });
+ }
+
measures.push({
- ...native_memory_footprint!,
+ unit: native_memory_footprint.unit,
name: 'Native Heap Usage',
+ values,
});
}
diff --git a/static/app/components/profiling/flamegraph/flamegraphChart.tsx b/static/app/components/profiling/flamegraph/flamegraphChart.tsx
index 14830a1b1bf621..39ff7a42f19182 100644
--- a/static/app/components/profiling/flamegraph/flamegraphChart.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphChart.tsx
@@ -16,10 +16,9 @@ import {
getPhysicalSpacePositionFromOffset,
transformMatrixBetweenRect,
} from 'sentry/utils/profiling/gl/utils';
-import type {Profile} from 'sentry/utils/profiling/profile/profile';
import {FlamegraphChartRenderer} from 'sentry/utils/profiling/renderers/chartRenderer';
import type {Rect} from 'sentry/utils/profiling/speedscope';
-import {formatTo} from 'sentry/utils/profiling/units/units';
+import {formatTo, type ProfilingFormatterUnit} from 'sentry/utils/profiling/units/units';
import {useCanvasScroll} from './interactions/useCanvasScroll';
import {useCanvasZoomOrScroll} from './interactions/useCanvasZoomOrScroll';
@@ -38,7 +37,7 @@ interface FlamegraphChartProps {
chartCanvas: FlamegraphCanvas | null;
chartCanvasRef: HTMLCanvasElement | null;
chartView: CanvasView | null;
- configViewUnit: Profile['unit'];
+ configViewUnit: ProfilingFormatterUnit;
noMeasurementMessage: string | undefined;
setChartCanvasRef: (ref: HTMLCanvasElement | null) => void;
status: RequestState['type'];
@@ -310,6 +309,7 @@ export function FlamegraphChart({
chartView={chartView}
chartRenderer={chartRenderer}
canvasBounds={canvasBounds}
+ configViewUnit={configViewUnit}
/>
) : null}
{/* transaction loads after profile, so we want to show loading even if it's in initial state */}
diff --git a/static/app/components/profiling/flamegraph/flamegraphChartTooltip.tsx b/static/app/components/profiling/flamegraph/flamegraphChartTooltip.tsx
index 585b2b9f4f00bd..b7a86cbc2036b1 100644
--- a/static/app/components/profiling/flamegraph/flamegraphChartTooltip.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphChartTooltip.tsx
@@ -9,6 +9,7 @@ import type {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
import type {FlamegraphChart} from 'sentry/utils/profiling/flamegraphChart';
import type {FlamegraphChartRenderer} from 'sentry/utils/profiling/renderers/chartRenderer';
import type {Rect} from 'sentry/utils/profiling/speedscope';
+import {formatTo, type ProfilingFormatterUnit} from 'sentry/utils/profiling/units/units';
import {
FlamegraphTooltipColorIndicator,
@@ -23,6 +24,7 @@ export interface FlamegraphChartTooltipProps {
chartRenderer: FlamegraphChartRenderer;
chartView: CanvasView;
configSpaceCursor: vec2;
+ configViewUnit: ProfilingFormatterUnit;
}
export function FlamegraphChartTooltip({
@@ -32,11 +34,14 @@ export function FlamegraphChartTooltip({
chart,
chartRenderer,
chartView,
-}: // chartRenderer,
-FlamegraphChartTooltipProps) {
+ configViewUnit,
+}: FlamegraphChartTooltipProps) {
const series = useMemo(() => {
- return chartRenderer.findHoveredSeries(configSpaceCursor, 6e7);
- }, [chartRenderer, configSpaceCursor]);
+ return chartRenderer.findHoveredSeries(
+ configSpaceCursor,
+ formatTo(100, 'milliseconds', configViewUnit)
+ );
+ }, [chartRenderer, configSpaceCursor, configViewUnit]);
return series.length > 0 ? (
void;
onCopyFunctionSource: () => void;
onHighlightAllOccurrencesClick: () => void;
- profileGroup: ProfileGroup | null;
+ profileGroup: ProfileGroup | ContinuousProfileGroup | null;
disableCallOrderSort?: boolean;
disableColorCoding?: boolean;
}
diff --git a/static/app/components/profiling/flamegraph/flamegraphDrawer/flamegraphDrawer.tsx b/static/app/components/profiling/flamegraph/flamegraphDrawer/flamegraphDrawer.tsx
index 192dc5209e6c91..c98baeb1ba221e 100644
--- a/static/app/components/profiling/flamegraph/flamegraphDrawer/flamegraphDrawer.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphDrawer/flamegraphDrawer.tsx
@@ -18,7 +18,10 @@ import type {FlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/flam
import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
import {useDispatchFlamegraphState} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphState';
import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {
+ ContinuousProfileGroup,
+ ProfileGroup,
+} from 'sentry/utils/profiling/profile/importProfile';
import {invertCallTree} from 'sentry/utils/profiling/profile/utils';
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import useOrganization from 'sentry/utils/useOrganization';
@@ -34,7 +37,7 @@ interface FlamegraphDrawerProps {
flamegraph: Flamegraph;
formatDuration: Flamegraph['formatter'];
getFrameColor: (frame: FlamegraphFrame) => string;
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
profileTransaction: ReturnType | null;
referenceNode: FlamegraphFrame;
rootNodes: FlamegraphFrame[];
diff --git a/static/app/components/profiling/flamegraph/flamegraphDrawer/profileDetails.tsx b/static/app/components/profiling/flamegraph/flamegraphDrawer/profileDetails.tsx
index aef4a5d633151c..2f21c53c553f78 100644
--- a/static/app/components/profiling/flamegraph/flamegraphDrawer/profileDetails.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphDrawer/profileDetails.tsx
@@ -11,13 +11,17 @@ import Link from 'sentry/components/links/link';
import Version from 'sentry/components/version';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
-import type {Organization, Project} from 'sentry/types';
import type {EventTransaction} from 'sentry/types/event';
import {DeviceContextKey} from 'sentry/types/event';
+import type {Organization} from 'sentry/types/organization';
+import type {Project} from 'sentry/types/project';
import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
import type {FlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphPreferences';
import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {
+ ContinuousProfileGroup,
+ ProfileGroup,
+} from 'sentry/utils/profiling/profile/importProfile';
import {makeFormatter} from 'sentry/utils/profiling/units/units';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
@@ -33,7 +37,7 @@ import {ProfilingDetailsFrameTabs, ProfilingDetailsListItem} from './flamegraphD
function renderValue(
key: string,
value: number | string | undefined,
- profileGroup?: ProfileGroup
+ profileGroup?: ProfileGroup | ContinuousProfileGroup
) {
if (key === 'threads' && value === undefined) {
return profileGroup?.profiles.length;
@@ -49,7 +53,7 @@ function renderValue(
}
interface ProfileDetailsProps {
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
projectId: string;
transaction: EventTransaction | null;
}
@@ -184,7 +188,7 @@ function TransactionDeviceDetails({
profileGroup,
transaction,
}: {
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
transaction: EventTransaction;
}) {
const deviceDetails = useMemo(() => {
@@ -253,7 +257,7 @@ function TransactionEventDetails({
transaction,
}: {
organization: Organization;
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
project: Project | undefined;
transaction: EventTransaction;
}) {
@@ -348,7 +352,11 @@ function TransactionEventDetails({
);
}
-function ProfileEnvironmentDetails({profileGroup}: {profileGroup: ProfileGroup}) {
+function ProfileEnvironmentDetails({
+ profileGroup,
+}: {
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
+}) {
return (
{Object.entries(ENVIRONMENT_DETAILS_KEY).map(([label, key]) => {
@@ -371,7 +379,7 @@ function ProfileEventDetails({
transaction,
}: {
organization: Organization;
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
project: Project | undefined;
transaction: EventTransaction | null;
}) {
diff --git a/static/app/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector.tsx b/static/app/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector.tsx
index 1761668e0e31e5..d2a616aabb0c88 100644
--- a/static/app/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphToolbar/flamegraphThreadSelector.tsx
@@ -8,13 +8,16 @@ import {t, tn} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {defined} from 'sentry/utils';
import type {FlamegraphState} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContext';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {
+ ContinuousProfileGroup,
+ ProfileGroup,
+} from 'sentry/utils/profiling/profile/importProfile';
import type {Profile} from 'sentry/utils/profiling/profile/profile';
import {makeFormatter} from 'sentry/utils/profiling/units/units';
export interface FlamegraphThreadSelectorProps {
onThreadIdChange: (threadId: Profile['threadId']) => void;
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
threadId: FlamegraphState['profiles']['threadId'];
}
diff --git a/static/app/components/profiling/flamegraph/flamegraphZoomView.tsx b/static/app/components/profiling/flamegraph/flamegraphZoomView.tsx
index 46ae04d5476aa1..ba9c8faa9ff842 100644
--- a/static/app/components/profiling/flamegraph/flamegraphZoomView.tsx
+++ b/static/app/components/profiling/flamegraph/flamegraphZoomView.tsx
@@ -28,7 +28,10 @@ import {
} from 'sentry/utils/profiling/gl/utils';
import {useContextMenu} from 'sentry/utils/profiling/hooks/useContextMenu';
import {useInternalFlamegraphDebugMode} from 'sentry/utils/profiling/hooks/useInternalFlamegraphDebugMode';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {
+ ContinuousProfileGroup,
+ ProfileGroup,
+} from 'sentry/utils/profiling/profile/importProfile';
import type {FlamegraphRenderer} from 'sentry/utils/profiling/renderers/flamegraphRenderer';
import {FlamegraphTextRenderer} from 'sentry/utils/profiling/renderers/flamegraphTextRenderer';
import {GridRenderer} from 'sentry/utils/profiling/renderers/gridRenderer';
@@ -80,7 +83,7 @@ interface FlamegraphZoomViewProps {
flamegraphOverlayCanvasRef: HTMLCanvasElement | null;
flamegraphRenderer: FlamegraphRenderer | null;
flamegraphView: CanvasView | null;
- profileGroup: ProfileGroup;
+ profileGroup: ProfileGroup | ContinuousProfileGroup;
setFlamegraphCanvasRef: React.Dispatch>;
setFlamegraphOverlayCanvasRef: React.Dispatch<
React.SetStateAction
diff --git a/static/app/types/profiling.d.ts b/static/app/types/profiling.d.ts
index c5e94c2d84cc8d..dbd174dffa4ee7 100644
--- a/static/app/types/profiling.d.ts
+++ b/static/app/types/profiling.d.ts
@@ -12,11 +12,21 @@ declare namespace Profiling {
value: number;
};
+ type ContinuousMeasurementValue = {
+ timestamp: number;
+ value: number;
+ };
+
type Measurement = {
unit: string;
values: MeasurementValue[];
};
+ type ContinuousMeasurement = {
+ unit: string;
+ values: ContinuousMeasurementValue[];
+ };
+
type Measurements = {
cpu_usage?: Measurement;
memory_footprint?: Measurement;
@@ -26,6 +36,15 @@ declare namespace Profiling {
[key: string]: Measurement;
};
+ type ContinuousMeasurements = {
+ cpu_usage?: ContinuousMeasurement;
+ memory_footprint?: ContinuousMeasurement;
+ frozen_frame_renders?: ContinuousMeasurement;
+ screen_frame_rates?: ContinuousMeasurement;
+ slow_frame_renders?: ContinuousMeasurement;
+ [key: string]: ContinuousMeasurement;
+ };
+
type SentrySampledProfileSample = {
stack_id: number;
thread_id: string;
@@ -117,7 +136,7 @@ declare namespace Profiling {
images: Image[];
};
platform: string;
- measurements?: Measurements;
+ measurements?: ContinuousMeasurements;
profile: ContinuousProfile;
}
diff --git a/static/app/utils/profiling/flamegraphChart.tsx b/static/app/utils/profiling/flamegraphChart.tsx
index fe818e5ed6f6ea..29531dbc36b759 100644
--- a/static/app/utils/profiling/flamegraphChart.tsx
+++ b/static/app/utils/profiling/flamegraphChart.tsx
@@ -17,8 +17,10 @@ interface Series {
type: 'line' | 'area';
}
-export interface ProfileSeriesMeasurement extends Profiling.Measurement {
+export interface ProfileSeriesMeasurement {
name: string;
+ unit: string;
+ values: {elapsed: number; value: number}[];
}
function computeLabelPrecision(min: number, max: number): number {
@@ -35,6 +37,7 @@ function computeLabelPrecision(min: number, max: number): number {
}
interface ChartOptions {
+ timelineUnit?: ProfilingFormatterUnit;
type?: 'line' | 'area';
}
@@ -64,7 +67,7 @@ export class FlamegraphChart {
options: ChartOptions = {}
) {
this.series = new Array();
- this.timelineFormatter = makeTimelineFormatter('nanoseconds');
+ this.timelineFormatter = makeTimelineFormatter(options.timelineUnit ?? 'nanoseconds');
if (!measurements || !measurements.length) {
this.formatter = makeFormatter('percent');
@@ -115,14 +118,14 @@ export class FlamegraphChart {
}
// Track and update X domain max and min
- if (m.elapsed_since_start_ns > this.domains.x[1]) {
- this.domains.x[1] = m.elapsed_since_start_ns;
+ if (m.elapsed > this.domains.x[1]) {
+ this.domains.x[1] = m.elapsed;
}
- if (m.elapsed_since_start_ns < this.domains.x[0]) {
- this.domains.x[1] = m.elapsed_since_start_ns;
+ if (m.elapsed < this.domains.x[0]) {
+ this.domains.x[1] = m.elapsed;
}
- this.series[j].points[i] = {x: m.elapsed_since_start_ns, y: m.value};
+ this.series[j].points[i] = {x: m.elapsed, y: m.value};
}
}
diff --git a/static/app/utils/profiling/hooks/useDifferentialFlamegraphModel.tsx b/static/app/utils/profiling/hooks/useDifferentialFlamegraphModel.tsx
index c7a3d86ce00079..af26d95b1eb2a9 100644
--- a/static/app/utils/profiling/hooks/useDifferentialFlamegraphModel.tsx
+++ b/static/app/utils/profiling/hooks/useDifferentialFlamegraphModel.tsx
@@ -53,7 +53,12 @@ export function useDifferentialFlamegraphModel(
return null;
}
- return importProfile(props.after.data, '', 'flamegraph', props.frameFilter);
+ return importProfile(
+ props.after.data,
+ '',
+ 'flamegraph',
+ props.frameFilter
+ ) as ProfileGroup;
}, [props.after.data, props.frameFilter]);
const afterFlamegraph = useMemo(() => {
diff --git a/static/app/utils/profiling/profile/eventedProfile.tsx b/static/app/utils/profiling/profile/eventedProfile.tsx
index d20f57f8153a38..407cbbbac5e9f0 100644
--- a/static/app/utils/profiling/profile/eventedProfile.tsx
+++ b/static/app/utils/profiling/profile/eventedProfile.tsx
@@ -1,7 +1,7 @@
import {lastOfArray} from 'sentry/utils/array/lastOfArray';
import {CallTreeNode} from 'sentry/utils/profiling/callTreeNode';
import type {Frame} from 'sentry/utils/profiling/frame';
-import {formatTo} from 'sentry/utils/profiling/units/units';
+import {assertValidProfilingUnit, formatTo} from 'sentry/utils/profiling/units/units';
import {Profile} from './profile';
import type {createFrameIndex} from './utils';
@@ -21,6 +21,7 @@ export class EventedProfile extends Profile {
frameFilter?: (frame: Frame) => boolean;
}
): EventedProfile {
+ assertValidProfilingUnit(eventedProfile.unit);
const profile = new EventedProfile({
duration: eventedProfile.endValue - eventedProfile.startValue,
startedAt: eventedProfile.startValue,
diff --git a/static/app/utils/profiling/profile/importProfile.tsx b/static/app/utils/profiling/profile/importProfile.tsx
index 239e20c749a820..5bb7639205332d 100644
--- a/static/app/utils/profiling/profile/importProfile.tsx
+++ b/static/app/utils/profiling/profile/importProfile.tsx
@@ -37,12 +37,25 @@ export interface ImportOptions {
export interface ProfileGroup {
activeProfileIndex: number;
- measurements: Partial;
+ measurements: Partial;
metadata: Partial;
name: string;
profiles: Profile[];
traceID: string;
transactionID: string | null;
+ type: 'transaction';
+ images?: Image[];
+}
+
+export interface ContinuousProfileGroup {
+ activeProfileIndex: number;
+ measurements: Partial;
+ metadata: Partial;
+ name: string;
+ profiles: Profile[];
+ traceID: string;
+ transactionID: string | null;
+ type: 'continuous';
images?: Image[];
}
@@ -51,7 +64,7 @@ export function importProfile(
traceID: string,
type: 'flamegraph' | 'flamechart',
frameFilter?: (frame: Frame) => boolean
-): ProfileGroup {
+): ProfileGroup | ContinuousProfileGroup {
return Sentry.withScope(scope => {
const span = Sentry.startInactiveSpan({
op: 'import',
@@ -102,6 +115,7 @@ function importJSSelfProfile(
const profile = importSingleProfile(input, frameIndex, options);
return {
+ type: 'transaction',
traceID,
name: traceID,
transactionID: null,
@@ -175,11 +189,12 @@ function importSentrySampledProfile(
}
return {
+ type: 'transaction',
transactionID: input.transaction.id,
traceID: input.transaction.trace_id,
name: input.transaction.name,
activeProfileIndex,
- measurements: input.measurements,
+ measurements: input.measurements ?? {},
metadata: {
deviceLocale: input.device.locale,
deviceManufacturer: input.device.manufacturer,
@@ -218,6 +233,7 @@ export function importSchema(
);
return {
+ type: 'transaction',
traceID,
transactionID: input.metadata.transactionID ?? null,
name: input.metadata?.transactionName ?? traceID,
@@ -237,7 +253,7 @@ export function importSentryContinuousProfileChunk(
input: Readonly,
traceID: string,
options: ImportOptions
-): ProfileGroup {
+): ContinuousProfileGroup {
const frameIndex = createContinuousProfileFrameIndex(
input.profile.frames,
input.platform
@@ -246,10 +262,11 @@ export function importSentryContinuousProfileChunk(
return {
traceID,
name: '',
+ type: 'continuous',
transactionID: null,
activeProfileIndex: 0,
profiles: [importSingleProfile(input.profile, frameIndex, options)],
- measurements: {},
+ measurements: input.measurements ?? {},
metadata: {
platform: input.platform,
},
@@ -266,9 +283,9 @@ function importSingleProfile(
| ReturnType
| ReturnType
| ReturnType,
- {span, type, continuous, frameFilter, profileIds}: ImportOptions
+ {span, type, frameFilter, profileIds}: ImportOptions
): Profile {
- if (continuous && isSentryContinuousProfile(profile)) {
+ if (isSentryContinuousProfile(profile)) {
// In some cases, the SDK may return spans as undefined and we dont want to throw there.
if (!span) {
return ContinuousProfile.FromProfile(profile, frameIndex);
diff --git a/static/app/utils/profiling/profile/profile.spec.tsx b/static/app/utils/profiling/profile/profile.spec.tsx
index 658e7c2a8ab9fa..1a92cb0103976d 100644
--- a/static/app/utils/profiling/profile/profile.spec.tsx
+++ b/static/app/utils/profiling/profile/profile.spec.tsx
@@ -78,7 +78,7 @@ describe('Profile', () => {
startedAt: 0,
endedAt: 1000,
name: 'profile',
- unit: 'ms',
+ unit: 'millisecond',
threadId: 0,
type: 'flamechart',
});
@@ -114,7 +114,7 @@ describe('Profile', () => {
startedAt: 0,
endedAt: 1000,
name: 'profile',
- unit: 'ms',
+ unit: 'millisecond',
threadId: 0,
type: 'flamechart',
});
@@ -156,7 +156,7 @@ describe('Profile', () => {
startedAt: 0,
endedAt: 1000,
name: 'profile',
- unit: 'ms',
+ unit: 'millisecond',
threadId: 0,
type: 'flamechart',
});
@@ -197,7 +197,7 @@ describe('Profile', () => {
startedAt: 0,
endedAt: 1000,
name: 'profile',
- unit: 'ms',
+ unit: 'millisecond',
threadId: 0,
type: 'flamechart',
});
diff --git a/static/app/utils/profiling/profile/profile.tsx b/static/app/utils/profiling/profile/profile.tsx
index 75af90ac5930bb..ce85365f92ff0e 100644
--- a/static/app/utils/profiling/profile/profile.tsx
+++ b/static/app/utils/profiling/profile/profile.tsx
@@ -1,3 +1,5 @@
+import type {ProfilingFormatterUnit} from 'sentry/utils/profiling/units/units';
+
import {CallTreeNode} from '../callTreeNode';
import {Frame} from '../frame';
@@ -21,7 +23,7 @@ export class Profile {
type: string;
// Unit in which the timings are reported in
- unit = 'microseconds';
+ unit: ProfilingFormatterUnit = 'microseconds';
// Name of the profile
name = 'Unknown';
@@ -62,7 +64,7 @@ export class Profile {
startedAt: number;
threadId: number;
type: 'flamechart' | 'flamegraph' | 'empty';
- unit: string;
+ unit: ProfilingFormatterUnit;
timestamp?: number;
}) {
this.threadId = threadId;
diff --git a/static/app/utils/profiling/profile/sampledProfile.tsx b/static/app/utils/profiling/profile/sampledProfile.tsx
index 7bbf19b99c23df..cb03cc6aabd8e9 100644
--- a/static/app/utils/profiling/profile/sampledProfile.tsx
+++ b/static/app/utils/profiling/profile/sampledProfile.tsx
@@ -1,5 +1,7 @@
import {CallTreeNode} from 'sentry/utils/profiling/callTreeNode';
+import {assertValidProfilingUnit} from '../units/units';
+
import {Frame} from './../frame';
import {Profile} from './profile';
import type {createFrameIndex} from './utils';
@@ -60,6 +62,7 @@ export class SampledProfile extends Profile {
profileIds?: Readonly;
}
): Profile {
+ assertValidProfilingUnit(sampledProfile.unit);
const profile = new SampledProfile({
duration: sampledProfile.endValue - sampledProfile.startValue,
startedAt: sampledProfile.startValue,
@@ -88,7 +91,7 @@ export class SampledProfile extends Profile {
);
}
- function resolveFrame(index) {
+ function resolveFrame(index: number) {
const resolvedFrame = frameIndex[index];
if (!resolvedFrame) {
throw new Error(`Could not resolve frame ${index} in frame index`);
diff --git a/static/app/views/profiling/profileGroupProvider.tsx b/static/app/views/profiling/profileGroupProvider.tsx
index 93ee12fb388a0c..fdc715af0de26a 100644
--- a/static/app/views/profiling/profileGroupProvider.tsx
+++ b/static/app/views/profiling/profileGroupProvider.tsx
@@ -2,18 +2,48 @@ import {createContext, useContext, useMemo} from 'react';
import * as Sentry from '@sentry/react';
import type {Frame} from 'sentry/utils/profiling/frame';
-import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
+import type {
+ ContinuousProfileGroup,
+ ProfileGroup,
+} from 'sentry/utils/profiling/profile/importProfile';
import {importProfile} from 'sentry/utils/profiling/profile/importProfile';
-type ProfileGroupContextValue = ProfileGroup;
-
+type ProfileGroupContextValue = ContinuousProfileGroup | ProfileGroup;
const ProfileGroupContext = createContext(null);
-export function useProfileGroup() {
+function assertContinuousProfileGroup(
+ input: ProfileGroupContextValue | null
+): asserts input is ContinuousProfileGroup {
+ if (input && input.type !== 'continuous') {
+ throw new Error('ProfileGroup is not of continuous profile type.');
+ }
+}
+
+function assertTransactionProfileGroup(
+ input: ProfileGroupContextValue | null
+): asserts input is ProfileGroup {
+ if (input && input.type !== 'transaction') {
+ throw new Error('ProfileGroup is not of transaction profile type.');
+ }
+}
+
+export function useProfileGroup(): ProfileGroup {
const context = useContext(ProfileGroupContext);
if (!context) {
throw new Error('useProfileGroup was called outside of ProfileGroupProvider');
}
+ assertTransactionProfileGroup(context);
+ return context;
+}
+
+export function useContinuousProfileGroup(): ContinuousProfileGroup {
+ const context = useContext(ProfileGroupContext);
+ if (!context) {
+ throw new Error(
+ 'useContinuousProfileGroup was called outside of ProfileGroupProvider'
+ );
+ }
+ assertContinuousProfileGroup(context);
return context;
}
@@ -25,6 +55,7 @@ export const LOADING_PROFILE_GROUP: Readonly = {
measurements: {},
traceID: '',
profiles: [],
+ type: 'transaction',
};
interface ProfileGroupProviderProps {