traceBar.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. let duration: string | null = null;
  146. if (props.node_space) {
  147. // Since transactions have ms precision, we show 2 decimal places only if the duration is greater than 1 second.
  148. const precision = isTransactionNode(props.node)
  149. ? props.node_space[1] >= 1000
  150. ? 2
  151. : 0
  152. : 2;
  153. duration = formatTraceDuration(props.node_space[1], precision);
  154. }
  155. const registerSpanBarRef = useCallback(
  156. (ref: HTMLDivElement | null) => {
  157. props.manager.registerSpanBarRef(
  158. ref,
  159. props.node_space!,
  160. props.color,
  161. props.virtualized_index
  162. );
  163. },
  164. [props.manager, props.node_space, props.color, props.virtualized_index]
  165. );
  166. const registerSpanBarTextRef = useCallback(
  167. (ref: HTMLDivElement | null) => {
  168. props.manager.registerSpanBarTextRef(
  169. ref,
  170. duration!,
  171. props.node_space!,
  172. props.virtualized_index
  173. );
  174. },
  175. [props.manager, props.node_space, props.virtualized_index, duration]
  176. );
  177. if (!props.node_space) {
  178. return null;
  179. }
  180. return (
  181. <Fragment>
  182. <div ref={registerSpanBarRef} className="TraceBar">
  183. {props.errors.size > 0 ? (
  184. <TraceErrorIcons
  185. node_space={props.node_space}
  186. errors={props.errors}
  187. manager={props.manager}
  188. />
  189. ) : null}
  190. {props.performance_issues.size > 0 ? (
  191. <TracePerformanceIssueIcons
  192. node_space={props.node_space}
  193. performance_issues={props.performance_issues}
  194. manager={props.manager}
  195. />
  196. ) : null}
  197. {props.performance_issues.size > 0 ||
  198. props.errors.size > 0 ||
  199. props.profiles.length > 0 ? (
  200. <TraceBackgroundPatterns
  201. node_space={props.node_space}
  202. performance_issues={props.performance_issues}
  203. errors={props.errors}
  204. manager={props.manager}
  205. />
  206. ) : null}
  207. </div>
  208. <div ref={registerSpanBarTextRef} className="TraceBarDuration">
  209. {duration}
  210. </div>
  211. </Fragment>
  212. );
  213. }
  214. interface AutogroupedTraceBarProps {
  215. color: string;
  216. entire_space: [number, number] | null;
  217. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  218. manager: VirtualizedViewManager;
  219. node: TraceTreeNode<TraceTree.NodeValue>;
  220. node_spaces: [number, number][];
  221. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  222. profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
  223. virtualized_index: number;
  224. }
  225. export function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
  226. const duration = props.entire_space ? formatTraceDuration(props.entire_space[1]) : null;
  227. const registerInvisibleBarRef = useCallback(
  228. (ref: HTMLDivElement | null) => {
  229. props.manager.registerInvisibleBarRef(
  230. ref,
  231. props.entire_space!,
  232. props.virtualized_index
  233. );
  234. },
  235. [props.manager, props.entire_space, props.virtualized_index]
  236. );
  237. const registerAutogroupedSpanBarTextRef = useCallback(
  238. (ref: HTMLDivElement | null) => {
  239. props.manager.registerSpanBarTextRef(
  240. ref,
  241. duration!,
  242. props.entire_space!,
  243. props.virtualized_index
  244. );
  245. },
  246. [props.manager, props.entire_space, props.virtualized_index, duration]
  247. );
  248. if (props.node_spaces && props.node_spaces.length <= 1) {
  249. return (
  250. <TraceBar
  251. color={props.color}
  252. node={props.node}
  253. node_space={props.entire_space}
  254. manager={props.manager}
  255. virtualized_index={props.virtualized_index}
  256. errors={props.errors}
  257. performance_issues={props.performance_issues}
  258. profiles={props.profiles}
  259. />
  260. );
  261. }
  262. if (!props.node_spaces || !props.entire_space) {
  263. return null;
  264. }
  265. return (
  266. <Fragment>
  267. <div ref={registerInvisibleBarRef} className="TraceBar Invisible">
  268. {props.node_spaces.map((node_space, i) => {
  269. const width = node_space[1] / props.entire_space![1];
  270. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  271. node_space[0],
  272. props.entire_space!
  273. );
  274. return (
  275. <div
  276. key={i}
  277. className="TraceBar"
  278. style={{
  279. left: `${left * 100}%`,
  280. width: `${width * 100}%`,
  281. backgroundColor: props.color,
  282. }}
  283. />
  284. );
  285. })}
  286. {/* Autogrouped bars only render icons. That is because in the case of multiple bars
  287. with tiny gaps, the background pattern looks broken as it does not repeat nicely */}
  288. {props.errors.size > 0 ? (
  289. <TraceErrorIcons
  290. node_space={props.entire_space}
  291. errors={props.errors}
  292. manager={props.manager}
  293. />
  294. ) : null}
  295. {props.performance_issues.size > 0 ? (
  296. <TracePerformanceIssueIcons
  297. node_space={props.entire_space}
  298. performance_issues={props.performance_issues}
  299. manager={props.manager}
  300. />
  301. ) : null}
  302. </div>
  303. <div ref={registerAutogroupedSpanBarTextRef} className="TraceBarDuration">
  304. {duration}
  305. </div>
  306. </Fragment>
  307. );
  308. }