Browse Source

feat(trace): render profile icon (#67088)

Renders profile icon on the timeline

---------

Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
Co-authored-by: Abdkhan14 <60121741+Abdkhan14@users.noreply.github.com>
Jonas 1 year ago
parent
commit
f5d9d32224

+ 107 - 11
static/app/views/performance/newTraceDetails/trace.tsx

@@ -18,7 +18,6 @@ import * as qs from 'query-string';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
 import Placeholder from 'sentry/components/placeholder';
-import {IconFire} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import type {Organization, PlatformKey, Project} from 'sentry/types';
 import {getDuration} from 'sentry/utils/formatters';
@@ -61,8 +60,6 @@ function Chevron(props: {direction: 'up' | 'down' | 'left'}) {
     <svg
       viewBox="0 0 16 16"
       style={{
-        fill: 'currentcolor',
-        color: 'currentcolor',
         transition: 'transform 120ms ease-in-out',
         transform: `rotate(${props.direction === 'up' ? 0 : props.direction === 'down' ? 180 : -90}deg)`,
       }}
@@ -72,6 +69,22 @@ function Chevron(props: {direction: 'up' | 'down' | 'left'}) {
   );
 }
 
+function Fire() {
+  return (
+    <svg viewBox="0 0 16 16">
+      <path d="M8.08,15.92A6.58,6.58,0,0,1,1.51,9.34a4.88,4.88,0,0,1,2.2-4.25.74.74,0,0,1,1,.34,6,6,0,0,1,4-5.3A.74.74,0,0,1,9.4.22a.73.73,0,0,1,.33.61v.31A15.07,15.07,0,0,0,10,4.93a3.72,3.72,0,0,1,2.3-1.7.74.74,0,0,1,.66.12.75.75,0,0,1,.3.6A6.21,6.21,0,0,0,14,6.79a5.78,5.78,0,0,1,.68,2.55A6.58,6.58,0,0,1,8.08,15.92ZM3.59,7.23A4.25,4.25,0,0,0,3,9.34a5.07,5.07,0,1,0,10.14,0,4.6,4.6,0,0,0-.54-1.94,8,8,0,0,1-.76-2.32A2,2,0,0,0,11.07,7a.75.75,0,0,1-1.32.58C8.4,6,8.25,4.22,8.23,2c-2,1.29-2.15,3.58-2.09,5.85A7.52,7.52,0,0,1,6.14,9a.74.74,0,0,1-.46.63.77.77,0,0,1-.76-.11A4.56,4.56,0,0,1,3.59,7.23Z" />
+    </svg>
+  );
+}
+
+function Profile() {
+  return (
+    <svg viewBox="0 0 20 16">
+      <path d="M15.25,0H.75C.33,0,0,.34,0,.75V5.59c0,.41,.34,.75,.75,.75h1.49v4.09c0,.41,.34,.75,.75,.75h1.73v4.09c0,.41,.34,.75,.75,.75h5.06c.41,0,.75-.34,.75-.75v-4.09h1.73c.41,0,.75-.34,.75-.75V6.34h1.49c.41,0,.75-.34,.75-.75V.75c0-.41-.34-.75-.75-.75Zm-5.47,14.52h-3.56v-3.34h3.56v3.34Zm2.48-4.84H3.74v-3.34H12.25v3.34Zm2.24-4.84H1.5V1.5H14.5v3.34Z" />
+    </svg>
+  );
+}
+
 function decodeScrollQueue(maybePath: unknown): TraceTree.NodePath[] | null {
   if (Array.isArray(maybePath)) {
     return maybePath;
@@ -792,6 +805,7 @@ function RenderRow(props: {
             node_spaces={props.node.autogroupedSegments}
             errors={props.node.errors}
             performance_issues={props.node.performance_issues}
+            profiles={props.node.profiles}
           />
           <button
             ref={ref =>
@@ -907,6 +921,7 @@ function RenderRow(props: {
             node_space={props.node.space}
             errors={props.node.value.errors}
             performance_issues={props.node.value.performance_issues}
+            profiles={props.node.profiles}
           />
           <button
             ref={ref =>
@@ -1023,6 +1038,7 @@ function RenderRow(props: {
             node_space={props.node.space}
             errors={props.node.value.errors}
             performance_issues={props.node.value.performance_issues}
+            profiles={NO_ERRORS}
           />
           <button
             ref={ref =>
@@ -1097,8 +1113,9 @@ function RenderRow(props: {
             manager={props.manager}
             color={pickBarColor('missing-instrumentation')}
             node_space={props.node.space}
-            errors={NO_ERRORS}
             performance_issues={NO_ERRORS}
+            profiles={NO_ERRORS}
+            errors={NO_ERRORS}
           />
           <button
             ref={ref =>
@@ -1185,6 +1202,7 @@ function RenderRow(props: {
             node_space={props.node.space}
             errors={NO_ERRORS}
             performance_issues={NO_ERRORS}
+            profiles={NO_ERRORS}
           />
           <button
             ref={ref =>
@@ -1266,7 +1284,7 @@ function RenderRow(props: {
           >
             {typeof props.node.value.timestamp === 'number' ? (
               <div className="TraceError">
-                <IconFire color="errorText" size="xs" />
+                <Fire />
               </div>
             ) : null}
           </InvisibleTraceBar>
@@ -1435,8 +1453,8 @@ interface TraceBarProps {
   manager: VirtualizedViewManager;
   node_space: [number, number] | null;
   performance_issues: TraceTreeNode<TraceTree.Transaction>['value']['performance_issues'];
+  profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
   virtualized_index: number;
-  duration?: number;
 }
 
 function TraceBar(props: TraceBarProps) {
@@ -1467,6 +1485,13 @@ function TraceBar(props: TraceBarProps) {
           } as React.CSSProperties
         }
       >
+        {props.profiles.length > 0 ? (
+          <Profiles
+            node_space={props.node_space}
+            profiles={props.profiles}
+            manager={props.manager}
+          />
+        ) : null}
         {props.errors.length > 0 ? (
           <Errors
             node_space={props.node_space}
@@ -1571,7 +1596,7 @@ function PerformanceIssues(props: PerformanceIssuesProps) {
             style={{left: left * 100 + '%', width: width + '%'}}
           >
             <div className="TraceError" style={{left: 0}}>
-              <IconFire color="errorText" size="xs" />
+              <Fire />
             </div>
           </div>
         );
@@ -1587,6 +1612,10 @@ interface ErrorsProps {
 }
 
 function Errors(props: ErrorsProps) {
+  if (!props.errors.length) {
+    return null;
+  }
+
   return (
     <Fragment>
       {props.errors.map((error, _i) => {
@@ -1607,7 +1636,45 @@ function Errors(props: ErrorsProps) {
             className="TraceError"
             style={{left: left * 100 + '%'}}
           >
-            <IconFire color="errorText" size="xs" />
+            <Fire />
+          </div>
+        );
+      })}
+    </Fragment>
+  );
+}
+
+interface ProfilesProps {
+  manager: VirtualizedViewManager;
+  node_space: [number, number] | null;
+  profiles: TraceTree.Profile[];
+}
+
+function Profiles(props: ProfilesProps) {
+  if (!props.profiles.length) {
+    return null;
+  }
+  return (
+    <Fragment>
+      {props.profiles.map((profile, _i) => {
+        const timestamp = profile.space[0];
+        // Clamp the profile timestamp to the span's timestamp
+        const left = props.manager.computeRelativeLeftPositionFromOrigin(
+          clamp(
+            timestamp,
+            props.node_space![0],
+            props.node_space![0] + props.node_space![1]
+          ),
+          props.node_space!
+        );
+
+        return (
+          <div
+            key={profile.profile_id}
+            className="TraceProfile"
+            style={{left: left * 100 + '%'}}
+          >
+            <Profile />
           </div>
         );
       })}
@@ -1622,8 +1689,8 @@ interface AutogroupedTraceBarProps {
   manager: VirtualizedViewManager;
   node_spaces: [number, number][];
   performance_issues: TraceTreeNode<TraceTree.Transaction>['value']['performance_issues'];
+  profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
   virtualized_index: number;
-  duration?: number;
 }
 
 function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
@@ -1634,9 +1701,9 @@ function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
         node_space={props.entire_space}
         manager={props.manager}
         virtualized_index={props.virtualized_index}
-        duration={props.duration}
         errors={props.errors}
         performance_issues={props.performance_issues}
+        profiles={props.profiles}
       />
     );
   }
@@ -1686,7 +1753,13 @@ function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
             />
           );
         })}
-
+        {props.profiles.length > 0 ? (
+          <Profiles
+            node_space={props.entire_space}
+            profiles={props.profiles}
+            manager={props.manager}
+          />
+        ) : null}
         {props.errors.length > 0 ? (
           <Errors
             node_space={props.entire_space}
@@ -1908,6 +1981,24 @@ const TraceStylingWrapper = styled('div')`
       }
     }
 
+    .TraceProfile {
+      position: absolute;
+      top: 50%;
+      transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale));
+      background: ${p => p.theme.background};
+      width: 18px !important;
+      height: 18px !important;
+      background-color: ${p => p.theme.purple300};
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      svg {
+        fill: ${p => p.theme.white};
+      }
+    }
+
     .TracePerformanceIssue {
       position: absolute;
       top: 0;
@@ -2066,6 +2157,11 @@ const TraceStylingWrapper = styled('div')`
         transform: translate(-50%, 0);
       }
     }
+
+    svg {
+      width: 14px;
+      height: 14px;
+    }
   }
 
   .TraceArrow {

+ 18 - 4
static/app/views/performance/newTraceDetails/traceTree.tsx

@@ -106,6 +106,7 @@ export declare namespace TraceTree {
   }
   type Trace = TraceSplitResults<Transaction>;
   type TraceError = TraceErrorType;
+  type Profile = {profile_id: string; space: [number, number]};
 
   interface MissingInstrumentationSpan {
     start_timestamp: number;
@@ -267,13 +268,12 @@ export class TraceTree {
 
     function visit(
       parent: TraceTreeNode<TraceTree.NodeValue | null>,
-      value: TraceTree.Transaction | TraceTree.TraceError
+      value: TraceFullDetailed | TraceTree.TraceError
     ) {
       const node = new TraceTreeNode(parent, value, {
         project_slug: value && 'project_slug' in value ? value.project_slug : undefined,
         event_id: value && 'event_id' in value ? value.event_id : undefined,
       });
-      node.canFetch = true;
 
       if (parent) {
         parent.children.push(node as TraceTreeNode<TraceTree.NodeValue>);
@@ -974,11 +974,12 @@ export class TraceTreeNode<T extends TraceTree.NodeValue> {
     event_id: undefined,
   };
 
+  multiplier: number;
   space: [number, number] | null = null;
 
-  multiplier: number;
-  private unit: 'milliseconds' = 'milliseconds';
+  profiles: TraceTree.Profile[] = [];
 
+  private unit: 'milliseconds' = 'milliseconds';
   private _depth: number | undefined;
   private _children: TraceTreeNode<TraceTree.NodeValue>[] = [];
   private _spanChildren: TraceTreeNode<
@@ -1001,6 +1002,8 @@ export class TraceTreeNode<T extends TraceTree.NodeValue> {
         value.start_timestamp * this.multiplier,
         (value.timestamp - value.start_timestamp) * this.multiplier,
       ];
+    } else if (value && 'timestamp' in value && typeof value.timestamp === 'number') {
+      this.space = [value.timestamp * this.multiplier, 0];
     }
 
     if (
@@ -1011,6 +1014,14 @@ export class TraceTreeNode<T extends TraceTree.NodeValue> {
       this.space = [this.value.timestamp * this.multiplier, 0];
     }
 
+    if (value && 'profile_id' in value && typeof value.profile_id === 'string') {
+      this.profiles.push({profile_id: value.profile_id, space: this.space ?? [0, 0]});
+    }
+
+    if (isTransactionNode(this)) {
+      this.canFetch = true;
+    }
+
     if (isTransactionNode(this) || isTraceNode(this) || isSpanNode(this)) {
       this.expanded = true;
     }
@@ -1397,6 +1408,7 @@ export class ParentAutogroupNode extends TraceTreeNode<TraceTree.ChildrenAutogro
 
   errors: TraceErrorType[] = [];
   performance_issues: TracePerformanceIssue[] = [];
+  profiles: TraceTree.Profile[] = [];
 
   private _autogroupedSegments: [number, number][] | undefined;
 
@@ -1447,8 +1459,10 @@ export class ParentAutogroupNode extends TraceTreeNode<TraceTree.ChildrenAutogro
 
 export class SiblingAutogroupNode extends TraceTreeNode<TraceTree.SiblingAutogroup> {
   groupCount: number = 0;
+
   errors: TraceErrorType[] = [];
   performance_issues: TracePerformanceIssue[] = [];
+  profiles: TraceTree.Profile[] = [];
 
   private _autogroupedSegments: [number, number][] | undefined;