Skip to content

Commit e4f33fb

Browse files
committed
fix(deno): add teardown to denoRuntimeMetricsIntegration, fix unit tests
1 parent 3ac07de commit e4f33fb

2 files changed

Lines changed: 98 additions & 164 deletions

File tree

packages/deno/src/integrations/denoRuntimeMetrics.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,12 @@ export const denoRuntimeMetricsIntegration = defineIntegration((options: DenoRun
102102
}
103103
intervalId = _INTERNAL_safeUnref(setInterval(collectMetrics, collectionIntervalMs));
104104
},
105+
106+
teardown(): void {
107+
if (intervalId) {
108+
clearInterval(intervalId);
109+
intervalId = undefined;
110+
}
111+
},
105112
};
106113
});
Lines changed: 91 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,193 +1,120 @@
11
// <reference lib="deno.ns" />
22

3+
import type { Envelope } from '@sentry/core';
4+
import { createStackParser, forEachEnvelopeItem, nodeStackLineParser } from '@sentry/core';
35
import { assertEquals, assertNotEquals } from 'https://deno.land/std@0.212.0/assert/mod.ts';
4-
import { spy, stub } from 'https://deno.land/std@0.212.0/testing/mock.ts';
5-
import { FakeTime } from 'https://deno.land/std@0.212.0/testing/time.ts';
6-
import { denoRuntimeMetricsIntegration, metrics } from '../build/esm/index.js';
6+
import {
7+
DenoClient,
8+
denoRuntimeMetricsIntegration,
9+
getCurrentScope,
10+
getDefaultIntegrations,
11+
} from '../build/esm/index.js';
12+
import { makeTestTransport } from './transport.ts';
713

8-
const MOCK_MEMORY: Deno.MemoryUsage = {
9-
rss: 50_000_000,
10-
heapTotal: 30_000_000,
11-
heapUsed: 20_000_000,
12-
external: 1_000_000,
13-
};
14+
const DSN = 'https://233a45e5efe34c47a3536797ce15dafa@nothing.here/5650507';
15+
16+
function delay(ms: number): Promise<void> {
17+
return new Promise(resolve => setTimeout(resolve, ms));
18+
}
1419

1520
// deno-lint-ignore no-explicit-any
16-
type AnyCall = { args: any[] };
21+
type MetricItem = { name: string; type: string; value: number; unit?: string; attributes?: Record<string, any> };
22+
23+
async function collectMetrics(
24+
integrationOptions: Parameters<typeof denoRuntimeMetricsIntegration>[0] = {},
25+
): Promise<MetricItem[]> {
26+
const envelopes: Envelope[] = [];
27+
28+
// Hold a reference so we can call teardown() to stop the interval before the test ends.
29+
const metricsIntegration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 100, ...integrationOptions });
30+
31+
const client = new DenoClient({
32+
dsn: DSN,
33+
integrations: [...getDefaultIntegrations({}), metricsIntegration],
34+
stackParser: createStackParser(nodeStackLineParser()),
35+
transport: makeTestTransport(envelope => {
36+
envelopes.push(envelope);
37+
}),
38+
});
39+
40+
client.init();
41+
getCurrentScope().setClient(client);
42+
43+
await delay(250);
44+
await client.flush(2000);
45+
46+
// Stop the collection interval so Deno's leak detector doesn't flag it.
47+
metricsIntegration.teardown?.();
48+
49+
const items: MetricItem[] = [];
50+
for (const envelope of envelopes) {
51+
forEachEnvelopeItem(envelope, item => {
52+
const [headers, body] = item;
53+
if (headers.type === 'trace_metric') {
54+
// deno-lint-ignore no-explicit-any
55+
items.push(...(body as any).items);
56+
}
57+
});
58+
}
59+
60+
return items;
61+
}
1762

1863
Deno.test('denoRuntimeMetricsIntegration has the correct name', () => {
1964
const integration = denoRuntimeMetricsIntegration();
2065
assertEquals(integration.name, 'DenoRuntimeMetrics');
2166
});
2267

