Files
sentinel/static/app/utils/profiling/profile/sentrySampledProfile.spec.tsx
2024-02-15 17:01:50 +00:00

518 lines
13 KiB
TypeScript

import merge from 'lodash/merge';
import type {DeepPartial} from 'sentry/types/utils';
import {Frame} from '../frame';
import {makeTestingBoilerplate} from './profile.spec';
import {SentrySampledProfile} from './sentrySampledProfile';
import {createSentrySampleProfileFrameIndex} from './utils';
export const makeSentrySampledProfile = (
profile?: DeepPartial<Profiling.SentrySampledProfile>
) => {
return merge(
{
event_id: '1',
version: '1',
os: {
name: 'iOS',
version: '16.0',
build_number: '19H253',
},
device: {
architecture: 'arm64e',
is_emulator: false,
locale: 'en_US',
manufacturer: 'Apple',
model: 'iPhone14,3',
},
timestamp: '2022-09-01T09:45:00.000Z',
platform: 'cocoa',
profile: {
samples: [
{
stack_id: 0,
thread_id: '0',
elapsed_since_start_ns: 0,
},
{
stack_id: 1,
thread_id: '0',
elapsed_since_start_ns: 1000,
},
],
frames: [
{
function: 'main',
instruction_addr: '',
lineno: 1,
colno: 1,
file: 'main.c',
},
{
function: 'foo',
instruction_addr: '',
lineno: 2,
colno: 2,
file: 'main.c',
},
],
stacks: [[1, 0], [0]],
},
transaction: {
id: '',
name: 'foo',
active_thread_id: 0,
trace_id: '1',
},
},
profile
) as Profiling.SentrySampledProfile;
};
describe('SentrySampledProfile', () => {
it('constructs a profile', () => {
const sampledProfile: Profiling.SentrySampledProfile = makeSentrySampledProfile();
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
const {open, close, timings} = makeTestingBoilerplate();
profile.forEach(open, close);
expect(profile.duration).toBe(1000);
expect(timings).toEqual([
['main', 'open'],
['foo', 'open'],
['foo', 'close'],
['main', 'close'],
]);
expect(profile.startedAt).toEqual(0);
expect(profile.endedAt).toEqual(1000);
});
it('tracks discarded samples', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.stats.discardedSamplesCount).toBe(2);
});
it('tracks negative samples', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: -1000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.stats.negativeSamplesCount).toBe(1);
});
it('tracks raw weights', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: 2000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: 3000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.rawWeights.length).toBe(2);
});
it('derives a profile name from the transaction.name and thread_id', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('bar');
expect(profile.threadId).toBe(0);
});
it('derives a profile name from just thread_id', () => {
const sampledProfile = makeSentrySampledProfile({
platform: 'python',
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
],
thread_metadata: {},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('');
expect(profile.threadId).toBe(0);
});
it('derives a profile name from just thread name', () => {
const sampledProfile = makeSentrySampledProfile({
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'foo',
},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('foo');
expect(profile.threadId).toBe(0);
});
it('derives a coca profile name from active thread id', () => {
const sampledProfile = makeSentrySampledProfile({
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
],
thread_metadata: {},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('com.apple.main-thread');
expect(profile.threadId).toBe(0);
});
it('derives a coca profile name from queue label', () => {
const sampledProfile = makeSentrySampledProfile({
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '1',
queue_address: '0x000000016bec7180',
},
],
thread_metadata: {},
queue_metadata: {
'0x000000016bec7180': {label: 'sentry-http-transport'},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('sentry-http-transport');
expect(profile.threadId).toBe(1);
});
it('derives a coca profile name from queue label thats main thread', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '1',
queue_address: '0x000000016bec7180',
},
],
thread_metadata: {},
queue_metadata: {
'0x000000016bec7180': {label: 'com.apple.main-thread'},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('com.apple.main-thread');
expect(profile.threadId).toBe(1);
});
it('derives a coca profile name from queue label thats not main thread', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 0,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '1',
queue_address: '0x000000016bec7180',
},
],
thread_metadata: {},
queue_metadata: {
'0x000000016bec7180': {label: 'com.apple.main-thread'},
},
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamechart'}
);
expect(profile.name).toBe('');
expect(profile.threadId).toBe(1);
});
it('flamegraph tracks node occurrences', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
{
stack_id: 1,
elapsed_since_start_ns: 2000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: 3000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
// Frame 0 occurs 3 times, frame 1 occurs once
stacks: [[0], [1, 0], [0]],
frames: [{function: 'f0'}, {function: 'f1'}, {function: 'f2'}],
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{type: 'flamegraph'}
);
expect(profile.callTree.children[0].count).toBe(2);
expect(profile.callTree.children[0].children[0].count).toBe(1);
});
it('filters frames', () => {
const sampledProfile = makeSentrySampledProfile({
transaction: {
id: '',
name: 'foo',
active_thread_id: 1,
trace_id: '1',
},
profile: {
samples: [
{
stack_id: 0,
elapsed_since_start_ns: 1000,
thread_id: '0',
},
{
stack_id: 0,
elapsed_since_start_ns: 2000,
thread_id: '0',
},
],
thread_metadata: {
'0': {
name: 'bar',
},
},
stacks: [[1, 0]],
frames: [{function: 'f0'}, {function: 'f1'}],
},
});
const profile = SentrySampledProfile.FromProfile(
sampledProfile,
createSentrySampleProfileFrameIndex(sampledProfile.profile.frames, 'javascript'),
{
type: 'flamegraph',
frameFilter: frame => frame.name === 'f0',
}
);
expect(profile.callTree.frame).toBe(Frame.Root);
expect(profile.callTree.children).toHaveLength(1);
expect(profile.callTree.children[0].frame.name).toEqual('f0');
// the f1 frame is filtered out, so the f0 frame has no children
expect(profile.callTree.children[0].children).toHaveLength(0);
});
});