mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
279 lines
7.8 KiB
TypeScript
279 lines
7.8 KiB
TypeScript
import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
|
|
import type {EventTransaction} from 'sentry/types';
|
|
import {EntryType, EventOrGroupType, IssueType} from 'sentry/types';
|
|
|
|
export enum ProblemSpan {
|
|
PARENT = 'parent',
|
|
OFFENDER = 'offender',
|
|
CAUSE = 'cause',
|
|
}
|
|
|
|
export const EXAMPLE_TRANSACTION_TITLE = '/api/0/transaction-test-endpoint/';
|
|
|
|
type AddSpanOpts = {
|
|
endTimestamp: number;
|
|
startTimestamp: number;
|
|
data?: Record<string, any>;
|
|
description?: string;
|
|
hash?: string;
|
|
op?: string;
|
|
problemSpan?: ProblemSpan | ProblemSpan[];
|
|
status?: string;
|
|
};
|
|
|
|
interface TransactionSettings {
|
|
duration?: number;
|
|
fcp?: number;
|
|
}
|
|
export class TransactionEventBuilder {
|
|
TRACE_ID = '8cbbc19c0f54447ab702f00263262726';
|
|
ROOT_SPAN_ID = '0000000000000000';
|
|
#event: EventTransaction;
|
|
#spans: RawSpanType[] = [];
|
|
|
|
constructor(
|
|
id?: string,
|
|
title?: string,
|
|
problemType?: IssueType,
|
|
transactionSettings?: TransactionSettings,
|
|
occurenceBasedEvent?: boolean
|
|
) {
|
|
const perfEvidenceData = {
|
|
causeSpanIds: [],
|
|
offenderSpanIds: [],
|
|
parentSpanIds: [],
|
|
};
|
|
this.#event = {
|
|
id: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
eventID: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
title: title ?? EXAMPLE_TRANSACTION_TITLE,
|
|
type: EventOrGroupType.TRANSACTION,
|
|
startTimestamp: 0,
|
|
endTimestamp: transactionSettings?.duration ?? 0,
|
|
contexts: {
|
|
trace: {
|
|
trace_id: this.TRACE_ID,
|
|
span_id: this.ROOT_SPAN_ID,
|
|
op: 'pageload',
|
|
status: 'ok',
|
|
type: 'trace',
|
|
},
|
|
},
|
|
entries: [
|
|
{
|
|
data: this.#spans,
|
|
type: EntryType.SPANS,
|
|
},
|
|
],
|
|
|
|
// For the purpose of mock data, we don't care as much about the properties below.
|
|
// They're here to satisfy the type constraints, but in the future if we need actual values here
|
|
// for testing purposes, we can add methods on the builder to set them.
|
|
crashFile: null,
|
|
culprit: '',
|
|
dateReceived: '',
|
|
dist: null,
|
|
errors: [],
|
|
fingerprints: [],
|
|
location: null,
|
|
message: '',
|
|
measurements: {
|
|
fcp: {
|
|
value: transactionSettings?.fcp ?? 0,
|
|
unit: 'millisecond',
|
|
},
|
|
},
|
|
perfProblem: undefined,
|
|
metadata: {
|
|
current_level: undefined,
|
|
current_tree_label: undefined,
|
|
directive: undefined,
|
|
display_title_with_tree_label: undefined,
|
|
filename: undefined,
|
|
finest_tree_label: undefined,
|
|
function: undefined,
|
|
message: undefined,
|
|
origin: undefined,
|
|
stripped_crash: undefined,
|
|
title: undefined,
|
|
type: undefined,
|
|
uri: undefined,
|
|
value: undefined,
|
|
},
|
|
occurrence: null,
|
|
projectID: '',
|
|
size: 0,
|
|
tags: [],
|
|
user: null,
|
|
};
|
|
if (occurenceBasedEvent) {
|
|
this.#event.occurrence = {
|
|
evidenceData: perfEvidenceData,
|
|
eventId: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
detectionTime: '100',
|
|
evidenceDisplay: [],
|
|
fingerprint: ['fingerprint123'],
|
|
id: 'id123',
|
|
issueTitle: 'N + 1 Query',
|
|
resourceId: '',
|
|
subtitle: 'SELECT * FROM TABLE',
|
|
type: 1006,
|
|
};
|
|
} else {
|
|
this.#event.perfProblem = perfEvidenceData;
|
|
this.#event.perfProblem.issueType =
|
|
problemType ?? IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES;
|
|
}
|
|
}
|
|
|
|
generateSpanId() {
|
|
// Convert the num of spans to a hex string to get its ID
|
|
return (this.#spans.length + 1).toString(16).padStart(16, '0');
|
|
}
|
|
|
|
addEntry(entry: EventTransaction['entries'][number]) {
|
|
this.#event.entries.push(entry);
|
|
}
|
|
|
|
addSpan(mockSpan: MockSpan, numSpans = 1, parentSpanId?: string) {
|
|
for (let i = 0; i < numSpans; i++) {
|
|
const spanId = this.generateSpanId();
|
|
const {span} = mockSpan;
|
|
const clonedSpan = {...span};
|
|
|
|
clonedSpan.span_id = spanId;
|
|
clonedSpan.trace_id = this.TRACE_ID;
|
|
clonedSpan.parent_span_id = parentSpanId ?? this.ROOT_SPAN_ID;
|
|
|
|
this.#spans.push(clonedSpan);
|
|
|
|
const problemSpans = Array.isArray(mockSpan.problemSpan)
|
|
? mockSpan.problemSpan
|
|
: [mockSpan.problemSpan];
|
|
|
|
const perfEvidenceData =
|
|
this.#event.perfProblem ?? this.#event.occurrence?.evidenceData;
|
|
|
|
problemSpans.forEach(problemSpan => {
|
|
switch (problemSpan) {
|
|
case ProblemSpan.PARENT:
|
|
perfEvidenceData?.parentSpanIds.push(spanId);
|
|
break;
|
|
case ProblemSpan.OFFENDER:
|
|
perfEvidenceData?.offenderSpanIds.push(spanId);
|
|
break;
|
|
case ProblemSpan.CAUSE:
|
|
perfEvidenceData?.causeSpanIds.push(spanId);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
if (clonedSpan.timestamp > this.#event.endTimestamp) {
|
|
this.#event.endTimestamp = clonedSpan.timestamp;
|
|
}
|
|
|
|
mockSpan.children.forEach(child => this.addSpan(child, 1, spanId));
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
getEventFixture() {
|
|
return this.#event;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A MockSpan object to be used for testing. This object is intended to be used in tandem with `TransactionEventBuilder`
|
|
*/
|
|
export class MockSpan {
|
|
span: RawSpanType;
|
|
children: MockSpan[] = [];
|
|
problemSpan: ProblemSpan | ProblemSpan[] | undefined;
|
|
|
|
/**
|
|
*
|
|
* @param opts.startTimestamp
|
|
* @param opts.endTimestamp
|
|
* @param opts.op The operation of the span
|
|
* @param opts.description The description of the span
|
|
* @param opts.status Optional span specific status, defaults to 'ok'
|
|
* @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)
|
|
* @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,
|
|
* this will be handled automatically and you do not need to provide an ID. Defaults to the root span's ID.
|
|
*/
|
|
constructor(opts: AddSpanOpts) {
|
|
const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
|
|
opts;
|
|
|
|
this.span = {
|
|
start_timestamp: startTimestamp,
|
|
timestamp: endTimestamp,
|
|
op,
|
|
description,
|
|
hash,
|
|
status: status ?? 'ok',
|
|
data: opts.data || {},
|
|
// These values are automatically assigned by the TransactionEventBuilder when the spans are added
|
|
span_id: '',
|
|
trace_id: '',
|
|
parent_span_id: '',
|
|
};
|
|
|
|
this.problemSpan = problemSpan;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param opts.numSpans If provided, will create the same span numSpan times
|
|
*/
|
|
addChild(opts: AddSpanOpts, numSpans = 1) {
|
|
const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
|
|
opts;
|
|
|
|
for (let i = 0; i < numSpans; i++) {
|
|
const span = new MockSpan({
|
|
startTimestamp,
|
|
endTimestamp,
|
|
op,
|
|
description,
|
|
hash,
|
|
status,
|
|
problemSpan,
|
|
});
|
|
this.children.push(span);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Will create `depth` spans, each span being a child of the previous.
|
|
* @param depth
|
|
*/
|
|
addDuplicateNestedChildren(depth = 1) {
|
|
let currentSpan: MockSpan = this;
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
currentSpan.addChild(currentSpan.getOpts());
|
|
currentSpan = currentSpan.children[0];
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
getOpts() {
|
|
return {
|
|
startTimestamp: this.span.start_timestamp,
|
|
endTimestamp: this.span.timestamp,
|
|
op: this.span.op,
|
|
description: this.span.description,
|
|
status: this.span.status,
|
|
problemSpan: this.problemSpan,
|
|
};
|
|
}
|
|
}
|