Browse Source

feat(traces): More improvements, use sdk names now (#70514)

### Summary
This uses sdk names as well as project to differentiate when it comes to
multi-service single project traces. This also adds some small UI
tweaks.
Kev 10 months ago
parent
commit
0b1e18c9da

+ 33 - 16
static/app/views/performance/traces/content.tsx

@@ -33,13 +33,14 @@ import {
   ProjectRenderer,
   SpanBreakdownSliceRenderer,
   SpanIdRenderer,
+  SpanTimeRenderer,
   TraceBreakdownContainer,
   TraceBreakdownRenderer,
   TraceIdRenderer,
   TraceIssuesRenderer,
 } from './fieldRenderers';
 import {TracesSearchBar} from './tracesSearchBar';
-import {normalizeTraces} from './utils';
+import {getSecondaryNameFromSpan, getStylingSliceName, normalizeTraces} from './utils';
 
 const DEFAULT_PER_PAGE = 20;
 
@@ -135,15 +136,17 @@ export function Content() {
             {t('Total Spans')}
           </StyledPanelHeader>
           <StyledPanelHeader align="right" lightText>
-            {t('Breakdown')}
+            {t('Timeline')}
           </StyledPanelHeader>
           <StyledPanelHeader align="right" lightText>
-            {t('Trace Duration')}
+            {t('Duration')}
+          </StyledPanelHeader>
+          <StyledPanelHeader align="right" lightText>
+            {t('Timestamp')}
           </StyledPanelHeader>
           <StyledPanelHeader align="right" lightText>
             {t('Issues')}
           </StyledPanelHeader>
-          <StyledPanelHeader align="right" lightText style={{padding: '5px'}} />
           {isLoading && (
             <StyledPanelItem span={7} overflow>
               <LoadingIndicator />
@@ -177,20 +180,22 @@ function TraceRow({trace}: {trace: TraceResult<Field>}) {
       }),
     [_setHighlightedSliceName]
   );
+
+  const onClickExpand = useCallback(() => setExpanded(e => !e), [setExpanded]);
+
   return (
     <Fragment>
-      <StyledPanelItem align="center" center>
+      <StyledPanelItem align="center" center onClick={onClickExpand}>
         <Button
           icon={<IconChevron size="xs" direction={expanded ? 'down' : 'right'} />}
           aria-label={t('Toggle trace details')}
           aria-expanded={expanded}
           size="zero"
           borderless
-          onClick={() => setExpanded(e => !e)}
         />
         <TraceIdRenderer traceId={trace.trace} timestamp={trace.spans[0].timestamp} />
       </StyledPanelItem>
-      <StyledPanelItem align="left" overflow>
+      <StyledPanelItem align="left" overflow onClick={onClickExpand}>
         <Description>
           {trace.project ? (
             <ProjectRenderer projectSlug={trace.project} hideName />
@@ -218,10 +223,12 @@ function TraceRow({trace}: {trace: TraceResult<Field>}) {
       <StyledPanelItem align="right">
         <PerformanceDuration milliseconds={trace.duration} abbreviation />
       </StyledPanelItem>
+      <StyledPanelItem align="right">
+        <SpanTimeRenderer timestamp={trace.start} tooltipShowSeconds />
+      </StyledPanelItem>
       <StyledPanelItem align="right">
         <TraceIssuesRenderer trace={trace} />
       </StyledPanelItem>
-      <StyledPanelItem style={{padding: '5px'}} />
       {expanded && (
         <SpanTable
           spans={trace.spans}
@@ -257,9 +264,8 @@ function SpanTable({
             {t('Span Duration')}
           </StyledPanelHeader>
           <StyledPanelHeader align="right" lightText>
-            {t('Issues')}
+            {t('Timestamp')}
           </StyledPanelHeader>
-
           {spans.map(span => (
             <SpanRow
               key={span.id}
@@ -308,25 +314,34 @@ function SpanRow({
         <TraceBreakdownContainer>
           <SpanBreakdownSliceRenderer
             sliceName={span.project}
+            sliceSecondaryName={getSecondaryNameFromSpan(span)}
             sliceStart={Math.ceil(span['precise.start_ts'] * 1000)}
             sliceEnd={Math.floor(span['precise.finish_ts'] * 1000)}
             trace={trace}
             theme={theme}
-            onMouseEnter={() => setHighlightedSliceName(span.project)}
+            onMouseEnter={() =>
+              setHighlightedSliceName(
+                getStylingSliceName(span.project, getSecondaryNameFromSpan(span)) ?? ''
+              )
+            }
           />
         </TraceBreakdownContainer>
       </StyledSpanPanelItem>
       <StyledSpanPanelItem align="right">
         <PerformanceDuration milliseconds={span['span.duration']} abbreviation />
       </StyledSpanPanelItem>
+
       <StyledSpanPanelItem align="right">
-        <EmptyValueContainer>{'\u2014'}</EmptyValueContainer>
+        <SpanTimeRenderer
+          timestamp={span['precise.start_ts'] * 1000}
+          tooltipShowSeconds
+        />
       </StyledSpanPanelItem>
     </Fragment>
   );
 }
 
-type SpanResult<F extends string> = Record<F, any>;
+export type SpanResult<F extends string> = Record<F, any>;
 
 export interface TraceResult<F extends string> {
   breakdowns: TraceBreakdownResult[];
@@ -344,6 +359,8 @@ export interface TraceResult<F extends string> {
 
 interface TraceBreakdownBase {
   end: number;
+  opCategory: string | null;
+  sdkName: string | null;
   start: number;
 }
 
@@ -423,13 +440,13 @@ const StyledPanel = styled(Panel)`
 const TracePanelContent = styled('div')`
   width: 100%;
   display: grid;
-  grid-template-columns: repeat(1, min-content) auto repeat(2, min-content) 120px 66px 10px;
+  grid-template-columns: repeat(1, min-content) auto repeat(2, min-content) 85px 85px 66px;
 `;
 
 const SpanPanelContent = styled('div')`
   width: 100%;
   display: grid;
-  grid-template-columns: repeat(1, min-content) auto repeat(1, min-content) 120px 66px;
+  grid-template-columns: repeat(1, min-content) auto repeat(1, min-content) 141px 85px;
 `;
 
 const StyledPanelHeader = styled(PanelHeader)<{align: 'left' | 'right'}>`
@@ -484,7 +501,7 @@ const BreakdownPanelItem = styled(StyledPanelItem)<{highlightedSliceName: string
   ${p =>
     p.highlightedSliceName
       ? `--highlightedSlice-${p.highlightedSliceName}-opacity: 1.0;
-         --highlightedSlice-${p.highlightedSliceName}-transform: translateY(-1px);
+         --highlightedSlice-${p.highlightedSliceName}-transform: translateY(-2px);
        `
       : null}
   ${p =>

+ 1 - 0
static/app/views/performance/traces/data.tsx

@@ -4,6 +4,7 @@ export const FIELDS = [
   'id',
   'timestamp',
   'span.op',
+  'sdk.name',
   'span.description',
   'span.duration',
   'span.self_time',

+ 58 - 23
static/app/views/performance/traces/fieldRenderers.tsx

@@ -4,11 +4,10 @@ import styled from '@emotion/styled';
 import {LinkButton} from 'sentry/components/button';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import Link from 'sentry/components/links/link';
-import LoadingIndicator from 'sentry/components/loadingIndicator';
-import {ROW_HEIGHT, ROW_PADDING} from 'sentry/components/performance/waterfall/constants';
 import {RowRectangle} from 'sentry/components/performance/waterfall/rowBar';
 import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
 import PerformanceDuration from 'sentry/components/performanceDuration';
+import TimeSince from 'sentry/components/timeSince';
 import {Tooltip} from 'sentry/components/tooltip';
 import {IconIssues} from 'sentry/icons';
 import {t} from 'sentry/locale';
@@ -27,6 +26,7 @@ import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transac
 
 import type {TraceResult} from './content';
 import type {Field} from './data';
+import {getStylingSliceName} from './utils';
 
 interface ProjectRendererProps {
   projectSlug: string;
@@ -55,8 +55,8 @@ export function ProjectRenderer({projectSlug, hideName}: ProjectRendererProps) {
 export const TraceBreakdownContainer = styled('div')`
   position: relative;
   display: flex;
-  min-width: 150px;
-  height: ${ROW_HEIGHT - 2 * ROW_PADDING}px;
+  min-width: 200px;
+  height: 15px;
   background-color: ${p => p.theme.gray100};
 `;
 
@@ -67,6 +67,7 @@ const RectangleTraceBreakdown = styled(RowRectangle)<{
   background-color: ${p => p.sliceColor};
   position: relative;
   width: 100%;
+  height: 15px;
   ${p => `
     opacity: var(--highlightedSlice-${p.sliceName ?? ''}-opacity, var(--defaultSlice-opacity, 1.0));
   `}
@@ -95,10 +96,15 @@ export function TraceBreakdownRenderer({
             sliceName={breakdown.project}
             sliceStart={breakdown.start}
             sliceEnd={breakdown.end}
+            sliceSecondaryName={breakdown.sdkName}
             trace={trace}
             theme={theme}
             onMouseEnter={() =>
-              breakdown.project ? setHighlightedSliceName(breakdown.project) : null
+              breakdown.project
+                ? setHighlightedSliceName(
+                    getStylingSliceName(breakdown.project, breakdown.sdkName) ?? ''
+                  )
+                : null
             }
           />
         );
@@ -107,19 +113,22 @@ export function TraceBreakdownRenderer({
   );
 }
 
-const BREAKDOWN_BAR_SIZE = 150;
-const BREAKDOWN_QUANTIZE_STEP = 3;
+const BREAKDOWN_BAR_SIZE = 200;
+const BREAKDOWN_QUANTIZE_STEP = 5;
+
 export function SpanBreakdownSliceRenderer({
   trace,
   theme,
   sliceName,
   sliceStart,
   sliceEnd,
+  sliceSecondaryName,
   onMouseEnter,
 }: {
   onMouseEnter: () => void;
   sliceEnd: number;
   sliceName: string | null;
+  sliceSecondaryName: string | null;
   sliceStart: number;
   theme: Theme;
   trace: TraceResult<Field>;
@@ -131,7 +140,10 @@ export function SpanBreakdownSliceRenderer({
   if (sliceDuration <= 0) {
     return null;
   }
-  const sliceColor = sliceName ? pickBarColor(sliceName) : theme.gray100;
+
+  const stylingSliceName = getStylingSliceName(sliceName, sliceSecondaryName);
+  const sliceColor = stylingSliceName ? pickBarColor(stylingSliceName) : theme.gray100;
+
   const sliceWidth =
     BREAKDOWN_QUANTIZE_STEP *
     Math.ceil(
@@ -143,7 +155,7 @@ export function SpanBreakdownSliceRenderer({
     Math.floor(
       ((BREAKDOWN_BAR_SIZE / BREAKDOWN_QUANTIZE_STEP) * relativeSliceStart) /
         traceDuration
-    ); // 150px wide breakdown.
+    );
   return (
     <BreakdownSlice
       sliceName={sliceName}
@@ -156,7 +168,15 @@ export function SpanBreakdownSliceRenderer({
           <div>
             <FlexContainer>
               {sliceName ? <ProjectRenderer projectSlug={sliceName} hideName /> : null}
-              <div>{sliceName}</div>
+              <strong>{sliceName}</strong>
+
+              {sliceSecondaryName ? (
+                <span>
+                  {'\u2014'}
+                  &nbsp;
+                  {sliceSecondaryName}
+                </span>
+              ) : null}
             </FlexContainer>
             <div>
               <PerformanceDuration milliseconds={sliceDuration} abbreviation />
@@ -165,7 +185,7 @@ export function SpanBreakdownSliceRenderer({
         }
         containerDisplayMode="block"
       >
-        <RectangleTraceBreakdown sliceColor={sliceColor} sliceName={sliceName} />
+        <RectangleTraceBreakdown sliceColor={sliceColor} sliceName={stylingSliceName} />
       </Tooltip>
     </BreakdownSlice>
   );
@@ -249,7 +269,11 @@ export function TraceIdRenderer({
     transactionId
   );
 
-  return <Link to={target}>{getShortEventId(traceId)}</Link>;
+  return (
+    <Link to={target} style={{minWidth: '66px', textAlign: 'right'}}>
+      {getShortEventId(traceId)}
+    </Link>
+  );
 }
 
 interface TransactionRendererProps {
@@ -283,6 +307,8 @@ export function TraceIssuesRenderer({trace}: {trace: TraceResult<Field>}) {
 
   const issueCount = trace.numErrors + trace.numOccurrences;
 
+  const issueText = issueCount >= 100 ? '99+' : issueCount === 0 ? '\u2014' : issueCount;
+
   return (
     <LinkButton
       to={normalizeUrl({
@@ -293,18 +319,27 @@ export function TraceIssuesRenderer({trace}: {trace: TraceResult<Field>}) {
       })}
       size="xs"
       icon={<IconIssues size="xs" />}
-      style={{minHeight: '20px', height: '20px'}}
+      disabled={issueCount === 0}
+      style={{minHeight: '24px', height: '24px', minWidth: '44px'}}
     >
-      {issueCount !== undefined ? (
-        issueCount
-      ) : (
-        <LoadingIndicator
-          size={12}
-          mini
-          style={{height: '12px', width: '12px', margin: 0, marginRight: 0}}
-        />
-      )}
-      {issueCount === 100 && '+'}
+      {issueText}
     </LinkButton>
   );
 }
+
+export function SpanTimeRenderer({
+  timestamp,
+  tooltipShowSeconds,
+}: {
+  timestamp: number;
+  tooltipShowSeconds?: boolean;
+}) {
+  const date = new Date(timestamp);
+  return (
+    <TimeSince
+      unitStyle="extraShort"
+      date={date}
+      tooltipShowSeconds={tooltipShowSeconds}
+    />
+  );
+}

+ 18 - 1
static/app/views/performance/traces/utils.tsx

@@ -1,4 +1,5 @@
-import type {TraceResult} from './content';
+import type {SpanResult, TraceResult} from './content';
+import type {Field} from './data';
 
 export function normalizeTraces(traces: TraceResult<string>[] | undefined) {
   if (!traces) {
@@ -9,3 +10,19 @@ export function normalizeTraces(traces: TraceResult<string>[] | undefined) {
     (t1, t2) => (t1.name ? '0' : '1').localeCompare(t2.name ? '0' : '1')
   );
 }
+
+export function getStylingSliceName(
+  sliceName: string | null,
+  sliceSecondaryName: string | null
+) {
+  if (sliceSecondaryName) {
+    // Our color picking relies on the first 4 letters. Since we want to differentiate sdknames and project names we have to include part of the sdk name.
+    return sliceSecondaryName.slice(-2) + (sliceName ?? '');
+  }
+
+  return sliceName;
+}
+
+export function getSecondaryNameFromSpan(span: SpanResult<Field>) {
+  return span['sdk.name'];
+}