Skip to content

Commit 75806c4

Browse files
authored
ref(perf): Don't use 'mark.<vital>' for drawing web vitals in event details (#38023)
1 parent 01b3ff9 commit 75806c4

File tree

3 files changed

+67
-36
lines changed

3 files changed

+67
-36
lines changed

static/app/components/events/interfaces/spans/measurementsPanel.tsx

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ type Props = {
2222
generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
2323
};
2424

25+
type VitalLabel = {
26+
isPoorValue: boolean;
27+
vital: Vital;
28+
};
29+
2530
function MeasurementsPanel(props: Props) {
2631
const {event, generateBounds, dividerPosition} = props;
2732
const measurements = getMeasurements(event, generateBounds);
@@ -44,30 +49,26 @@ function MeasurementsPanel(props: Props) {
4449
return null;
4550
}
4651

47-
// Measurements are referred to by their full name `measurements.<name>`
48-
// here but are stored using their abbreviated name `<name>`. Make sure
49-
// to convert it appropriately.
50-
const vitals: Vital[] = Object.keys(verticalMark.marks).map(
51-
name => WEB_VITAL_DETAILS[`measurements.${name}`]
52-
);
52+
const vitalLabels: VitalLabel[] = Object.keys(verticalMark.marks).map(name => ({
53+
vital: WEB_VITAL_DETAILS[`measurements.${name}`],
54+
isPoorValue: verticalMark.marks[name].failedThreshold,
55+
}));
5356

54-
if (vitals.length > 1) {
57+
if (vitalLabels.length > 1) {
5558
return (
5659
<MultiLabelContainer
5760
key={String(timestamp)}
58-
failedThreshold={verticalMark.failedThreshold}
5961
left={toPercent(bounds.left || 0)}
60-
vitals={vitals}
62+
vitalLabels={vitalLabels}
6163
/>
6264
);
6365
}
6466

6567
return (
6668
<LabelContainer
6769
key={String(timestamp)}
68-
failedThreshold={verticalMark.failedThreshold}
6970
left={toPercent(bounds.left || 0)}
70-
vital={vitals[0]}
71+
vitalLabel={vitalLabels[0]}
7172
/>
7273
);
7374
})}
@@ -123,9 +124,8 @@ const Label = styled('div')<{
123124
export default MeasurementsPanel;
124125

125126
type LabelContainerProps = {
126-
failedThreshold: boolean;
127127
left: string;
128-
vital: Vital;
128+
vitalLabel: VitalLabel;
129129
};
130130

131131
type LabelContainerState = {
@@ -149,7 +149,7 @@ class LabelContainer extends Component<LabelContainerProps> {
149149
elementDOMRef = createRef<HTMLDivElement>();
150150

151151
render() {
152-
const {left, failedThreshold, vital} = this.props;
152+
const {left, vitalLabel} = this.props;
153153

154154
return (
155155
<StyledLabelContainer
@@ -158,18 +158,23 @@ class LabelContainer extends Component<LabelContainerProps> {
158158
left: `clamp(calc(0.5 * ${this.state.width}px), ${left}, calc(100% - 0.5 * ${this.state.width}px))`,
159159
}}
160160
>
161-
<Label failedThreshold={failedThreshold} isSingleLabel>
162-
<Tooltip title={vital.name} position="top" containerDisplayMode="inline-block">
163-
{vital.acronym}
161+
<Label failedThreshold={vitalLabel.isPoorValue} isSingleLabel>
162+
<Tooltip
163+
title={vitalLabel.vital.name}
164+
position="top"
165+
containerDisplayMode="inline-block"
166+
>
167+
{vitalLabel.vital.acronym}
164168
</Tooltip>
165169
</Label>
166170
</StyledLabelContainer>
167171
);
168172
}
169173
}
170174

171-
type MultiLabelContainerProps = Omit<LabelContainerProps, 'vital'> & {
172-
vitals: Vital[];
175+
type MultiLabelContainerProps = {
176+
left: string;
177+
vitalLabels: VitalLabel[];
173178
};
174179

175180
class MultiLabelContainer extends Component<MultiLabelContainerProps> {
@@ -190,7 +195,7 @@ class MultiLabelContainer extends Component<MultiLabelContainerProps> {
190195
elementDOMRef = createRef<HTMLDivElement>();
191196

192197
render() {
193-
const {left, failedThreshold, vitals} = this.props;
198+
const {left, vitalLabels} = this.props;
194199

195200
return (
196201
<StyledMultiLabelContainer
@@ -199,14 +204,14 @@ class MultiLabelContainer extends Component<MultiLabelContainerProps> {
199204
left: `clamp(calc(0.5 * ${this.state.width}px), ${left}, calc(100% - 0.5 * ${this.state.width}px))`,
200205
}}
201206
>
202-
{vitals.map(vital => (
203-
<Label failedThreshold={failedThreshold} key={`${vital.name}-label`}>
207+
{vitalLabels.map(label => (
208+
<Label failedThreshold={label.isPoorValue} key={`${label.vital.name}-label`}>
204209
<Tooltip
205-
title={vital.name}
210+
title={label.vital.name}
206211
position="top"
207212
containerDisplayMode="inline-block"
208213
>
209-
{vital.acronym}
214+
{label.vital.acronym}
210215
</Tooltip>
211216
</Label>
212217
))}

static/app/components/events/interfaces/spans/utils.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {Organization} from 'sentry/types';
1414
import {EntryType, EventTransaction} from 'sentry/types/event';
1515
import {assert} from 'sentry/types/utils';
1616
import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
17+
import {WebVital} from 'sentry/utils/fields';
1718
import {TraceError} from 'sentry/utils/performance/quickTrace/types';
1819
import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
1920
import {getPerformanceTransaction} from 'sentry/utils/performanceForSentry';
@@ -512,6 +513,7 @@ export const durationlessBrowserOps = ['mark', 'paint'];
512513

513514
type Measurements = {
514515
[name: string]: {
516+
failedThreshold: boolean;
515517
timestamp: number;
516518
value: number | undefined;
517519
};
@@ -541,26 +543,37 @@ export function getMeasurements(
541543
event: EventTransaction,
542544
generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType
543545
): Map<number, VerticalMark> {
544-
if (!event.measurements) {
546+
if (!event.measurements || !event.startTimestamp) {
545547
return new Map();
546548
}
547549

550+
const {startTimestamp} = event;
551+
552+
// Note: CLS and INP should not be included here, since they are not timeline-based measurements.
553+
const allowedVitals = new Set<string>([
554+
WebVital.FCP,
555+
WebVital.FP,
556+
WebVital.FID,
557+
WebVital.LCP,
558+
WebVital.TTFB,
559+
]);
560+
548561
const measurements = Object.keys(event.measurements)
549-
.filter(name => name.startsWith('mark.'))
562+
.filter(name => allowedVitals.has(`measurements.${name}`))
550563
.map(name => {
551-
const slug = name.slice('mark.'.length);
552-
const associatedMeasurement = event.measurements![slug];
564+
const associatedMeasurement = event.measurements![name];
553565
return {
554566
name,
555-
timestamp: event.measurements![name].value,
567+
// Time timestamp is in seconds, but the measurement value is given in ms so convert it here
568+
timestamp: startTimestamp + associatedMeasurement.value / 1000,
556569
value: associatedMeasurement ? associatedMeasurement.value : undefined,
557570
};
558571
});
559572

560573
const mergedMeasurements = new Map<number, VerticalMark>();
561574

562575
measurements.forEach(measurement => {
563-
const name = measurement.name.slice('mark.'.length);
576+
const name = measurement.name;
564577
const value = measurement.value;
565578

566579
const bounds = generateBounds({
@@ -584,11 +597,14 @@ export function getMeasurements(
584597
if (positionDelta <= MERGE_LABELS_THRESHOLD_PERCENT) {
585598
const verticalMark = mergedMeasurements.get(otherPos)!;
586599

600+
const {poorThreshold} = WEB_VITAL_DETAILS[`measurements.${name}`];
601+
587602
verticalMark.marks = {
588603
...verticalMark.marks,
589604
[name]: {
590605
value,
591606
timestamp: measurement.timestamp,
607+
failedThreshold: value ? value >= poorThreshold : false,
592608
},
593609
};
594610

@@ -601,15 +617,22 @@ export function getMeasurements(
601617
}
602618
}
603619

620+
const {poorThreshold} = WEB_VITAL_DETAILS[`measurements.${name}`];
621+
604622
const marks = {
605-
[name]: {value, timestamp: measurement.timestamp},
623+
[name]: {
624+
value,
625+
timestamp: measurement.timestamp,
626+
failedThreshold: value ? value >= poorThreshold : false,
627+
},
606628
};
607629

608630
mergedMeasurements.set(roundedPos, {
609631
marks,
610632
failedThreshold: hasFailedThreshold(marks),
611633
});
612634
});
635+
613636
return mergedMeasurements;
614637
}
615638

tests/js/spec/components/events/interfaces/spans/traceView.spec.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -734,9 +734,9 @@ describe('TraceView', () => {
734734
generateSampleSpan('browser', 'test5', 'f000000000000000', 'a000000000000000', event);
735735

736736
event.measurements = {
737-
'mark.fcp': {value: 1000},
738-
'mark.fp': {value: 1050},
739-
'mark.lcp': {value: 1100},
737+
fcp: {value: 1000},
738+
fp: {value: 1050},
739+
lcp: {value: 1100},
740740
};
741741

742742
const waterfallModel = new WaterfallModel(event);
@@ -758,9 +758,12 @@ describe('TraceView', () => {
758758
const event = generateSampleEvent();
759759
generateSampleSpan('browser', 'test1', 'b000000000000000', 'a000000000000000', event);
760760

761+
event.startTimestamp = 1;
762+
event.endTimestamp = 100;
763+
761764
event.measurements = {
762-
'mark.fcp': {value: 1000},
763-
'mark.lcp': {value: 200000000},
765+
fcp: {value: 858.3002090454102, unit: 'millisecond'},
766+
lcp: {value: 1000363.800048828125, unit: 'millisecond'},
764767
};
765768

766769
const waterfallModel = new WaterfallModel(event);

0 commit comments

Comments
 (0)