Просмотр исходного кода

feat(perf): Add basic percentile selector to Query Details duration chart (#59274)

First steps. This is a tentative UI, to explore how this performs with
real data.

- Add a percentile selector to "Duration" chart on Query Details page
- Fetch `spm()` and `span.self_time` separately. Fetching durations
takes _much_ longer, no point delaying showing the Throughput graph.
This also prevents the Throughput chart from re-fetching when the
duration aggregate changes
- Don't fetch `http_error_rate()`, we don't need it on this page. That
was a copy error
George Gritsouk 1 год назад
Родитель
Сommit
c66c4eb710

+ 54 - 25
static/app/views/performance/database/databaseSpanSummaryPage.tsx

@@ -13,10 +13,16 @@ import {space} from 'sentry/styles/space';
 import type {Sort} from 'sentry/utils/discover/fields';
 import {RateUnits} from 'sentry/utils/discover/fields';
 import {formatRate} from 'sentry/utils/formatters';
+import {decodeScalar} from 'sentry/utils/queryString';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
+import {DurationAggregateSelector} from 'sentry/views/performance/database/durationAggregateSelector';
 import {ModulePageProviders} from 'sentry/views/performance/database/modulePageProviders';
+import {
+  AVAILABLE_DURATION_AGGREGATE_OPTIONS,
+  DEFAULT_DURATION_AGGREGATE,
+} from 'sentry/views/performance/database/settings';
 import {AVG_COLOR, THROUGHPUT_COLOR} from 'sentry/views/starfish/colours';
 import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
 import ChartPanel from 'sentry/views/starfish/components/chartPanel';
@@ -29,10 +35,7 @@ import {
 import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useSpanMetricsSeries';
 import {SpanFunction, SpanMetricsField} from 'sentry/views/starfish/types';
 import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
-import {
-  getDurationChartTitle,
-  getThroughputChartTitle,
-} from 'sentry/views/starfish/views/spans/types';
+import {getThroughputChartTitle} from 'sentry/views/starfish/views/spans/types';
 import {useModuleSort} from 'sentry/views/starfish/views/spans/useModuleSort';
 import {Block, BlockContainer} from 'sentry/views/starfish/views/spanSummaryPage/block';
 import {SampleList} from 'sentry/views/starfish/views/spanSummaryPage/sampleList';
@@ -45,6 +48,7 @@ type Query = {
   transaction: string;
   transactionMethod: string;
   [QueryParameterNames.SPANS_SORT]: string;
+  aggregate?: string;
 };
 
 type Props = {
@@ -55,6 +59,22 @@ function SpanSummaryPage({params}: Props) {
   const organization = useOrganization();
   const location = useLocation<Query>();
 
+  const arePercentilesEnabled = organization.features?.includes(
+    'performance-database-view-percentiles'
+  );
+
+  let durationAggregate = arePercentilesEnabled
+    ? decodeScalar(location.query.aggregate, DEFAULT_DURATION_AGGREGATE)
+    : DEFAULT_DURATION_AGGREGATE;
+
+  if (
+    !AVAILABLE_DURATION_AGGREGATE_OPTIONS.map(option => option.value).includes(
+      durationAggregate
+    )
+  ) {
+    durationAggregate = DEFAULT_DURATION_AGGREGATE;
+  }
+
   const {groupId} = params;
   const {transaction, transactionMethod, endpoint, endpointMethod} = location.query;
 
@@ -95,22 +115,21 @@ function SpanSummaryPage({params}: Props) {
     [SpanMetricsField.SPAN_GROUP]: string;
   };
 
-  const {isLoading: areSpanMetricsSeriesLoading, data: spanMetricsSeriesData} =
-    useSpanMetricsSeries(
-      groupId,
-      queryFilter,
-      [`avg(${SpanMetricsField.SPAN_SELF_TIME})`, 'spm()', 'http_error_count()'],
-      'api.starfish.span-summary-page-metrics-chart'
-    );
-
-  useSynchronizeCharts([!areSpanMetricsSeriesLoading]);
-
-  const spanMetricsThroughputSeries = {
-    seriesName: span?.[SpanMetricsField.SPAN_OP]?.startsWith('db')
-      ? 'Queries'
-      : 'Requests',
-    data: spanMetricsSeriesData?.['spm()'].data,
-  };
+  const {isLoading: isThroughputDataLoading, data: throughputData} = useSpanMetricsSeries(
+    groupId,
+    queryFilter,
+    ['spm()'],
+    'api.starfish.span-summary-page-metrics-chart'
+  );
+
+  const {isLoading: isDurationDataLoading, data: durationData} = useSpanMetricsSeries(
+    groupId,
+    queryFilter,
+    [`${durationAggregate}(${SpanMetricsField.SPAN_SELF_TIME})`],
+    'api.starfish.span-summary-page-metrics-chart'
+  );
+
+  useSynchronizeCharts([!isThroughputDataLoading && !isDurationDataLoading]);
 
   return (
     <ModulePageProviders
@@ -175,8 +194,8 @@ function SpanSummaryPage({params}: Props) {
               >
                 <Chart
                   height={CHART_HEIGHT}
-                  data={[spanMetricsThroughputSeries]}
-                  loading={areSpanMetricsSeriesLoading}
+                  data={[throughputData['spm()']]}
+                  loading={isThroughputDataLoading}
                   utc={false}
                   chartColors={[THROUGHPUT_COLOR]}
                   isLineChart
@@ -191,13 +210,23 @@ function SpanSummaryPage({params}: Props) {
             </Block>
 
             <Block>
-              <ChartPanel title={getDurationChartTitle(span?.[SpanMetricsField.SPAN_OP])}>
+              <ChartPanel
+                title={
+                  arePercentilesEnabled ? (
+                    <DurationAggregateSelector aggregate={durationAggregate} />
+                  ) : (
+                    t('Average Duration')
+                  )
+                }
+              >
                 <Chart
                   height={CHART_HEIGHT}
                   data={[
-                    spanMetricsSeriesData?.[`avg(${SpanMetricsField.SPAN_SELF_TIME})`],
+                    durationData[
+                      `${durationAggregate}(${SpanMetricsField.SPAN_SELF_TIME})`
+                    ],
                   ]}
-                  loading={areSpanMetricsSeriesLoading}
+                  loading={isDurationDataLoading}
                   utc={false}
                   chartColors={[AVG_COLOR]}
                   isLineChart

+ 49 - 0
static/app/views/performance/database/durationAggregateSelector.tsx

@@ -0,0 +1,49 @@
+import {browserHistory} from 'react-router';
+import styled from '@emotion/styled';
+
+import {CompactSelect} from 'sentry/components/compactSelect';
+import {space} from 'sentry/styles/space';
+import {useLocation} from 'sentry/utils/useLocation';
+import {AVAILABLE_DURATION_AGGREGATE_OPTIONS} from 'sentry/views/performance/database/settings';
+
+interface Props {
+  aggregate: string;
+}
+
+export function DurationAggregateSelector({aggregate}: Props) {
+  const location = useLocation();
+
+  const handleDurationFunctionChange = option => {
+    browserHistory.push({
+      ...location,
+      query: {
+        ...location.query,
+        aggregate: option.value,
+      },
+    });
+  };
+
+  return (
+    <StyledCompactSelect
+      size="md"
+      options={AVAILABLE_DURATION_AGGREGATE_OPTIONS}
+      value={aggregate}
+      onChange={handleDurationFunctionChange}
+      triggerProps={{borderless: true, size: 'zero'}}
+    />
+  );
+}
+
+// TODO: Talk to UI folks about making this a built-in dropdown size if we end
+// up using this element
+const StyledCompactSelect = styled(CompactSelect)`
+  text-align: left;
+  font-weight: normal;
+  margin: -${space(0.5)} -${space(1)} -${space(0.25)};
+  min-width: 0;
+
+  button {
+    padding: ${space(0.5)} ${space(1)};
+    font-size: ${p => p.theme.fontSizeLarge};
+  }
+`;

+ 21 - 0
static/app/views/performance/database/settings.ts

@@ -1,3 +1,5 @@
+import {t} from 'sentry/locale';
+
 export const MIN_SDK_VERSION_BY_PLATFORM: {[platform: string]: string} = {
   'sentry.python': '1.29.2',
   'sentry.javascript': '7.63.0',
@@ -9,3 +11,22 @@ export const MIN_SDK_VERSION_BY_PLATFORM: {[platform: string]: string} = {
   'sentry.symfony': '4.11.0',
   'sentry.android': '6.30.0',
 };
+
+export const DEFAULT_DURATION_AGGREGATE = 'avg';
+
+export const AVAILABLE_DURATION_AGGREGATES = ['avg', 'p50', 'p75', 'p95', 'p99']; // TODO: `max` is not a supported aggregate for this dataset on the backend. Why?
+
+const DURATION_AGGREGATE_LABELS = {
+  avg: t('Average Duration'),
+  p50: t('Duration p50'),
+  p75: t('Duration p75'),
+  p95: t('Duration p95'),
+  p99: t('Duration p99'),
+};
+
+export const AVAILABLE_DURATION_AGGREGATE_OPTIONS = AVAILABLE_DURATION_AGGREGATES.map(
+  aggregate => ({
+    value: aggregate,
+    label: DURATION_AGGREGATE_LABELS[aggregate],
+  })
+);

+ 5 - 2
static/app/views/starfish/components/chartPanel.tsx

@@ -1,7 +1,6 @@
 import styled from '@emotion/styled';
 
 import Panel from 'sentry/components/panels/panel';
-import PanelBody from 'sentry/components/panels/panelBody';
 import {space} from 'sentry/styles/space';
 import {Subtitle} from 'sentry/views/performance/landing/widgets/widgets/singleFieldAreaWidget';
 
@@ -15,7 +14,7 @@ type Props = {
 export default function ChartPanel({title, children, button, subtitle}: Props) {
   return (
     <Panel>
-      <PanelBody withPadding>
+      <PanelBody>
         {title && (
           <Header>
             {title && <ChartLabel>{title}</ChartLabel>}
@@ -41,6 +40,10 @@ const ChartLabel = styled('div')`
   ${p => p.theme.text.cardTitle}
 `;
 
+const PanelBody = styled('div')`
+  padding: ${space(2)};
+`;
+
 const Header = styled('div')`
   padding: 0 ${space(1)} 0 0;
   width: 100%;

+ 0 - 1
static/app/views/starfish/queries/useSpanMetricsSeries.tsx

@@ -36,7 +36,6 @@ export const useSpanMetricsSeries = (
   const enabled =
     Boolean(group) && Object.values(queryFilters).every(value => Boolean(value));
 
-  // TODO: Add referrer
   const result = useSpansQuery<SpanMetrics[]>({
     eventView,
     initialData: [],