Skip to content

Commit 115ea08

Browse files
feat(uptime): Represent uptime requests in trace waterfall (#97609)
This is still a WIP, but we would like to get something going, it looks like this <img alt="clipboard.png" width="1942" src="https://i.imgur.com/IZ1e4G0.png" />
1 parent 6e25427 commit 115ea08

File tree

13 files changed

+361
-14
lines changed

13 files changed

+361
-14
lines changed

static/app/utils/fields/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2877,7 +2877,14 @@ const FEEDBACK_FIELD_DEFINITIONS: Record<FeedbackFieldKey, FieldDefinition> = {
28772877

28782878
export const getFieldDefinition = (
28792879
key: string,
2880-
type: 'event' | 'replay' | 'replay_click' | 'feedback' | 'span' | 'log' = 'event',
2880+
type:
2881+
| 'event'
2882+
| 'replay'
2883+
| 'replay_click'
2884+
| 'feedback'
2885+
| 'span'
2886+
| 'log'
2887+
| 'uptime' = 'event',
28812888
kind?: FieldKind
28822889
): FieldDefinition | null => {
28832890
switch (type) {

static/app/views/alerts/rules/uptime/uptimeChecksGrid.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {t, tct} from 'sentry/locale';
1313
import {space} from 'sentry/styles/space';
1414
import {getShortEventId} from 'sentry/utils/events';
1515
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
16+
import useOrganization from 'sentry/utils/useOrganization';
1617
import type {UptimeCheck, UptimeRule} from 'sentry/views/alerts/rules/uptime/types';
1718
import {useSpans} from 'sentry/views/insights/common/queries/useDiscover';
1819
import {
@@ -94,6 +95,7 @@ function CheckInBodyCell({
9495
uptimeRule: UptimeRule;
9596
}) {
9697
const theme = useTheme();
98+
const organization = useOrganization();
9799

98100
const {
99101
timestamp,
@@ -109,6 +111,10 @@ function CheckInBodyCell({
109111
return <Cell />;
110112
}
111113

114+
const alwaysShowTraceLink = organization.features.includes(
115+
'uptime-eap-uptime-results-query'
116+
);
117+
112118
switch (column.key) {
113119
case 'timestamp': {
114120
return (
@@ -182,8 +188,10 @@ function CheckInBodyCell({
182188

183189
return (
184190
<TraceCell>
185-
{spanCount ? (
186-
<Link to={`/performance/trace/${traceId}/`}>{getShortEventId(traceId)}</Link>
191+
{alwaysShowTraceLink || spanCount ? (
192+
<Link to={`/performance/trace/${traceId}/?includeUptime=1`}>
193+
{getShortEventId(traceId)}
194+
</Link>
187195
) : (
188196
getShortEventId(traceId)
189197
)}

static/app/views/explore/components/traceItemSearchQueryBuilder.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ const getFunctionTags = (supportedAggregates?: AggregationKey[]) => {
3838
}, {} as TagCollection);
3939
};
4040

41-
const typeMap: Record<TraceItemDataset, 'span' | 'log'> = {
41+
const typeMap: Record<TraceItemDataset, 'span' | 'log' | 'uptime'> = {
4242
[TraceItemDataset.SPANS]: 'span',
4343
[TraceItemDataset.LOGS]: 'log',
44+
[TraceItemDataset.UPTIME_RESULTS]: 'uptime',
4445
};
4546

4647
function getTraceItemFieldDefinitionFunction(

static/app/views/explore/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {Project} from 'sentry/types/project';
33
export enum TraceItemDataset {
44
LOGS = 'logs',
55
SPANS = 'spans',
6+
UPTIME_RESULTS = 'uptime_results',
67
}
78

89
export interface UseTraceItemAttributeBaseProps {

static/app/views/explore/utils.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -684,14 +684,16 @@ export function getSavedQueryTraceItemUrl({
684684

685685
const TRACE_ITEM_TO_URL_FUNCTION: Record<
686686
TraceItemDataset,
687-
({
688-
savedQuery,
689-
organization,
690-
}: {
691-
organization: Organization;
692-
savedQuery: SavedQuery;
693-
}) => string
687+
| (({
688+
savedQuery,
689+
organization,
690+
}: {
691+
organization: Organization;
692+
savedQuery: SavedQuery;
693+
}) => string)
694+
| undefined
694695
> = {
695696
[TraceItemDataset.LOGS]: getLogsUrlFromSavedQueryUrl,
696697
[TraceItemDataset.SPANS]: getExploreUrlFromSavedQueryUrl,
698+
[TraceItemDataset.UPTIME_RESULTS]: undefined,
697699
};

static/app/views/performance/newTraceDetails/traceApi/useTrace.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export function getTraceQueryParams(
112112
demo,
113113
limit,
114114
timestamp: timestamp?.toString(),
115+
include_uptime: query.includeUptime,
115116
};
116117

117118
for (const key in queryParams) {

static/app/views/performance/newTraceDetails/traceApi/useTraceMeta.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,11 @@ export function useTraceMeta(replayTraces: ReplayTrace[]): TraceMetaQueryResults
154154

155155
const normalizedParams = useMemo(() => {
156156
const query = qs.parse(location.search);
157-
return normalizeDateTimeParams(query, {
157+
const params = normalizeDateTimeParams(query, {
158158
allowAbsolutePageDatetime: true,
159159
});
160+
161+
return {...params, include_uptime: query.includeUptime} as Record<string, any>;
160162
}, []);
161163

162164
// demo has the format ${projectSlug}:${eventId}

static/app/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function Attributes({
8787
}: {
8888
attributes: TraceItemResponseAttribute[];
8989
location: Location;
90-
node: TraceTreeNode<TraceTree.EAPSpan>;
90+
node: TraceTreeNode<TraceTree.EAPSpan | TraceTree.UptimeCheck>;
9191
organization: Organization;
9292
project: Project | undefined;
9393
theme: Theme;
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {type Theme, useTheme} from '@emotion/react';
2+
import type {Location} from 'history';
3+
4+
import LoadingError from 'sentry/components/loadingError';
5+
import LoadingIndicator from 'sentry/components/loadingIndicator';
6+
import {t} from 'sentry/locale';
7+
import type {Organization} from 'sentry/types/organization';
8+
import type {Project} from 'sentry/types/project';
9+
import {useLocation} from 'sentry/utils/useLocation';
10+
import useProjects from 'sentry/utils/useProjects';
11+
import {prettifyAttributeName} from 'sentry/views/explore/components/traceItemAttributes/utils';
12+
import {
13+
type TraceItemDetailsResponse,
14+
useTraceItemDetails,
15+
} from 'sentry/views/explore/hooks/useTraceItemDetails';
16+
import {TraceItemDataset} from 'sentry/views/explore/types';
17+
import {Attributes} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/attributes';
18+
import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles';
19+
import type {TraceTreeNodeDetailsProps} from 'sentry/views/performance/newTraceDetails/traceDrawer/tabs/traceTreeNodeDetails';
20+
import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
21+
import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode';
22+
23+
import {RequestWaterfall, type RequestWaterfallData} from './requestWaterfall';
24+
25+
function UptimeNodeDetailsHeader({
26+
node,
27+
organization,
28+
onTabScrollToNode,
29+
hideNodeActions,
30+
}: {
31+
node: TraceTreeNode<TraceTree.UptimeCheck>;
32+
onTabScrollToNode: (node: TraceTreeNode<any>) => void;
33+
organization: Organization;
34+
hideNodeActions?: boolean;
35+
}) {
36+
const itemId = node.value.event_id;
37+
return (
38+
<TraceDrawerComponents.HeaderContainer>
39+
<TraceDrawerComponents.Title>
40+
<TraceDrawerComponents.LegacyTitleText>
41+
<TraceDrawerComponents.TitleText>
42+
{t('Uptime Check Request')}
43+
</TraceDrawerComponents.TitleText>
44+
<TraceDrawerComponents.SubtitleWithCopyButton
45+
subTitle={`Check ID: ${itemId}`}
46+
clipboardText={itemId}
47+
/>
48+
</TraceDrawerComponents.LegacyTitleText>
49+
</TraceDrawerComponents.Title>
50+
{!hideNodeActions && (
51+
<TraceDrawerComponents.NodeActions
52+
node={node}
53+
organization={organization}
54+
onTabScrollToNode={onTabScrollToNode}
55+
/>
56+
)}
57+
</TraceDrawerComponents.HeaderContainer>
58+
);
59+
}
60+
61+
export function UptimeNodeDetails(
62+
props: TraceTreeNodeDetailsProps<TraceTreeNode<TraceTree.UptimeCheck>>
63+
) {
64+
const {node} = props;
65+
const location = useLocation();
66+
const theme = useTheme();
67+
const {projects} = useProjects();
68+
69+
const project = projects.find(
70+
proj => proj.slug === (node.value.project_slug ?? node.event?.projectSlug)
71+
);
72+
73+
return (
74+
<UptimeSpanNodeDetails
75+
{...props}
76+
node={node}
77+
project={project}
78+
location={location}
79+
theme={theme}
80+
/>
81+
);
82+
}
83+
84+
type UptimeSpanNodeDetailsProps = TraceTreeNodeDetailsProps<
85+
TraceTreeNode<TraceTree.UptimeCheck>
86+
> & {
87+
location: Location;
88+
project: Project | undefined;
89+
theme: Theme;
90+
};
91+
92+
function UptimeSpanNodeDetails(props: UptimeSpanNodeDetailsProps) {
93+
const {node, traceId} = props;
94+
const {
95+
data: traceItemData,
96+
isPending: isTraceItemPending,
97+
isError: isTraceItemError,
98+
} = useTraceItemDetails({
99+
traceItemId: node.value.event_id,
100+
projectId: node.value.project_id.toString(),
101+
traceId: node.metadata.replayTraceSlug ?? traceId,
102+
traceItemType: TraceItemDataset.UPTIME_RESULTS,
103+
referrer: 'api.explore.log-item-details', // TODO: change to span details
104+
enabled: true,
105+
});
106+
107+
if (isTraceItemPending) {
108+
return <LoadingIndicator />;
109+
}
110+
111+
if (isTraceItemError) {
112+
return <LoadingError message={t('Failed to fetch span details')} />;
113+
}
114+
115+
return <UptimeSpanNodeDetailsContent {...props} traceItemData={traceItemData} />;
116+
}
117+
118+
type UptimeSpanNodeDetailsContentProps = UptimeSpanNodeDetailsProps & {
119+
traceItemData: TraceItemDetailsResponse;
120+
};
121+
122+
function UptimeSpanNodeDetailsContent({
123+
hideNodeActions,
124+
node,
125+
onTabScrollToNode,
126+
organization,
127+
project,
128+
theme,
129+
traceItemData,
130+
}: UptimeSpanNodeDetailsContentProps) {
131+
const location = useLocation();
132+
const attributes = traceItemData.attributes;
133+
134+
const attrs = attributes.reduce(
135+
(acc, attribute) => {
136+
// Some attribute keys include prefixes and metadata (e.g. "tags[ai.prompt_tokens.used,number]")
137+
// prettifyAttributeName normalizes those
138+
acc[prettifyAttributeName(attribute.name)] = attribute.value;
139+
return acc;
140+
},
141+
{} as Record<string, string | number | boolean>
142+
);
143+
144+
const waterfall: RequestWaterfallData = {
145+
dns: {
146+
durationUs: Number(attrs.dns_lookup_duration_us),
147+
startUs: Number(attrs.dns_lookup_start_us),
148+
},
149+
TcpConn: {
150+
durationUs: Number(attrs.tcp_connection_duration_us),
151+
startUs: Number(attrs.tcp_connection_start_us),
152+
},
153+
tlsHandshake: {
154+
durationUs: Number(attrs.tls_handshake_duration_us),
155+
startUs: Number(attrs.tls_handshake_start_us),
156+
},
157+
receiveResponse: {
158+
durationUs: Number(attrs.receive_response_duration_us),
159+
startUs: Number(attrs.receive_response_start_us),
160+
},
161+
sendRequest: {
162+
durationUs: Number(attrs.send_request_duration_us),
163+
startUs: Number(attrs.send_request_start_us),
164+
},
165+
firstByte: {
166+
durationUs: Number(attrs.time_to_first_byte_duration_us),
167+
startUs: Number(attrs.time_to_first_byte_start_us),
168+
},
169+
};
170+
171+
return (
172+
<TraceDrawerComponents.DetailContainer>
173+
<UptimeNodeDetailsHeader
174+
node={node}
175+
organization={organization}
176+
onTabScrollToNode={onTabScrollToNode}
177+
hideNodeActions={hideNodeActions}
178+
/>
179+
<TraceDrawerComponents.BodyContainer>
180+
<RequestWaterfall data={waterfall} />
181+
<Attributes
182+
node={node}
183+
attributes={attributes}
184+
theme={theme}
185+
location={location}
186+
organization={organization}
187+
project={project}
188+
/>
189+
</TraceDrawerComponents.BodyContainer>
190+
</TraceDrawerComponents.DetailContainer>
191+
);
192+
}

0 commit comments

Comments
 (0)