@@ -8,9 +8,9 @@ import type { TemplateSegment, CompiledTemplate } from './template'
88import type { CaptureOptions } from './capture'
99
1010// Sampling rate limits
11- const MAX_SNAPSHOTS_PER_SECOND_GLOBALLY = 25
12- const MAX_SNAPSHOTS_PER_SECOND_PER_PROBE = 1
13- const MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE = 5000
11+ const DEFAULT_MAX_SNAPSHOTS_PER_SECOND_GLOBALLY = 25
12+ const DEFAULT_MAX_SNAPSHOTS_PER_SECOND_PER_PROBE = 1
13+ const DEFAULT_MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE = 5000
1414
1515// Global snapshot rate limiting
1616let globalSnapshotSamplingRateWindowStart = 0
@@ -32,6 +32,12 @@ export interface ProbeSampling {
3232 snapshotsPerSecond ?: number
3333}
3434
35+ export interface ProbeBudgetConfiguration {
36+ maxSnapshotsPerSecondGlobally ?: number
37+ maxSnapshotsPerSecondPerProbe ?: number
38+ maxNonSnapshotsPerSecondPerProbe ?: number
39+ }
40+
3541export interface Probe {
3642 id : string
3743 version : number
@@ -70,6 +76,32 @@ const probeIdToFunctionId: Record<string, string> = {
7076 // @ts -expect-error - Pre-populate with a placeholder key to help V8 optimize property lookups.
7177 __placeholder__ : undefined ,
7278}
79+ let currentProbeBudgetConfiguration : Required < ProbeBudgetConfiguration > = {
80+ maxSnapshotsPerSecondGlobally : DEFAULT_MAX_SNAPSHOTS_PER_SECOND_GLOBALLY ,
81+ maxSnapshotsPerSecondPerProbe : DEFAULT_MAX_SNAPSHOTS_PER_SECOND_PER_PROBE ,
82+ maxNonSnapshotsPerSecondPerProbe : DEFAULT_MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE ,
83+ }
84+
85+ export function setProbeBudgetConfiguration ( configuration : ProbeBudgetConfiguration = { } ) : void {
86+ currentProbeBudgetConfiguration = {
87+ maxSnapshotsPerSecondGlobally : normalizeProbeBudgetRate (
88+ configuration . maxSnapshotsPerSecondGlobally ,
89+ DEFAULT_MAX_SNAPSHOTS_PER_SECOND_GLOBALLY
90+ ) ,
91+ maxSnapshotsPerSecondPerProbe : normalizeProbeBudgetRate (
92+ configuration . maxSnapshotsPerSecondPerProbe ,
93+ DEFAULT_MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
94+ ) ,
95+ maxNonSnapshotsPerSecondPerProbe : normalizeProbeBudgetRate (
96+ configuration . maxNonSnapshotsPerSecondPerProbe ,
97+ DEFAULT_MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE
98+ ) ,
99+ }
100+ }
101+
102+ export function resetProbeBudgetConfiguration ( ) : void {
103+ setProbeBudgetConfiguration ( )
104+ }
73105
74106/**
75107 * Add a probe to the registry
@@ -136,6 +168,9 @@ export function removeProbe(id: string): void {
136168 probe . condition . clearCache ( )
137169 }
138170 probes . splice ( i , 1 )
171+ // TODO: Gracefully drain in-flight entries instead of clearing them immediately.
172+ // Deleting a probe can currently race with return/throw handling, whether removal
173+ // comes from delivery updates or budget-based auto-unregistering.
139174 clearActiveEntries ( id )
140175 break
141176 }
@@ -199,7 +234,7 @@ export function checkGlobalSnapshotBudget(now: number, captureSnapshot: boolean)
199234 }
200235
201236 // Check if we've exceeded the global limit
202- if ( snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY ) {
237+ if ( snapshotsSampledWithinTheLastSecond >= currentProbeBudgetConfiguration . maxSnapshotsPerSecondGlobally ) {
203238 return false
204239 }
205240
@@ -266,7 +301,13 @@ export function initializeProbe(probe: Probe): asserts probe is InitializedProbe
266301 // Optimize for fast calculations when probe is hit - calculate sampling budget
267302 const snapshotsPerSecond =
268303 probe . sampling ?. snapshotsPerSecond ??
269- ( probe . captureSnapshot ? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE : MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE )
304+ ( probe . captureSnapshot
305+ ? currentProbeBudgetConfiguration . maxSnapshotsPerSecondPerProbe
306+ : currentProbeBudgetConfiguration . maxNonSnapshotsPerSecondPerProbe )
270307 ; ( probe as InitializedProbe ) . msBetweenSampling = ( 1 / snapshotsPerSecond ) * 1000 // Convert to milliseconds
271308 ; ( probe as InitializedProbe ) . lastCaptureMs = - Infinity // Initialize to -Infinity to allow first call
272309}
310+
311+ function normalizeProbeBudgetRate ( rate : number | undefined , defaultRate : number ) : number {
312+ return typeof rate === 'number' && Number . isFinite ( rate ) && rate > 0 ? rate : defaultRate
313+ }
0 commit comments