Skip to content

Commit f4016c9

Browse files
committed
use min value for all runtime metrics
1 parent b29d628 commit f4016c9

File tree

8 files changed

+95
-8
lines changed

8 files changed

+95
-8
lines changed

packages/bun/src/integrations/bunRuntimeMetrics.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { performance } from 'perf_hooks';
22
import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics } from '@sentry/core';
3-
import type { NodeRuntimeMetricsOptions } from '@sentry/node';
3+
import { _INTERNAL_normalizeCollectionInterval, type NodeRuntimeMetricsOptions } from '@sentry/node';
44

55
const INTEGRATION_NAME = 'BunRuntimeMetrics';
66
const DEFAULT_INTERVAL_MS = 30_000;
@@ -44,7 +44,9 @@ export interface BunRuntimeMetricsOptions {
4444
collect?: BunCollectOptions;
4545
/**
4646
* How often to collect metrics, in milliseconds.
47+
* Minimum allowed value is 1000ms.
4748
* @default 30000
49+
* @minimum 1000
4850
*/
4951
collectionIntervalMs?: number;
5052
}
@@ -62,7 +64,10 @@ export interface BunRuntimeMetricsOptions {
6264
* ```
6365
*/
6466
export const bunRuntimeMetricsIntegration = defineIntegration((options: BunRuntimeMetricsOptions = {}) => {
65-
const collectionIntervalMs = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS;
67+
const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval(
68+
options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS,
69+
INTEGRATION_NAME,
70+
);
6671
const collect = {
6772
// Default on
6873
cpuUtilization: true,

packages/bun/test/integrations/bunRuntimeMetrics.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,31 @@ describe('bunRuntimeMetricsIntegration', () => {
212212
expect(countSpy).not.toHaveBeenCalledWith('bun.runtime.process.uptime', expect.anything(), expect.anything());
213213
});
214214
});
215+
216+
describe('collectionIntervalMs minimum', () => {
217+
it('enforces minimum of 1000ms and warns', () => {
218+
const warnSpy = spyOn(globalThis.console, 'warn').mockImplementation(() => {});
219+
220+
const integration = bunRuntimeMetricsIntegration({ collectionIntervalMs: 100 });
221+
integration.setup();
222+
223+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs'));
224+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('1000'));
225+
226+
// Should fire at minimum 1000ms, not at 100ms
227+
jest.advanceTimersByTime(100);
228+
expect(gaugeSpy).not.toHaveBeenCalled();
229+
230+
jest.advanceTimersByTime(900);
231+
expect(gaugeSpy).toHaveBeenCalled();
232+
});
233+
234+
it('falls back to minimum when NaN', () => {
235+
const warnSpy = spyOn(globalThis.console, 'warn').mockImplementation(() => {});
236+
237+
bunRuntimeMetricsIntegration({ collectionIntervalMs: NaN });
238+
239+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs'));
240+
});
241+
});
215242
});

packages/deno/src/integrations/denoRuntimeMetrics.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface DenoRuntimeMetricsOptions {
2828
};
2929
/**
3030
* How often to collect metrics, in milliseconds.
31-
* Values below 1000ms are clamped to 1000ms.
31+
* Minimum allowed value is 1000ms.
3232
* @default 30000
3333
* @minimum 1000
3434
*/
@@ -52,7 +52,7 @@ export const denoRuntimeMetricsIntegration = defineIntegration((options: DenoRun
5252
if (!Number.isFinite(rawInterval) || rawInterval < MIN_INTERVAL_MS) {
5353
// eslint-disable-next-line no-console
5454
console.warn(
55-
`[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_INTERVAL_MS}ms. Clamping to ${MIN_INTERVAL_MS}ms.`,
55+
`[Sentry] denoRuntimeMetricsIntegration: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_INTERVAL_MS}ms. Using minimum of ${MIN_INTERVAL_MS}ms.`,
5656
);
5757
}
5858
const collectionIntervalMs = Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_INTERVAL_MS) : MIN_INTERVAL_MS;

packages/deno/test/deno-runtime-metrics.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Deno.test('attaches correct sentry.origin attribute', async () => {
119119
assertEquals(rss?.attributes?.['sentry.origin']?.value, 'auto.deno.runtime_metrics');
120120
});
121121

122-
Deno.test('warns and clamps collectionIntervalMs below 1000ms', () => {
122+
Deno.test('warns and enforces minimum collectionIntervalMs', () => {
123123
const warnings: string[] = [];
124124
const originalWarn = globalThis.console.warn;
125125
globalThis.console.warn = (msg: string) => warnings.push(msg);
@@ -135,7 +135,7 @@ Deno.test('warns and clamps collectionIntervalMs below 1000ms', () => {
135135
assertStringIncludes(warnings[0]!, '1000');
136136
});
137137

138-
Deno.test('warns and clamps collectionIntervalMs when NaN', () => {
138+
Deno.test('warns and falls back to minimum when collectionIntervalMs is NaN', () => {
139139
const warnings: string[] = [];
140140
const originalWarn = globalThis.console.warn;
141141
globalThis.console.warn = (msg: string) => warnings.push(msg);

packages/node-core/src/common-exports.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import * as logger from './logs/exports';
1212

1313
// Node-core integrations (not OTel-dependent)
1414
export { nodeContextIntegration } from './integrations/context';
15-
export { nodeRuntimeMetricsIntegration, type NodeRuntimeMetricsOptions } from './integrations/nodeRuntimeMetrics';
15+
export {
16+
nodeRuntimeMetricsIntegration,
17+
type NodeRuntimeMetricsOptions,
18+
_INTERNAL_normalizeCollectionInterval,
19+
} from './integrations/nodeRuntimeMetrics';
1620
export { contextLinesIntegration } from './integrations/contextlines';
1721
export { localVariablesIntegration } from './integrations/local-variables';
1822
export { modulesIntegration } from './integrations/modules';

packages/node-core/src/integrations/nodeRuntimeMetrics.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,24 @@ import { _INTERNAL_safeDateNow, _INTERNAL_safeUnref, defineIntegration, metrics
33

44
const INTEGRATION_NAME = 'NodeRuntimeMetrics';
55
const DEFAULT_INTERVAL_MS = 30_000;
6+
const MIN_COLLECTION_INTERVAL_MS = 1_000;
67
const EVENT_LOOP_DELAY_RESOLUTION_MS = 10;
78

9+
/**
10+
* Normalizes a `collectionIntervalMs` value, enforcing a minimum of 1000ms.
11+
* Warns if the value is below the minimum or non-finite (e.g. NaN).
12+
* @internal
13+
*/
14+
export function _INTERNAL_normalizeCollectionInterval(rawInterval: number, integrationName: string): number {
15+
if (!Number.isFinite(rawInterval) || rawInterval < MIN_COLLECTION_INTERVAL_MS) {
16+
// eslint-disable-next-line no-console
17+
console.warn(
18+
`[Sentry] ${integrationName}: collectionIntervalMs (${rawInterval}) is below the minimum of ${MIN_COLLECTION_INTERVAL_MS}ms. Using minimum of ${MIN_COLLECTION_INTERVAL_MS}ms.`,
19+
);
20+
}
21+
return Number.isFinite(rawInterval) ? Math.max(rawInterval, MIN_COLLECTION_INTERVAL_MS) : MIN_COLLECTION_INTERVAL_MS;
22+
}
23+
824
export interface NodeRuntimeMetricsOptions {
925
/**
1026
* Which metrics to collect.
@@ -44,7 +60,9 @@ export interface NodeRuntimeMetricsOptions {
4460
};
4561
/**
4662
* How often to collect metrics, in milliseconds.
63+
* Minimum allowed value is 1000ms.
4764
* @default 30000
65+
* @minimum 1000
4866
*/
4967
collectionIntervalMs?: number;
5068
}
@@ -62,7 +80,10 @@ export interface NodeRuntimeMetricsOptions {
6280
* ```
6381
*/
6482
export const nodeRuntimeMetricsIntegration = defineIntegration((options: NodeRuntimeMetricsOptions = {}) => {
65-
const collectionIntervalMs = options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS;
83+
const collectionIntervalMs = _INTERNAL_normalizeCollectionInterval(
84+
options.collectionIntervalMs ?? DEFAULT_INTERVAL_MS,
85+
INTEGRATION_NAME,
86+
);
6687
const collect = {
6788
// Default on
6889
cpuUtilization: true,

packages/node-core/test/integrations/nodeRuntimeMetrics.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,5 +329,34 @@ describe('nodeRuntimeMetricsIntegration', () => {
329329

330330
expect(countSpy).not.toHaveBeenCalledWith('node.runtime.process.uptime', expect.anything(), expect.anything());
331331
});
332+
333+
it('enforces minimum collectionIntervalMs of 1000ms and warns', () => {
334+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
335+
336+
const integration = nodeRuntimeMetricsIntegration({ collectionIntervalMs: 100 });
337+
integration.setup();
338+
339+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs'));
340+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('1000'));
341+
342+
// Should fire at the minimum 1000ms, not at 100ms
343+
vi.advanceTimersByTime(100);
344+
expect(gaugeSpy).not.toHaveBeenCalled();
345+
346+
vi.advanceTimersByTime(900);
347+
expect(gaugeSpy).toHaveBeenCalled();
348+
349+
warnSpy.mockRestore();
350+
});
351+
352+
it('falls back to minimum when collectionIntervalMs is NaN', () => {
353+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
354+
355+
nodeRuntimeMetricsIntegration({ collectionIntervalMs: NaN });
356+
357+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('collectionIntervalMs'));
358+
359+
warnSpy.mockRestore();
360+
});
332361
});
333362
});

packages/node/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,5 @@ export {
203203
cron,
204204
NODE_VERSION,
205205
validateOpenTelemetrySetup,
206+
_INTERNAL_normalizeCollectionInterval,
206207
} from '@sentry/node-core';

0 commit comments

Comments
 (0)