spanChart.spec.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import {EntrySpans, EventOrGroupType, EventTransaction} from 'sentry/types/event';
  2. import {SpanChart} from 'sentry/utils/profiling/spanChart';
  3. import {SpanTree} from 'sentry/utils/profiling/spanTree';
  4. import {Rect} from './speedscope';
  5. function s(partial: Partial<EntrySpans['data'][0]>): EntrySpans['data'][0] {
  6. return {
  7. timestamp: 0,
  8. start_timestamp: 0,
  9. exclusive_time: 0,
  10. description: '',
  11. op: '',
  12. span_id: '',
  13. parent_span_id: '',
  14. trace_id: '',
  15. hash: '',
  16. data: {},
  17. ...partial,
  18. };
  19. }
  20. function txn(partial: Partial<EventTransaction>): EventTransaction {
  21. return {
  22. id: '',
  23. projectID: '',
  24. user: {},
  25. contexts: {},
  26. entries: [],
  27. errors: [],
  28. dateCreated: '',
  29. startTimestamp: Date.now(),
  30. endTimestamp: Date.now() + 1000,
  31. title: '',
  32. type: EventOrGroupType.TRANSACTION,
  33. culprit: '',
  34. dist: null,
  35. eventID: '',
  36. fingerprints: [],
  37. dateReceived: new Date().toISOString(),
  38. message: '',
  39. metadata: {},
  40. size: 0,
  41. tags: [],
  42. occurrence: null,
  43. location: '',
  44. crashFile: null,
  45. ...partial,
  46. };
  47. }
  48. describe('spanChart', () => {
  49. it('iterates over all spans with depth', () => {
  50. const start = Date.now();
  51. const tree = new SpanTree(
  52. txn({
  53. contexts: {trace: {span_id: 'root'}},
  54. startTimestamp: start + 0,
  55. endTimestamp: start + 1,
  56. }),
  57. [
  58. s({
  59. span_id: '1',
  60. parent_span_id: 'root',
  61. timestamp: start + 1,
  62. start_timestamp: start,
  63. }),
  64. s({
  65. span_id: '2',
  66. parent_span_id: '1',
  67. timestamp: start + 0.5,
  68. start_timestamp: start,
  69. }),
  70. s({
  71. span_id: '3',
  72. parent_span_id: '2',
  73. timestamp: start + 0.2,
  74. start_timestamp: start,
  75. }),
  76. s({
  77. span_id: '4',
  78. parent_span_id: '1',
  79. timestamp: start + 1,
  80. start_timestamp: start + 0.5,
  81. }),
  82. ]
  83. );
  84. expect(tree.root.children[0].children[1].span.span_id).toBe('4');
  85. const chart = new SpanChart(tree);
  86. chart.forEachSpanOfTree(chart.spanTrees[0], 0, span => {
  87. if (span.node.span.span_id === '1') {
  88. expect(span.depth).toBe(1);
  89. } else if (span.node.span.span_id === '2') {
  90. expect(span.depth).toBe(2);
  91. } else if (span.node.span.span_id === '3') {
  92. expect(span.depth).toBe(3);
  93. } else if (span.node.span.span_id === '4') {
  94. expect(span.depth).toBe(2);
  95. }
  96. });
  97. });
  98. it('keeps track of shortest duration span', () => {
  99. const start = Date.now();
  100. const tree = new SpanTree(
  101. txn({
  102. contexts: {trace: {span_id: 'root'}},
  103. startTimestamp: start + 0,
  104. endTimestamp: start + 10,
  105. }),
  106. [
  107. s({
  108. span_id: '1',
  109. parent_span_id: 'root',
  110. timestamp: start + 10,
  111. start_timestamp: start,
  112. }),
  113. s({
  114. span_id: '2',
  115. parent_span_id: '1',
  116. timestamp: start + 5,
  117. start_timestamp: start,
  118. }),
  119. s({
  120. span_id: '3',
  121. parent_span_id: '2',
  122. timestamp: start + 1,
  123. start_timestamp: start,
  124. }),
  125. ]
  126. );
  127. const chart = new SpanChart(tree);
  128. expect(chart.minSpanDuration).toBe(1 * 1e3);
  129. });
  130. it('tracks chart depth', () => {
  131. const start = Date.now();
  132. const tree = new SpanTree(
  133. txn({
  134. contexts: {trace: {span_id: 'root'}},
  135. startTimestamp: start + 0,
  136. endTimestamp: start + 1,
  137. }),
  138. [
  139. s({
  140. span_id: '1',
  141. parent_span_id: 'root',
  142. timestamp: start + 1,
  143. start_timestamp: start,
  144. }),
  145. s({
  146. span_id: '2',
  147. parent_span_id: '1',
  148. timestamp: start + 0.5,
  149. start_timestamp: start,
  150. }),
  151. s({
  152. span_id: '3',
  153. parent_span_id: '2',
  154. timestamp: start + 0.2,
  155. start_timestamp: start,
  156. }),
  157. s({
  158. span_id: '4',
  159. parent_span_id: '3',
  160. timestamp: start + 0.1,
  161. start_timestamp: start,
  162. }),
  163. ]
  164. );
  165. const chart = new SpanChart(tree);
  166. expect(chart.depth).toBe(4);
  167. });
  168. it('initializes configSpace', () => {
  169. const start = Date.now();
  170. const tree = new SpanTree(
  171. txn({
  172. contexts: {trace: {span_id: 'root'}},
  173. startTimestamp: start + 0,
  174. endTimestamp: start + 10,
  175. }),
  176. [
  177. s({
  178. span_id: '1',
  179. parent_span_id: 'root',
  180. timestamp: start + 10,
  181. start_timestamp: start,
  182. }),
  183. s({
  184. span_id: '2',
  185. parent_span_id: '1',
  186. timestamp: start + 5,
  187. start_timestamp: start,
  188. }),
  189. s({
  190. span_id: '3',
  191. parent_span_id: '2',
  192. timestamp: start + 1,
  193. start_timestamp: start,
  194. }),
  195. ]
  196. );
  197. const chart = new SpanChart(tree);
  198. expect(chart.configSpace.equals(new Rect(0, 0, 10 * 1e3, 3))).toBe(true);
  199. });
  200. it('remaps spans to start of benchmark', () => {
  201. const start = Date.now();
  202. const tree = new SpanTree(
  203. txn({
  204. contexts: {trace: {span_id: 'root'}},
  205. startTimestamp: start + 5,
  206. endTimestamp: start + 10,
  207. }),
  208. [
  209. s({
  210. span_id: '1',
  211. parent_span_id: 'root',
  212. timestamp: start + 10,
  213. start_timestamp: start + 6,
  214. }),
  215. s({
  216. span_id: '2',
  217. parent_span_id: '1',
  218. timestamp: start + 9,
  219. start_timestamp: start + 8,
  220. }),
  221. ]
  222. );
  223. const chart = new SpanChart(tree);
  224. expect(chart.spans[1].start).toBe(1 * 1e3);
  225. expect(chart.spans[1].end).toBe(5 * 1e3);
  226. expect(chart.spans[2].start).toBe(3 * 1e3);
  227. expect(chart.spans[2].end).toBe(4 * 1e3);
  228. });
  229. it('converts durations to final unit', () => {
  230. const start = Date.now();
  231. const tree = new SpanTree(
  232. txn({
  233. contexts: {trace: {span_id: 'root'}},
  234. startTimestamp: start + 1,
  235. endTimestamp: start + 11,
  236. }),
  237. [
  238. s({
  239. span_id: '1',
  240. parent_span_id: 'root',
  241. timestamp: start + 4,
  242. start_timestamp: start + 2,
  243. }),
  244. ]
  245. );
  246. const chart = new SpanChart(tree, {unit: 'nanoseconds'});
  247. expect(chart.spans[1].start).toBe(1 * 1e9);
  248. expect(chart.spans[1].end).toBe(3 * 1e9);
  249. expect(chart.spans[1].duration).toBe(2 * 1e9);
  250. expect(chart.configSpace.height).toBe(1);
  251. expect(chart.configSpace.width).toBe(10 * 1e9);
  252. });
  253. it('does not infinitely loop if a span is truly orphaned', () => {
  254. const start = Date.now();
  255. const tree = new SpanTree(
  256. txn({
  257. contexts: {trace: {span_id: 'root'}},
  258. startTimestamp: start + 0,
  259. endTimestamp: start + 10,
  260. }),
  261. [
  262. // If a span belongs to nothing, we dont render it,
  263. // for now we only render spans that ultimately belong
  264. // to the transaction when the parent_span_id is followed
  265. s({
  266. span_id: '1',
  267. parent_span_id: 'root',
  268. timestamp: start + 10,
  269. start_timestamp: start,
  270. }),
  271. s({
  272. span_id: '2',
  273. parent_span_id: undefined,
  274. timestamp: start + 10,
  275. start_timestamp: start,
  276. }),
  277. ]
  278. );
  279. const chart = new SpanChart(tree);
  280. expect(chart.spanTrees.length).toBe(1);
  281. });
  282. it('creates a new tree from orphaned spans', () => {
  283. const start = Date.now();
  284. const tree = new SpanTree(
  285. txn({
  286. contexts: {trace: {span_id: 'root'}},
  287. startTimestamp: start + 0,
  288. endTimestamp: start + 10,
  289. }),
  290. [
  291. // These two spans overlap and as first is inserted,
  292. // the 2nd span can no longer be inserted and the parent_span_id
  293. // edge is no longer satisfied
  294. s({
  295. span_id: '1',
  296. parent_span_id: 'root',
  297. timestamp: start + 10,
  298. start_timestamp: start,
  299. }),
  300. s({
  301. span_id: '2',
  302. parent_span_id: 'root',
  303. timestamp: start + 10,
  304. start_timestamp: start,
  305. }),
  306. ]
  307. );
  308. const chart = new SpanChart(tree);
  309. expect(chart.spanTrees.length).toBe(2);
  310. // Even though they belong to the same root,
  311. // the spans are offset visually by the tree height
  312. expect(chart.spans[1].depth).toBe(1);
  313. expect(chart.spans[2].depth).toBe(2);
  314. });
  315. });