utils.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  2. import {EntryType, EventOrGroupType, EventTransaction, IssueType} from 'sentry/types';
  3. export enum ProblemSpan {
  4. PARENT = 'parent',
  5. OFFENDER = 'offender',
  6. CAUSE = 'cause',
  7. }
  8. export const EXAMPLE_TRANSACTION_TITLE = '/api/0/transaction-test-endpoint/';
  9. type AddSpanOpts = {
  10. endTimestamp: number;
  11. startTimestamp: number;
  12. data?: Record<string, any>;
  13. description?: string;
  14. hash?: string;
  15. op?: string;
  16. problemSpan?: ProblemSpan | ProblemSpan[];
  17. status?: string;
  18. };
  19. interface TransactionSettings {
  20. duration?: number;
  21. fcp?: number;
  22. }
  23. export class TransactionEventBuilder {
  24. TRACE_ID = '8cbbc19c0f54447ab702f00263262726';
  25. ROOT_SPAN_ID = '0000000000000000';
  26. #event: EventTransaction;
  27. #spans: RawSpanType[] = [];
  28. constructor(
  29. id?: string,
  30. title?: string,
  31. problemType?: IssueType,
  32. transactionSettings?: TransactionSettings,
  33. occurenceBasedEvent?: boolean
  34. ) {
  35. const perfEvidenceData = {
  36. causeSpanIds: [],
  37. offenderSpanIds: [],
  38. parentSpanIds: [],
  39. };
  40. this.#event = {
  41. id: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  42. eventID: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  43. title: title ?? EXAMPLE_TRANSACTION_TITLE,
  44. type: EventOrGroupType.TRANSACTION,
  45. startTimestamp: 0,
  46. endTimestamp: transactionSettings?.duration ?? 0,
  47. contexts: {
  48. trace: {
  49. trace_id: this.TRACE_ID,
  50. span_id: this.ROOT_SPAN_ID,
  51. op: 'pageload',
  52. status: 'ok',
  53. type: 'trace',
  54. },
  55. },
  56. entries: [
  57. {
  58. data: this.#spans,
  59. type: EntryType.SPANS,
  60. },
  61. ],
  62. // For the purpose of mock data, we don't care as much about the properties below.
  63. // They're here to satisfy the type constraints, but in the future if we need actual values here
  64. // for testing purposes, we can add methods on the builder to set them.
  65. crashFile: null,
  66. culprit: '',
  67. dateReceived: '',
  68. dist: null,
  69. errors: [],
  70. fingerprints: [],
  71. location: null,
  72. message: '',
  73. measurements: {
  74. fcp: {
  75. value: transactionSettings?.fcp ?? 0,
  76. unit: 'millisecond',
  77. },
  78. },
  79. perfProblem: undefined,
  80. metadata: {
  81. current_level: undefined,
  82. current_tree_label: undefined,
  83. directive: undefined,
  84. display_title_with_tree_label: undefined,
  85. filename: undefined,
  86. finest_tree_label: undefined,
  87. function: undefined,
  88. message: undefined,
  89. origin: undefined,
  90. stripped_crash: undefined,
  91. title: undefined,
  92. type: undefined,
  93. uri: undefined,
  94. value: undefined,
  95. },
  96. occurrence: null,
  97. projectID: '',
  98. size: 0,
  99. tags: [],
  100. user: null,
  101. };
  102. if (occurenceBasedEvent) {
  103. this.#event.occurrence = {
  104. evidenceData: perfEvidenceData,
  105. eventId: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  106. detectionTime: '100',
  107. evidenceDisplay: [],
  108. fingerprint: ['fingerprint123'],
  109. id: 'id123',
  110. issueTitle: 'N + 1 Query',
  111. resourceId: '',
  112. subtitle: 'SELECT * FROM TABLE',
  113. type: 1006,
  114. };
  115. } else {
  116. this.#event.perfProblem = perfEvidenceData;
  117. this.#event.perfProblem.issueType =
  118. problemType ?? IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES;
  119. }
  120. }
  121. generateSpanId() {
  122. // Convert the num of spans to a hex string to get its ID
  123. return (this.#spans.length + 1).toString(16).padStart(16, '0');
  124. }
  125. addEntry(entry: EventTransaction['entries'][number]) {
  126. this.#event.entries.push(entry);
  127. }
  128. addSpan(mockSpan: MockSpan, numSpans = 1, parentSpanId?: string) {
  129. for (let i = 0; i < numSpans; i++) {
  130. const spanId = this.generateSpanId();
  131. const {span} = mockSpan;
  132. const clonedSpan = {...span};
  133. clonedSpan.span_id = spanId;
  134. clonedSpan.trace_id = this.TRACE_ID;
  135. clonedSpan.parent_span_id = parentSpanId ?? this.ROOT_SPAN_ID;
  136. this.#spans.push(clonedSpan);
  137. const problemSpans = Array.isArray(mockSpan.problemSpan)
  138. ? mockSpan.problemSpan
  139. : [mockSpan.problemSpan];
  140. const perfEvidenceData =
  141. this.#event.perfProblem ?? this.#event.occurrence?.evidenceData;
  142. problemSpans.forEach(problemSpan => {
  143. switch (problemSpan) {
  144. case ProblemSpan.PARENT:
  145. perfEvidenceData?.parentSpanIds.push(spanId);
  146. break;
  147. case ProblemSpan.OFFENDER:
  148. perfEvidenceData?.offenderSpanIds.push(spanId);
  149. break;
  150. case ProblemSpan.CAUSE:
  151. perfEvidenceData?.causeSpanIds.push(spanId);
  152. break;
  153. default:
  154. break;
  155. }
  156. });
  157. if (clonedSpan.timestamp > this.#event.endTimestamp) {
  158. this.#event.endTimestamp = clonedSpan.timestamp;
  159. }
  160. mockSpan.children.forEach(child => this.addSpan(child, 1, spanId));
  161. }
  162. return this;
  163. }
  164. getEvent() {
  165. return this.#event;
  166. }
  167. }
  168. /**
  169. * A MockSpan object to be used for testing. This object is intended to be used in tandem with `TransactionEventBuilder`
  170. */
  171. export class MockSpan {
  172. span: RawSpanType;
  173. children: MockSpan[] = [];
  174. problemSpan: ProblemSpan | ProblemSpan[] | undefined;
  175. /**
  176. *
  177. * @param opts.startTimestamp
  178. * @param opts.endTimestamp
  179. * @param opts.op The operation of the span
  180. * @param opts.description The description of the span
  181. * @param opts.status Optional span specific status, defaults to 'ok'
  182. * @param opts.problemSpan If this span should be part of a performance problem, indicates the type of problem span (i.e ProblemSpan.OFFENDER, ProblemSpan.PARENT)
  183. * @param opts.parentSpanId When provided, will explicitly set this span's parent ID. If you are creating nested spans via `addChild` on the `MockSpan` object,
  184. * this will be handled automatically and you do not need to provide an ID. Defaults to the root span's ID.
  185. */
  186. constructor(opts: AddSpanOpts) {
  187. const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
  188. opts;
  189. this.span = {
  190. start_timestamp: startTimestamp,
  191. timestamp: endTimestamp,
  192. op,
  193. description,
  194. hash,
  195. status: status ?? 'ok',
  196. data: opts.data || {},
  197. // These values are automatically assigned by the TransactionEventBuilder when the spans are added
  198. span_id: '',
  199. trace_id: '',
  200. parent_span_id: '',
  201. };
  202. this.problemSpan = problemSpan;
  203. }
  204. /**
  205. *
  206. * @param opts.numSpans If provided, will create the same span numSpan times
  207. */
  208. addChild(opts: AddSpanOpts, numSpans = 1) {
  209. const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
  210. opts;
  211. for (let i = 0; i < numSpans; i++) {
  212. const span = new MockSpan({
  213. startTimestamp,
  214. endTimestamp,
  215. op,
  216. description,
  217. hash,
  218. status,
  219. problemSpan,
  220. });
  221. this.children.push(span);
  222. }
  223. return this;
  224. }
  225. /**
  226. * Allows you to create a nested group of duplicate mock spans by duplicating the current span. This is useful for simulating the nested 'autogrouped' condition on the span tree.
  227. * Will create `depth` spans, each span being a child of the previous.
  228. * @param depth
  229. */
  230. addDuplicateNestedChildren(depth = 1) {
  231. let currentSpan: MockSpan = this;
  232. for (let i = 0; i < depth; i++) {
  233. currentSpan.addChild(currentSpan.getOpts());
  234. currentSpan = currentSpan.children[0];
  235. }
  236. return this;
  237. }
  238. getOpts() {
  239. return {
  240. startTimestamp: this.span.start_timestamp,
  241. endTimestamp: this.span.timestamp,
  242. op: this.span.op,
  243. description: this.span.description,
  244. status: this.span.status,
  245. problemSpan: this.problemSpan,
  246. };
  247. }
  248. }