traceBar.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import {Fragment, useCallback} from 'react';
  2. import type {Theme} from '@emotion/react';
  3. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  4. import {formatTraceDuration} from 'sentry/utils/duration/formatTraceDuration';
  5. import {getStylingSliceName} from '../../../traces/utils';
  6. import {
  7. isAutogroupedNode,
  8. isMissingInstrumentationNode,
  9. isSpanNode,
  10. isTraceErrorNode,
  11. isTransactionNode,
  12. } from '../traceGuards';
  13. import type {TraceTree} from '../traceModels/traceTree';
  14. import type {TraceTreeNode} from '../traceModels/traceTreeNode';
  15. import type {VirtualizedViewManager} from '../traceRenderers/virtualizedViewManager';
  16. import {TraceBackgroundPatterns} from '../traceRow/traceBackgroundPatterns';
  17. import {TraceErrorIcons, TracePerformanceIssueIcons} from '../traceRow/traceIcons';
  18. export function makeTraceNodeBarColor(
  19. theme: Theme,
  20. node: TraceTreeNode<TraceTree.NodeValue>
  21. ): string {
  22. if (isTransactionNode(node)) {
  23. return pickBarColor(
  24. getStylingSliceName(node.value.project_slug, node.value.sdk_name) ??
  25. node.value['transaction.op']
  26. );
  27. }
  28. if (isSpanNode(node)) {
  29. return pickBarColor(node.value.op);
  30. }
  31. if (isAutogroupedNode(node)) {
  32. if (node.errors.size > 0) {
  33. return theme.red300;
  34. }
  35. return theme.blue300;
  36. }
  37. if (isMissingInstrumentationNode(node)) {
  38. return theme.gray300;
  39. }
  40. if (isTraceErrorNode(node)) {
  41. // Theme defines this as orange, yet everywhere in our product we show red for errors
  42. if (node.value.level === 'error' || node.value.level === 'fatal') {
  43. return theme.red300;
  44. }
  45. if (node.value.level) {
  46. return theme.level[node.value.level] ?? theme.red300;
  47. }
  48. return theme.red300;
  49. }
  50. return pickBarColor('default');
  51. }
  52. interface InvisibleTraceBarProps {
  53. children: React.ReactNode;
  54. manager: VirtualizedViewManager;
  55. node_space: [number, number] | null;
  56. virtualizedIndex: number;
  57. }
  58. export function InvisibleTraceBar(props: InvisibleTraceBarProps) {
  59. const registerInvisibleBarRef = useCallback(
  60. (ref: HTMLDivElement | null) => {
  61. props.manager.registerInvisibleBarRef(
  62. ref,
  63. props.node_space!,
  64. props.virtualizedIndex
  65. );
  66. },
  67. [props.manager, props.node_space, props.virtualizedIndex]
  68. );
  69. const onDoubleClick = useCallback(
  70. (e: React.MouseEvent) => {
  71. e.stopPropagation();
  72. props.manager.onZoomIntoSpace(props.node_space!);
  73. },
  74. [props.manager, props.node_space]
  75. );
  76. if (!props.node_space || !props.children) {
  77. return null;
  78. }
  79. return (
  80. <div
  81. ref={registerInvisibleBarRef}
  82. onDoubleClick={onDoubleClick}
  83. className="TraceBar Invisible"
  84. >
  85. {props.children}
  86. </div>
  87. );
  88. }
  89. interface MissingInstrumentationTraceBarProps {
  90. color: string;
  91. manager: VirtualizedViewManager;
  92. node_space: [number, number] | null;
  93. virtualized_index: number;
  94. }
  95. export function MissingInstrumentationTraceBar(
  96. props: MissingInstrumentationTraceBarProps
  97. ) {
  98. const duration = props.node_space ? formatTraceDuration(props.node_space[1]) : null;
  99. const registerSpanBarRef = useCallback(
  100. (ref: HTMLDivElement | null) => {
  101. props.manager.registerSpanBarRef(
  102. ref,
  103. props.node_space!,
  104. props.color,
  105. props.virtualized_index
  106. );
  107. },
  108. [props.manager, props.node_space, props.color, props.virtualized_index]
  109. );
  110. const registerSpanBarTextRef = useCallback(
  111. (ref: HTMLDivElement | null) => {
  112. props.manager.registerSpanBarTextRef(
  113. ref,
  114. duration!,
  115. props.node_space!,
  116. props.virtualized_index
  117. );
  118. },
  119. [props.manager, props.node_space, props.virtualized_index, duration]
  120. );
  121. return (
  122. <Fragment>
  123. <div ref={registerSpanBarRef} className="TraceBar">
  124. <div className="TracePatternContainer">
  125. <div className="TracePattern missing_instrumentation" />
  126. </div>
  127. </div>
  128. <div ref={registerSpanBarTextRef} className="TraceBarDuration">
  129. {duration}
  130. </div>
  131. </Fragment>
  132. );
  133. }
  134. interface TraceBarProps {
  135. color: string;
  136. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  137. manager: VirtualizedViewManager;
  138. node: TraceTreeNode<TraceTree.NodeValue>;
  139. node_space: [number, number] | null;
  140. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  141. profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
  142. virtualized_index: number;
  143. }
  144. export function TraceBar(props: TraceBarProps) {
  145. const duration = props.node_space
  146. ? formatTraceDuration(props.node_space[1], isTransactionNode(props.node) ? 0 : 2)
  147. : null;
  148. const registerSpanBarRef = useCallback(
  149. (ref: HTMLDivElement | null) => {
  150. props.manager.registerSpanBarRef(
  151. ref,
  152. props.node_space!,
  153. props.color,
  154. props.virtualized_index
  155. );
  156. },
  157. [props.manager, props.node_space, props.color, props.virtualized_index]
  158. );
  159. const registerSpanBarTextRef = useCallback(
  160. (ref: HTMLDivElement | null) => {
  161. props.manager.registerSpanBarTextRef(
  162. ref,
  163. duration!,
  164. props.node_space!,
  165. props.virtualized_index
  166. );
  167. },
  168. [props.manager, props.node_space, props.virtualized_index, duration]
  169. );
  170. if (!props.node_space) {
  171. return null;
  172. }
  173. return (
  174. <Fragment>
  175. <div ref={registerSpanBarRef} className="TraceBar">
  176. {props.errors.size > 0 ? (
  177. <TraceErrorIcons
  178. node_space={props.node_space}
  179. errors={props.errors}
  180. manager={props.manager}
  181. />
  182. ) : null}
  183. {props.performance_issues.size > 0 ? (
  184. <TracePerformanceIssueIcons
  185. node_space={props.node_space}
  186. performance_issues={props.performance_issues}
  187. manager={props.manager}
  188. />
  189. ) : null}
  190. {props.performance_issues.size > 0 ||
  191. props.errors.size > 0 ||
  192. props.profiles.length > 0 ? (
  193. <TraceBackgroundPatterns
  194. node_space={props.node_space}
  195. performance_issues={props.performance_issues}
  196. errors={props.errors}
  197. manager={props.manager}
  198. />
  199. ) : null}
  200. </div>
  201. <div ref={registerSpanBarTextRef} className="TraceBarDuration">
  202. {duration}
  203. </div>
  204. </Fragment>
  205. );
  206. }
  207. interface AutogroupedTraceBarProps {
  208. color: string;
  209. entire_space: [number, number] | null;
  210. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  211. manager: VirtualizedViewManager;
  212. node: TraceTreeNode<TraceTree.NodeValue>;
  213. node_spaces: [number, number][];
  214. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  215. profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
  216. virtualized_index: number;
  217. }
  218. export function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
  219. const duration = props.entire_space ? formatTraceDuration(props.entire_space[1]) : null;
  220. const registerInvisibleBarRef = useCallback(
  221. (ref: HTMLDivElement | null) => {
  222. props.manager.registerInvisibleBarRef(
  223. ref,
  224. props.entire_space!,
  225. props.virtualized_index
  226. );
  227. },
  228. [props.manager, props.entire_space, props.virtualized_index]
  229. );
  230. const registerAutogroupedSpanBarTextRef = useCallback(
  231. (ref: HTMLDivElement | null) => {
  232. props.manager.registerSpanBarTextRef(
  233. ref,
  234. duration!,
  235. props.entire_space!,
  236. props.virtualized_index
  237. );
  238. },
  239. [props.manager, props.entire_space, props.virtualized_index, duration]
  240. );
  241. if (props.node_spaces && props.node_spaces.length <= 1) {
  242. return (
  243. <TraceBar
  244. color={props.color}
  245. node={props.node}
  246. node_space={props.entire_space}
  247. manager={props.manager}
  248. virtualized_index={props.virtualized_index}
  249. errors={props.errors}
  250. performance_issues={props.performance_issues}
  251. profiles={props.profiles}
  252. />
  253. );
  254. }
  255. if (!props.node_spaces || !props.entire_space) {
  256. return null;
  257. }
  258. return (
  259. <Fragment>
  260. <div ref={registerInvisibleBarRef} className="TraceBar Invisible">
  261. {props.node_spaces.map((node_space, i) => {
  262. const width = node_space[1] / props.entire_space![1];
  263. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  264. node_space[0],
  265. props.entire_space!
  266. );
  267. return (
  268. <div
  269. key={i}
  270. className="TraceBar"
  271. style={{
  272. left: `${left * 100}%`,
  273. width: `${width * 100}%`,
  274. backgroundColor: props.color,
  275. }}
  276. />
  277. );
  278. })}
  279. {/* Autogrouped bars only render icons. That is because in the case of multiple bars
  280. with tiny gaps, the background pattern looks broken as it does not repeat nicely */}
  281. {props.errors.size > 0 ? (
  282. <TraceErrorIcons
  283. node_space={props.entire_space}
  284. errors={props.errors}
  285. manager={props.manager}
  286. />
  287. ) : null}
  288. {props.performance_issues.size > 0 ? (
  289. <TracePerformanceIssueIcons
  290. node_space={props.entire_space}
  291. performance_issues={props.performance_issues}
  292. manager={props.manager}
  293. />
  294. ) : null}
  295. </div>
  296. <div ref={registerAutogroupedSpanBarTextRef} className="TraceBarDuration">
  297. {duration}
  298. </div>
  299. </Fragment>
  300. );
  301. }