23-
Deno.test('starts a collection interval', () => {
24-
using time = new FakeTime();
25-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
26-
const gaugeSpy = spy(metrics, 'gauge');
68+
Deno.test('emits default memory metrics with correct shape', async () => {
69+
const items = await collectMetrics();
70+
const names = items.map(i => i.name);
2771

28-
try {
29-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
30-
integration.setup!({} as never);
72+
assertEquals(names.includes('deno.runtime.mem.rss'), true);
73+
assertEquals(names.includes('deno.runtime.mem.heap_used'), true);
74+
assertEquals(names.includes('deno.runtime.mem.heap_total'), true);
3175

32-
assertEquals(gaugeSpy.calls.length, 0);
33-
time.tick(1_000);
34-
assertNotEquals(gaugeSpy.calls.length, 0);
35-
} finally {
36-
gaugeSpy.restore();
37-
}
76+
const rss = items.find(i => i.name === 'deno.runtime.mem.rss');
77+
assertEquals(rss?.type, 'gauge');
78+
assertEquals(rss?.unit, 'byte');
79+
assertEquals(typeof rss?.value, 'number');
3880
});
3981

40-
Deno.test('emits default memory metrics', () => {
41-
using time = new FakeTime();
42-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
43-
const gaugeSpy = spy(metrics, 'gauge');
44-
45-
try {
46-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
47-
integration.setup!({} as never);
48-
time.tick(1_000);
49-
50-
const names = (gaugeSpy.calls as AnyCall[]).map(c => c.args[0]);
51-
assertEquals(names.includes('deno.runtime.mem.rss'), true);
52-
assertEquals(names.includes('deno.runtime.mem.heap_used'), true);
53-
assertEquals(names.includes('deno.runtime.mem.heap_total'), true);
54-
} finally {
55-
gaugeSpy.restore();
56-
}
57-
});
82+
Deno.test('emits uptime counter', async () => {
83+
const items = await collectMetrics();
84+
const uptime = items.find(i => i.name === 'deno.runtime.process.uptime');
5885

59-
Deno.test('emits correct memory values', () => {
60-
using time = new FakeTime();
61-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
62-
const gaugeSpy = spy(metrics, 'gauge');
63-
64-
try {
65-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
66-
integration.setup!({} as never);
67-
time.tick(1_000);
68-
69-
const calls = gaugeSpy.calls as AnyCall[];
70-
const rssCall = calls.find(c => c.args[0] === 'deno.runtime.mem.rss');
71-
const heapUsedCall = calls.find(c => c.args[0] === 'deno.runtime.mem.heap_used');
72-
const heapTotalCall = calls.find(c => c.args[0] === 'deno.runtime.mem.heap_total');
73-
74-
assertEquals(rssCall?.args[1], 50_000_000);
75-
assertEquals(heapUsedCall?.args[1], 20_000_000);
76-
assertEquals(heapTotalCall?.args[1], 30_000_000);
77-
} finally {
78-
gaugeSpy.restore();
79-
}
86+
assertNotEquals(uptime, undefined);
87+
assertEquals(uptime?.type, 'counter');
88+
assertEquals(uptime?.unit, 'second');
8089
});
8190

82-
Deno.test('does not emit mem.external by default', () => {
83-
using time = new FakeTime();
84-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
85-
const gaugeSpy = spy(metrics, 'gauge');
86-
87-
try {
88-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
89-
integration.setup!({} as never);
90-
time.tick(1_000);
91-
92-
const names = (gaugeSpy.calls as AnyCall[]).map(c => c.args[0]);
93-
assertEquals(names.includes('deno.runtime.mem.external'), false);
94-
} finally {
95-
gaugeSpy.restore();
96-
}
91+
Deno.test('does not emit mem.external by default', async () => {
92+
const items = await collectMetrics();
93+
const names = items.map(i => i.name);
94+
assertEquals(names.includes('deno.runtime.mem.external'), false);
9795
});
9896

99-
Deno.test('emits mem.external when opted in', () => {
100-
using time = new FakeTime();
101-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
102-
const gaugeSpy = spy(metrics, 'gauge');
97+
Deno.test('emits mem.external when opted in', async () => {
98+
const items = await collectMetrics({ collect: { memExternal: true } });
99+
const external = items.find(i => i.name === 'deno.runtime.mem.external');
103100

104-
try {
105-
const integration = denoRuntimeMetricsIntegration({
106-
collectionIntervalMs: 1_000,
107-
collect: { memExternal: true },
108-
});
109-
integration.setup!({} as never);
110-
time.tick(1_000);
111-
112-
const calls = gaugeSpy.calls as AnyCall[];
113-
const externalCall = calls.find(c => c.args[0] === 'deno.runtime.mem.external');
114-
assertEquals(externalCall?.args[1], 1_000_000);
115-
} finally {
116-
gaugeSpy.restore();
117-
}
101+
assertNotEquals(external, undefined);
102+
assertEquals(external?.type, 'gauge');
103+
assertEquals(external?.unit, 'byte');
118104
});
119105

120-
Deno.test('emits uptime counter', () => {
121-
using time = new FakeTime();
122-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
123-
const countSpy = spy(metrics, 'count');
124-
125-
try {
126-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
127-
integration.setup!({} as never);
128-
time.tick(1_000);
106+
Deno.test('respects opt-out: skips uptime when disabled', async () => {
107+
const items = await collectMetrics({ collect: { uptime: false } });
108+
const names = items.map(i => i.name);
129109

130-
const uptimeCall = (countSpy.calls as AnyCall[]).find(c => c.args[0] === 'deno.runtime.process.uptime');
131-
assertNotEquals(uptimeCall, undefined);
132-
} finally {
133-
countSpy.restore();
134-
}
135-
});
136-
137-
Deno.test('respects opt-out: skips mem.rss when memRss is false', () => {
138-
using time = new FakeTime();
139-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
140-
const gaugeSpy = spy(metrics, 'gauge');
141-
142-
try {
143-
const integration = denoRuntimeMetricsIntegration({
144-
collectionIntervalMs: 1_000,
145-
collect: { memRss: false },
146-
});
147-
integration.setup!({} as never);
148-
time.tick(1_000);
149-
150-
const names = (gaugeSpy.calls as AnyCall[]).map(c => c.args[0]);
151-
assertEquals(names.includes('deno.runtime.mem.rss'), false);
152-
} finally {
153-
gaugeSpy.restore();
154-
}
110+
assertEquals(names.includes('deno.runtime.mem.rss'), true);
111+
assertEquals(names.includes('deno.runtime.process.uptime'), false);
155112
});
156113

157-
Deno.test('skips uptime when uptime is false', () => {
158-
using time = new FakeTime();
159-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
160-
const countSpy = spy(metrics, 'count');
114+
Deno.test('attaches correct sentry.origin attribute', async () => {
115+
const items = await collectMetrics();
116+
const rss = items.find(i => i.name === 'deno.runtime.mem.rss');
161117

162-
try {
163-
const integration = denoRuntimeMetricsIntegration({
164-
collectionIntervalMs: 1_000,
165-
collect: { uptime: false },
166-
});
167-
integration.setup!({} as never);
168-
time.tick(1_000);
169-
170-
const uptimeCall = (countSpy.calls as AnyCall[]).find(c => c.args[0] === 'deno.runtime.process.uptime');
171-
assertEquals(uptimeCall, undefined);
172-
} finally {
173-
countSpy.restore();
174-
}
175-
});
176-
177-
Deno.test('attaches correct sentry.origin attribute', () => {
178-
using time = new FakeTime();
179-
using _memStub = stub(Deno, 'memoryUsage', () => MOCK_MEMORY);
180-
const gaugeSpy = spy(metrics, 'gauge');
181-
182-
try {
183-
const integration = denoRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 });
184-
integration.setup!({} as never);
185-
time.tick(1_000);
186-
187-
const calls = gaugeSpy.calls as AnyCall[];
188-
const rssCall = calls.find(c => c.args[0] === 'deno.runtime.mem.rss');
189-
assertEquals(rssCall?.args[2]?.attributes?.['sentry.origin'], 'auto.deno.runtime_metrics');
190-
} finally {
191-
gaugeSpy.restore();
192-
}
118+
// Attributes in the serialized envelope are { type, value } objects.
119+
assertEquals(rss?.attributes?.['sentry.origin']?.value, 'auto.deno.runtime_metrics');
193120
});

0 commit comments

Comments
 (0)