Browse Source

feat(performance): Update queues module samples panel to support displaying producer samples. (#70637)

Updates samples panel to support both consumer and producer samples and
fixes some minor ui bugs in samples panel.
edwardgou-sentry 10 months ago
parent
commit
c0e4900cfe

+ 1 - 0
static/app/views/performance/http/charts/durationChart.tsx

@@ -61,6 +61,7 @@ export function DurationChart({
         error={error}
         chartColors={[AVG_COLOR]}
         type={ChartType.LINE}
+        aggregateOutputFormat="duration"
       />
     </ChartPanel>
   );

+ 2 - 2
static/app/views/performance/queues/destinationSummary/destinationSummaryPage.tsx

@@ -25,7 +25,7 @@ import Onboarding from 'sentry/views/performance/onboarding';
 import {LatencyChart} from 'sentry/views/performance/queues/charts/latencyChart';
 import {ThroughputChart} from 'sentry/views/performance/queues/charts/throughputChart';
 import {TransactionsTable} from 'sentry/views/performance/queues/destinationSummary/transactionsTable';
-import {MessageConsumerSamplesPanel} from 'sentry/views/performance/queues/messageConsumerSamplesPanel';
+import {MessageSamplesPanel} from 'sentry/views/performance/queues/messageSamplesPanel';
 import {useQueuesMetricsQuery} from 'sentry/views/performance/queues/queries/useQueuesMetricsQuery';
 import {
   DESTINATION_TITLE,
@@ -155,7 +155,7 @@ function DestinationSummaryPage() {
           </ModuleLayout.Layout>
         </Layout.Main>
       </Layout.Body>
-      <MessageConsumerSamplesPanel />
+      <MessageSamplesPanel />
     </Fragment>
   );
 }

+ 3 - 2
static/app/views/performance/queues/destinationSummary/transactionsTable.tsx

@@ -148,7 +148,7 @@ function renderBodyCell(
   }
 
   if (key === 'transaction') {
-    return <TransactionCell transaction={row[key]} />;
+    return <TransactionCell transaction={row[key]} op={op} />;
   }
 
   if (!meta?.fields) {
@@ -179,12 +179,13 @@ function renderBodyCell(
   });
 }
 
-function TransactionCell({transaction}: {transaction: string}) {
+function TransactionCell({transaction, op}: {op: string; transaction: string}) {
   const organization = useOrganization();
   const {query} = useLocation();
   const queryString = {
     ...query,
     transaction,
+    'span.op': op,
   };
   return (
     <NoOverflow>

+ 91 - 5
static/app/views/performance/queues/messageConsumerSamplesPanel.spec.tsx → static/app/views/performance/queues/messageSamplesPanel.spec.tsx

@@ -5,13 +5,13 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
-import {MessageConsumerSamplesPanel} from 'sentry/views/performance/queues/messageConsumerSamplesPanel';
+import {MessageSamplesPanel} from 'sentry/views/performance/queues/messageSamplesPanel';
 
 jest.mock('sentry/utils/useLocation');
 jest.mock('sentry/utils/usePageFilters');
 jest.mock('sentry/utils/useOrganization');
 
-describe('messageConsumerSamplesPanel', () => {
+describe('messageSamplesPanel', () => {
   const organization = OrganizationFixture();
 
   let eventsRequestMock, eventsStatsRequestMock, samplesRequestMock;
@@ -85,8 +85,93 @@ describe('messageConsumerSamplesPanel', () => {
     jest.resetAllMocks();
   });
 
-  it('renders', async () => {
-    render(<MessageConsumerSamplesPanel />);
+  it('renders consumer panel', async () => {
+    jest.mocked(useLocation).mockReturnValue({
+      pathname: '',
+      search: '',
+      query: {
+        transaction: 'sentry.tasks.store.save_event',
+        destination: 'event-queue',
+        'span.op': 'queue.process',
+      },
+      hash: '',
+      state: undefined,
+      action: 'PUSH',
+      key: '',
+    });
+    render(<MessageSamplesPanel />);
+    await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
+    expect(eventsStatsRequestMock).toHaveBeenCalled();
+    expect(eventsRequestMock).toHaveBeenCalledWith(
+      `/organizations/${organization.slug}/events/`,
+      expect.objectContaining({
+        method: 'GET',
+        query: expect.objectContaining({
+          dataset: 'spansMetrics',
+          environment: [],
+          field: [
+            'count()',
+            'count_op(queue.publish)',
+            'count_op(queue.process)',
+            'sum(span.duration)',
+            'avg(span.duration)',
+            'avg_if(span.duration,span.op,queue.publish)',
+            'avg_if(span.duration,span.op,queue.process)',
+            'avg(messaging.message.receive.latency)',
+          ],
+          per_page: 10,
+          project: [],
+          query:
+            'span.op:[queue.process,queue.publish] messaging.destination.name:event-queue transaction:sentry.tasks.store.save_event',
+          statsPeriod: '10d',
+        }),
+      })
+    );
+    expect(samplesRequestMock).toHaveBeenCalledWith(
+      `/api/0/organizations/${organization.slug}/spans-samples/`,
+      expect.objectContaining({
+        query: expect.objectContaining({
+          additionalFields: [
+            'trace',
+            'transaction.id',
+            'span.description',
+            'measurements.messaging.message.body.size',
+            'measurements.messaging.message.receive.latency',
+            'measurements.messaging.message.retry.count',
+            'messaging.message.id',
+            'trace.status',
+            'span.duration',
+          ],
+          firstBound: 2666.6666666666665,
+          lowerBound: 0,
+          project: [],
+          query:
+            'span.op:queue.process transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
+          referrer: undefined,
+          secondBound: 5333.333333333333,
+          statsPeriod: '10d',
+          upperBound: 8000,
+        }),
+      })
+    );
+    expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
+  });
+
+  it('renders producer panel', async () => {
+    jest.mocked(useLocation).mockReturnValue({
+      pathname: '',
+      search: '',
+      query: {
+        transaction: 'sentry.tasks.store.save_event',
+        destination: 'event-queue',
+        'span.op': 'queue.publish',
+      },
+      hash: '',
+      state: undefined,
+      action: 'PUSH',
+      key: '',
+    });
+    render(<MessageSamplesPanel />);
     await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
     expect(eventsStatsRequestMock).toHaveBeenCalled();
     expect(eventsRequestMock).toHaveBeenCalledWith(
@@ -124,6 +209,7 @@ describe('messageConsumerSamplesPanel', () => {
             'span.description',
             'measurements.messaging.message.body.size',
             'measurements.messaging.message.receive.latency',
+            'measurements.messaging.message.retry.count',
             'messaging.message.id',
             'trace.status',
             'span.duration',
@@ -132,7 +218,7 @@ describe('messageConsumerSamplesPanel', () => {
           lowerBound: 0,
           project: [],
           query:
-            'span.op:queue.process OR span.op:queue.publish transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
+            'span.op:queue.publish transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
           referrer: undefined,
           secondBound: 5333.333333333333,
           statsPeriod: '10d',

+ 115 - 42
static/app/views/performance/queues/messageConsumerSamplesPanel.tsx → static/app/views/performance/queues/messageSamplesPanel.tsx

@@ -1,3 +1,4 @@
+import {Fragment} from 'react';
 import styled from '@emotion/styled';
 import * as qs from 'query-string';
 
@@ -23,22 +24,26 @@ import {MetricReadout} from 'sentry/views/performance/metricReadout';
 import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
 import {MessageSpanSamplesTable} from 'sentry/views/performance/queues/messageSpanSamplesTable';
 import {useQueuesMetricsQuery} from 'sentry/views/performance/queues/queries/useQueuesMetricsQuery';
+import {
+  CONSUMER_QUERY_FILTER,
+  MessageActorType,
+  PRODUCER_QUERY_FILTER,
+} from 'sentry/views/performance/queues/settings';
+import {Subtitle} from 'sentry/views/profiling/landing/styles';
 import {computeAxisMax} from 'sentry/views/starfish/components/chart';
 import DetailPanel from 'sentry/views/starfish/components/detailPanel';
 import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useDiscoverSeries';
-import {SpanIndexedField} from 'sentry/views/starfish/types';
+import {SpanIndexedField, type SpanMetricsResponse} from 'sentry/views/starfish/types';
 import {useSampleScatterPlotSeries} from 'sentry/views/starfish/views/spanSummaryPage/sampleList/durationChart/useSampleScatterPlotSeries';
 
-// We're defining our own query filter here, apart from settings.ts because the spans endpoint doesn't accept IN operations
-const DEFAULT_QUERY_FILTER = 'span.op:queue.process OR span.op:queue.publish';
-
-export function MessageConsumerSamplesPanel() {
+export function MessageSamplesPanel() {
   const router = useRouter();
   const query = useLocationQuery({
     fields: {
       project: decodeScalar,
       destination: decodeScalar,
       transaction: decodeScalar,
+      'span.op': decodeScalar,
     },
   });
   const {projects} = useProjects();
@@ -59,7 +64,16 @@ export function MessageConsumerSamplesPanel() {
 
   const isPanelOpen = Boolean(detailKey);
 
-  const search = new MutableSearch(DEFAULT_QUERY_FILTER);
+  const messageActorType =
+    query['span.op'] === 'queue.publish'
+      ? MessageActorType.PRODUCER
+      : MessageActorType.CONSUMER;
+  const queryFilter =
+    messageActorType === MessageActorType.PRODUCER
+      ? PRODUCER_QUERY_FILTER
+      : CONSUMER_QUERY_FILTER;
+
+  const search = new MutableSearch(queryFilter);
   search.addFilterValue('transaction', query.transaction);
   search.addFilterValue('messaging.destination.name', query.destination);
 
@@ -103,6 +117,7 @@ export function MessageConsumerSamplesPanel() {
       SpanIndexedField.SPAN_DESCRIPTION,
       SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE,
       SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY,
+      SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT,
       SpanIndexedField.MESSAGING_MESSAGE_ID,
       SpanIndexedField.TRACE_STATUS,
       SpanIndexedField.SPAN_DURATION,
@@ -139,7 +154,7 @@ export function MessageConsumerSamplesPanel() {
         <ModuleLayout.Layout>
           <ModuleLayout.Full>
             <HeaderContainer>
-              {project && (
+              {project ? (
                 <SpanSummaryProjectAvatar
                   project={project}
                   direction="left"
@@ -147,8 +162,15 @@ export function MessageConsumerSamplesPanel() {
                   hasTooltip
                   tooltip={project.slug}
                 />
+              ) : (
+                <div />
               )}
               <TitleContainer>
+                <Subtitle>
+                  {messageActorType === MessageActorType.PRODUCER
+                    ? t('Producer')
+                    : t('Consumer')}
+                </Subtitle>
                 <Title>
                   <Link
                     to={normalizeUrl(
@@ -168,36 +190,19 @@ export function MessageConsumerSamplesPanel() {
           </ModuleLayout.Full>
 
           <ModuleLayout.Full>
-            <MetricsRibbon>
-              <MetricReadout
-                align="left"
-                title={t('Processed')}
-                value={transactionMetrics?.[0]?.['count()']}
-                unit={'count'}
-                isLoading={aretransactionMetricsFetching}
-              />
-              <MetricReadout
-                align="left"
-                title={t('Error Rate')}
-                value={undefined}
-                unit={'percentage'}
-                isLoading={aretransactionMetricsFetching}
-              />
-              <MetricReadout
-                title={t('Avg Time In Queue')}
-                value={transactionMetrics[0]?.['avg(messaging.message.receive.latency)']}
-                unit={DurationUnit.MILLISECOND}
-                isLoading={false}
-              />
-              <MetricReadout
-                title={t('Avg Processing Latency')}
-                value={
-                  transactionMetrics[0]?.['avg_if(span.duration,span.op,queue.process)']
-                }
-                unit={DurationUnit.MILLISECOND}
-                isLoading={false}
-              />
-            </MetricsRibbon>
+            <MetricsRibbonContainer>
+              {messageActorType === MessageActorType.PRODUCER ? (
+                <ProducerMetricsRibbon
+                  metrics={transactionMetrics}
+                  isLoading={aretransactionMetricsFetching}
+                />
+              ) : (
+                <ConsumerMetricsRibbon
+                  metrics={transactionMetrics}
+                  isLoading={aretransactionMetricsFetching}
+                />
+              )}
+            </MetricsRibbonContainer>
           </ModuleLayout.Full>
           <ModuleLayout.Full>
             <DurationChart
@@ -235,14 +240,16 @@ export function MessageConsumerSamplesPanel() {
               // Samples endpoint doesn't provide meta data, so we need to provide it here
               meta={{
                 fields: {
-                  'span.duration': 'duration',
-                  'measurements.messaging.message.body.size': 'size',
+                  [SpanIndexedField.SPAN_DURATION]: 'duration',
+                  [SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE]: 'size',
+                  [SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT]: 'number',
                 },
                 units: {
-                  'span.duration': DurationUnit.MILLISECOND,
-                  'measurements.messaging.message.body.size': SizeUnit.BYTE,
+                  [SpanIndexedField.SPAN_DURATION]: DurationUnit.MILLISECOND,
+                  [SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE]: SizeUnit.BYTE,
                 },
               }}
+              type={messageActorType}
             />
           </ModuleLayout.Full>
 
@@ -257,6 +264,72 @@ export function MessageConsumerSamplesPanel() {
   );
 }
 
+function ProducerMetricsRibbon({
+  metrics,
+  isLoading,
+}: {
+  isLoading: boolean;
+  metrics: Partial<SpanMetricsResponse>[];
+}) {
+  return (
+    <Fragment>
+      <MetricReadout
+        align="left"
+        title={t('Published')}
+        value={metrics?.[0]?.['count()']}
+        unit={'count'}
+        isLoading={isLoading}
+      />
+      <MetricReadout
+        align="left"
+        title={t('Error Rate')}
+        value={undefined}
+        unit={'percentage'}
+        isLoading={isLoading}
+      />
+    </Fragment>
+  );
+}
+
+function ConsumerMetricsRibbon({
+  metrics,
+  isLoading,
+}: {
+  isLoading: boolean;
+  metrics: Partial<SpanMetricsResponse>[];
+}) {
+  return (
+    <Fragment>
+      <MetricReadout
+        align="left"
+        title={t('Processed')}
+        value={metrics?.[0]?.['count()']}
+        unit={'count'}
+        isLoading={isLoading}
+      />
+      <MetricReadout
+        align="left"
+        title={t('Error Rate')}
+        value={undefined}
+        unit={'percentage'}
+        isLoading={isLoading}
+      />
+      <MetricReadout
+        title={t('Avg Time In Queue')}
+        value={metrics[0]?.['avg(messaging.message.receive.latency)']}
+        unit={DurationUnit.MILLISECOND}
+        isLoading={false}
+      />
+      <MetricReadout
+        title={t('Avg Processing Latency')}
+        value={metrics[0]?.['avg_if(span.duration,span.op,queue.process)']}
+        unit={DurationUnit.MILLISECOND}
+        isLoading={false}
+      />
+    </Fragment>
+  );
+}
+
 const SAMPLE_HOVER_DEBOUNCE = 10;
 
 const SpanSummaryProjectAvatar = styled(ProjectAvatar)`
@@ -285,7 +358,7 @@ const Title = styled('h4')`
   margin-bottom: 0;
 `;
 
-const MetricsRibbon = styled('div')`
+const MetricsRibbonContainer = styled('div')`
   display: flex;
   flex-wrap: wrap;
   gap: ${space(4)};

+ 23 - 2
static/app/views/performance/queues/messageSpanSamplesTable.spec.tsx

@@ -4,6 +4,7 @@ import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 import useOrganization from 'sentry/utils/useOrganization';
 import {MessageSpanSamplesTable} from 'sentry/views/performance/queues/messageSpanSamplesTable';
+import {MessageActorType} from 'sentry/views/performance/queues/settings';
 
 jest.mock('sentry/utils/useOrganization');
 
@@ -12,14 +13,34 @@ describe('messageSpanSamplesTable', () => {
   jest.mocked(useOrganization).mockReturnValue(organization);
 
   beforeEach(() => {});
-  it('renders', () => {
-    render(<MessageSpanSamplesTable data={[]} isLoading={false} />);
+  it('renders consumer samples table', () => {
+    render(
+      <MessageSpanSamplesTable
+        data={[]}
+        isLoading={false}
+        type={MessageActorType.CONSUMER}
+      />
+    );
     expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
     expect(screen.getByRole('columnheader', {name: 'Span ID'})).toBeInTheDocument();
     expect(screen.getByRole('columnheader', {name: 'Message ID'})).toBeInTheDocument();
     expect(
       screen.getByRole('columnheader', {name: 'Processing Time'})
     ).toBeInTheDocument();
+    expect(screen.getByRole('columnheader', {name: 'Retries'})).toBeInTheDocument();
+    expect(screen.getByRole('columnheader', {name: 'Status'})).toBeInTheDocument();
+  });
+  it('renders producer samples table', () => {
+    render(
+      <MessageSpanSamplesTable
+        data={[]}
+        isLoading={false}
+        type={MessageActorType.PRODUCER}
+      />
+    );
+    expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
+    expect(screen.getByRole('columnheader', {name: 'Span ID'})).toBeInTheDocument();
+    expect(screen.getByRole('columnheader', {name: 'Message ID'})).toBeInTheDocument();
     expect(screen.getByRole('columnheader', {name: 'Message Size'})).toBeInTheDocument();
     expect(screen.getByRole('columnheader', {name: 'Status'})).toBeInTheDocument();
   });

+ 32 - 2
static/app/views/performance/queues/messageSpanSamplesTable.tsx

@@ -12,6 +12,7 @@ import type {EventsMetaType} from 'sentry/utils/discover/eventView';
 import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
+import {MessageActorType} from 'sentry/views/performance/queues/settings';
 import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
 import {SpanIdCell} from 'sentry/views/starfish/components/tableCells/spanIdCell';
 import type {IndexedResponse} from 'sentry/views/starfish/types';
@@ -27,6 +28,7 @@ type DataRowKeys =
   | SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE
   | SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY
   | SpanIndexedField.MESSAGING_MESSAGE_ID
+  | SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT
   | SpanIndexedField.TRACE_STATUS
   | SpanIndexedField.SPAN_DURATION;
 
@@ -34,6 +36,7 @@ type ColumnKeys =
   | SpanIndexedField.ID
   | SpanIndexedField.MESSAGING_MESSAGE_ID
   | SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE
+  | SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT
   | SpanIndexedField.TRACE_STATUS
   | SpanIndexedField.SPAN_DURATION;
 
@@ -41,7 +44,7 @@ type DataRow = Pick<IndexedResponse, DataRowKeys>;
 
 type Column = GridColumnHeader<ColumnKeys>;
 
-const COLUMN_ORDER: Column[] = [
+const CONSUMER_COLUMN_ORDER: Column[] = [
   {
     key: SpanIndexedField.ID,
     name: t('Span ID'),
@@ -57,6 +60,29 @@ const COLUMN_ORDER: Column[] = [
     name: t('Processing Time'),
     width: COL_WIDTH_UNDEFINED,
   },
+  {
+    key: SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT,
+    name: t('Retries'),
+    width: COL_WIDTH_UNDEFINED,
+  },
+  {
+    key: SpanIndexedField.TRACE_STATUS,
+    name: t('Status'),
+    width: COL_WIDTH_UNDEFINED,
+  },
+];
+
+const PRODUCER_COLUMN_ORDER: Column[] = [
+  {
+    key: SpanIndexedField.ID,
+    name: t('Span ID'),
+    width: 150,
+  },
+  {
+    key: SpanIndexedField.MESSAGING_MESSAGE_ID,
+    name: t('Message ID'),
+    width: COL_WIDTH_UNDEFINED,
+  },
   {
     key: SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE,
     name: t('Message Size'),
@@ -72,6 +98,7 @@ const COLUMN_ORDER: Column[] = [
 interface Props {
   data: DataRow[];
   isLoading: boolean;
+  type: MessageActorType;
   error?: Error | null;
   highlightedSpanId?: string;
   meta?: EventsMetaType;
@@ -87,6 +114,7 @@ export function MessageSpanSamplesTable({
   onSampleMouseOver,
   onSampleMouseOut,
   highlightedSpanId,
+  type,
 }: Props) {
   const location = useLocation();
   const organization = useOrganization();
@@ -97,7 +125,9 @@ export function MessageSpanSamplesTable({
       isLoading={isLoading}
       error={error}
       data={data}
-      columnOrder={COLUMN_ORDER}
+      columnOrder={
+        type === MessageActorType.PRODUCER ? PRODUCER_COLUMN_ORDER : CONSUMER_COLUMN_ORDER
+      }
       columnSortBy={[]}
       grid={{
         renderHeadCell: col =>

+ 7 - 0
static/app/views/performance/queues/settings.ts

@@ -14,3 +14,10 @@ export const releaseLevelAsBadgeProps = {
 };
 
 export const DEFAULT_QUERY_FILTER = 'span.op:[queue.process,queue.publish]';
+export const CONSUMER_QUERY_FILTER = 'span.op:queue.process';
+export const PRODUCER_QUERY_FILTER = 'span.op:queue.publish';
+
+export enum MessageActorType {
+  PRODUCER = 'producer',
+  CONSUMER = 'consumer',
+}

+ 1 - 0
static/app/views/starfish/components/tableCells/renderHeadCell.tsx

@@ -66,6 +66,7 @@ const NUMERIC_FIELDS = new Set([
   SpanIndexedField.SPAN_DURATION,
   SpanIndexedField.CACHE_ITEM_SIZE,
   SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE,
+  SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT,
 ]);
 
 export const renderHeadCell = ({column, location, sort, sortParameterName}: Options) => {

+ 2 - 0
static/app/views/starfish/types.tsx

@@ -196,6 +196,7 @@ export enum SpanIndexedField {
   MESSAGING_MESSAGE_ID = 'messaging.message.id',
   MESSAGING_MESSAGE_BODY_SIZE = 'measurements.messaging.message.body.size',
   MESSAGING_MESSAGE_RECEIVE_LATENCY = 'measurements.messaging.message.receive.latency',
+  MESSAGING_MESSAGE_RETRY_COUNT = 'measurements.messaging.message.retry.count',
 }
 
 export type IndexedResponse = {
@@ -236,6 +237,7 @@ export type IndexedResponse = {
   [SpanIndexedField.MESSAGING_MESSAGE_ID]: string;
   [SpanIndexedField.MESSAGING_MESSAGE_BODY_SIZE]: number;
   [SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY]: number;
+  [SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT]: number;
 };
 
 export type IndexedProperty = keyof IndexedResponse;