-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
meta(changelog): Update changelog for 10.8.0 #17481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e24422
e6e20d8
895b385
dfdc3b0
f5d4bd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import * as Sentry from '@sentry/browser'; | ||
import { browserProfilingIntegration } from '@sentry/browser'; | ||
|
||
window.Sentry = Sentry; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
integrations: [browserProfilingIntegration()], | ||
tracesSampleRate: 1, | ||
profilesSampleRate: 1, | ||
}); | ||
|
||
function fibonacci(n) { | ||
if (n <= 1) { | ||
return n; | ||
} | ||
return fibonacci(n - 1) + fibonacci(n - 2); | ||
} | ||
|
||
await Sentry.startSpanManual({ name: 'root-fibonacci-2', parentSpan: null, forceTransaction: true }, async span => { | ||
fibonacci(30); | ||
|
||
// Timeout to prevent flaky tests. Integration samples every 20ms, if function is too fast it might not get sampled | ||
await new Promise(resolve => setTimeout(resolve, 21)); | ||
span.end(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { expect } from '@playwright/test'; | ||
import type { Event, Profile } from '@sentry/core'; | ||
import { sentryTest } from '../../../utils/fixtures'; | ||
import { | ||
properEnvelopeRequestParser, | ||
shouldSkipTracingTest, | ||
waitForTransactionRequestOnUrl, | ||
} from '../../../utils/helpers'; | ||
|
||
sentryTest( | ||
'does not send profile envelope when document-policy is not set', | ||
async ({ page, getLocalTestUrl, browserName }) => { | ||
if (shouldSkipTracingTest() || browserName !== 'chromium') { | ||
// Profiling only works when tracing is enabled | ||
sentryTest.skip(); | ||
} | ||
|
||
const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
||
const req = await waitForTransactionRequestOnUrl(page, url); | ||
const transactionEvent = properEnvelopeRequestParser<Event>(req, 0); | ||
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1); | ||
|
||
expect(transactionEvent).toBeDefined(); | ||
expect(profileEvent).toBeUndefined(); | ||
}, | ||
); | ||
|
||
sentryTest('sends profile envelope in legacy mode', async ({ page, getLocalTestUrl, browserName }) => { | ||
if (shouldSkipTracingTest() || browserName !== 'chromium') { | ||
// Profiling only works when tracing is enabled | ||
sentryTest.skip(); | ||
} | ||
|
||
const url = await getLocalTestUrl({ testDir: __dirname, responseHeaders: { 'Document-Policy': 'js-profiling' } }); | ||
|
||
const req = await waitForTransactionRequestOnUrl(page, url); | ||
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1); | ||
expect(profileEvent).toBeDefined(); | ||
|
||
const profile = profileEvent.profile; | ||
expect(profileEvent.profile).toBeDefined(); | ||
|
||
expect(profile.samples).toBeDefined(); | ||
expect(profile.stacks).toBeDefined(); | ||
expect(profile.frames).toBeDefined(); | ||
expect(profile.thread_metadata).toBeDefined(); | ||
|
||
// Samples | ||
expect(profile.samples.length).toBeGreaterThanOrEqual(2); | ||
for (const sample of profile.samples) { | ||
expect(typeof sample.elapsed_since_start_ns).toBe('string'); | ||
expect(sample.elapsed_since_start_ns).toMatch(/^\d+$/); // Numeric string | ||
expect(parseInt(sample.elapsed_since_start_ns, 10)).toBeGreaterThanOrEqual(0); | ||
|
||
expect(typeof sample.stack_id).toBe('number'); | ||
expect(sample.stack_id).toBeGreaterThanOrEqual(0); | ||
expect(sample.thread_id).toBe('0'); // Should be main thread | ||
} | ||
|
||
// Stacks | ||
expect(profile.stacks.length).toBeGreaterThan(0); | ||
for (const stack of profile.stacks) { | ||
expect(Array.isArray(stack)).toBe(true); | ||
for (const frameIndex of stack) { | ||
expect(typeof frameIndex).toBe('number'); | ||
expect(frameIndex).toBeGreaterThanOrEqual(0); | ||
expect(frameIndex).toBeLessThan(profile.frames.length); | ||
} | ||
} | ||
|
||
// Frames | ||
expect(profile.frames.length).toBeGreaterThan(0); | ||
for (const frame of profile.frames) { | ||
expect(frame).toHaveProperty('function'); | ||
expect(frame).toHaveProperty('abs_path'); | ||
expect(frame).toHaveProperty('lineno'); | ||
expect(frame).toHaveProperty('colno'); | ||
|
||
expect(typeof frame.function).toBe('string'); | ||
expect(typeof frame.abs_path).toBe('string'); | ||
expect(typeof frame.lineno).toBe('number'); | ||
expect(typeof frame.colno).toBe('number'); | ||
} | ||
|
||
const functionNames = profile.frames.map(frame => frame.function).filter(name => name !== ''); | ||
|
||
if ((process.env.PW_BUNDLE || '').endsWith('min')) { | ||
// Function names are minified in minified bundles | ||
expect(functionNames.length).toBeGreaterThan(0); | ||
expect((functionNames as string[]).every(name => name?.length > 0)).toBe(true); // Just make sure they're not empty strings | ||
} else { | ||
expect(functionNames).toEqual( | ||
expect.arrayContaining([ | ||
'_startRootSpan', | ||
'withScope', | ||
'createChildOrRootSpan', | ||
'startSpanManual', | ||
'startProfileForSpan', | ||
'startJSSelfProfile', | ||
]), | ||
); | ||
} | ||
|
||
expect(profile.thread_metadata).toHaveProperty('0'); | ||
expect(profile.thread_metadata['0']).toHaveProperty('name'); | ||
expect(profile.thread_metadata['0'].name).toBe('main'); | ||
|
||
// Test that profile duration makes sense (should be > 20ms based on test setup) | ||
const startTime = parseInt(profile.samples[0].elapsed_since_start_ns, 10); | ||
const endTime = parseInt(profile.samples[profile.samples.length - 1].elapsed_since_start_ns, 10); | ||
const durationNs = endTime - startTime; | ||
const durationMs = durationNs / 1_000_000; // Convert ns to ms | ||
|
||
// Should be at least 20ms based on our setTimeout(21) in the test | ||
expect(durationMs).toBeGreaterThan(20); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ export const envelopeParser = (request: Request | null): unknown[] => { | |
}); | ||
}; | ||
|
||
// Rather use the `properEnvelopeRequestParser`, as the `envelopeParser` does not follow the envelope spec. | ||
export const envelopeRequestParser = <T = SentryEvent>(request: Request | null, envelopeIndex = 2): T => { | ||
return envelopeParser(request)[envelopeIndex] as T; | ||
}; | ||
|
@@ -79,8 +80,12 @@ function getEventAndTraceHeader(envelope: EventEnvelope): EventAndTraceHeader { | |
return [event, trace]; | ||
} | ||
|
||
export const properEnvelopeRequestParser = <T = SentryEvent>(request: Request | null, envelopeIndex = 1): T => { | ||
return properEnvelopeParser(request)[0]?.[envelopeIndex] as T; | ||
export const properEnvelopeRequestParser = <T = SentryEvent>( | ||
request: Request | null, | ||
envelopeItemIndex: number, | ||
envelopeIndex = 1, // 1 is usually the payload of the envelope (0 is the header) | ||
): T => { | ||
return properEnvelopeParser(request)[envelopeItemIndex]?.[envelopeIndex] as T; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Envelope Request Parser Parameter ShiftThe |
||
}; | ||
|
||
export const properFullEnvelopeRequestParser = <T extends Envelope>(request: Request | null): T => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React from 'react'; | ||
import { Link } from 'react-router-dom'; | ||
|
||
export const anotherNestedRoutes = [ | ||
{ | ||
path: 'sub', | ||
children: [ | ||
{ | ||
index: true, | ||
element: ( | ||
<div id="another-lazy-route"> | ||
Another Lazy Route | ||
<Link to="/lazy/inner/999/888/777" id="navigate-to-inner"> | ||
Navigate to Inner Lazy Route | ||
</Link> | ||
</div> | ||
), | ||
}, | ||
{ | ||
path: ':id', | ||
children: [ | ||
{ | ||
index: true, | ||
element: <div id="another-lazy-route-with-id">Another Lazy Route with ID</div>, | ||
}, | ||
{ | ||
path: ':subId', | ||
element: ( | ||
<div id="another-lazy-route-deep"> | ||
Another Deep Lazy Route | ||
<Link to="/lazy/inner/111/222/333" id="navigate-to-inner-from-deep"> | ||
Navigate to Inner from Deep | ||
</Link> | ||
</div> | ||
), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: leftover
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.