Skip to content

Commit fa30008

Browse files
jchrostek-ddclaude
andcommitted
refactor(integration-tests): rename telemetry types and simplify metrics
- Rename DatadogTelemetry -> InvocationTracesLogs (per-invocation data) - Rename RuntimeTelemetry -> DatadogTelemetry (aggregated telemetry) - Remove redundant getMetricPoints wrapper - Remove metricsApiAvailable graceful degradation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 82d8085 commit fa30008

File tree

6 files changed

+65
-146
lines changed

6 files changed

+65
-146
lines changed

integration-tests/tests/lmi.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { invokeAndCollectTelemetry, FunctionConfig } from './utils/default';
2-
import { RuntimeTelemetry } from './utils/datadog';
2+
import { DatadogTelemetry } from './utils/datadog';
33
import { getIdentifier } from '../config';
44

55
const runtimes = ['node', 'python', 'java', 'dotnet'] as const;
@@ -9,7 +9,7 @@ const identifier = getIdentifier();
99
const stackName = `integ-${identifier}-lmi`;
1010

1111
describe('LMI Integration Tests', () => {
12-
let telemetry: Record<string, RuntimeTelemetry>;
12+
let telemetry: Record<string, DatadogTelemetry>;
1313

1414
beforeAll(async () => {
1515
const functions: FunctionConfig[] = runtimes.map(runtime => ({

integration-tests/tests/on-demand.test.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { invokeAndCollectTelemetry, FunctionConfig } from './utils/default';
2-
import { RuntimeTelemetry, MetricPoint, ENHANCED_METRICS_CONFIG, isMetricsApiAvailable } from './utils/datadog';
2+
import { DatadogTelemetry, MetricPoint, ENHANCED_METRICS_CONFIG } from './utils/datadog';
33
import { forceColdStart } from './utils/lambda';
44
import { getIdentifier } from '../config';
55

@@ -10,7 +10,7 @@ const identifier = getIdentifier();
1010
const stackName = `integ-${identifier}-on-demand`;
1111

1212
describe('On-Demand Integration Tests', () => {
13-
let telemetry: Record<string, RuntimeTelemetry>;
13+
let telemetry: Record<string, DatadogTelemetry>;
1414

1515
beforeAll(async () => {
1616
const functions: FunctionConfig[] = runtimes.map(runtime => ({
@@ -23,7 +23,7 @@ describe('On-Demand Integration Tests', () => {
2323

2424
// Add 5s delay between invocations to ensure warm container is reused
2525
// Required because there is post-runtime processing with 'end' flush strategy
26-
// invokeAndCollectTelemetry now returns RuntimeTelemetry with metrics included
26+
// invokeAndCollectTelemetry now returns DatadogTelemetry with metrics included
2727
telemetry = await invokeAndCollectTelemetry(functions, 2, 1, 5000);
2828

2929
console.log('All invocations and data fetching completed');
@@ -155,15 +155,6 @@ describe('On-Demand Integration Tests', () => {
155155
});
156156

157157
describe('duration metrics', () => {
158-
// Helper to check if metrics API is available and skip if not
159-
const skipIfNoMetricsApi = () => {
160-
if (!isMetricsApiAvailable()) {
161-
console.log('⚠️ Skipping metrics test - API unavailable (missing timeseries_query scope)');
162-
return true;
163-
}
164-
return false;
165-
};
166-
167158
// Helper to get latest value from points
168159
const getLatestValue = (points: MetricPoint[]) =>
169160
points.length > 0 ? points[points.length - 1].value : null;
@@ -175,7 +166,6 @@ describe('On-Demand Integration Tests', () => {
175166

176167
describe.each(durationMetrics)('%s', (metricName) => {
177168
it('should be emitted', () => {
178-
if (skipIfNoMetricsApi()) return;
179169
const { duration } = getTelemetry().metrics;
180170
// Metrics may not be indexed in the query time window for all runtimes
181171
if (duration[metricName].length === 0) {
@@ -186,7 +176,6 @@ describe('On-Demand Integration Tests', () => {
186176
});
187177

188178
it('should have a positive value', () => {
189-
if (skipIfNoMetricsApi()) return;
190179
const { duration } = getTelemetry().metrics;
191180
const value = getLatestValue(duration[metricName]);
192181
// Skip if no data available
@@ -201,14 +190,12 @@ describe('On-Demand Integration Tests', () => {
201190
// Count validation
202191
describe('count validation', () => {
203192
it('should emit runtime_duration for each invocation', () => {
204-
if (skipIfNoMetricsApi()) return;
205193
const { duration } = getTelemetry().metrics;
206194
// Enhanced metrics may aggregate points, so we check >= 1 instead of exact count
207195
expect(duration['runtime_duration'].length).toBeGreaterThanOrEqual(1);
208196
});
209197

210198
it('should emit init_duration only on cold start', () => {
211-
if (skipIfNoMetricsApi()) return;
212199
const { duration } = getTelemetry().metrics;
213200
// init_duration should exist for cold start (may be 0 or 1 depending on runtime/timing)
214201
// Some runtimes may not emit init_duration in all cases
@@ -220,7 +207,6 @@ describe('On-Demand Integration Tests', () => {
220207

221208
// Relationship tests
222209
it('duration and runtime_duration should be comparable', () => {
223-
if (skipIfNoMetricsApi()) return;
224210
const { duration } = getTelemetry().metrics;
225211
const durationValue = getLatestValue(duration['duration']);
226212
const runtimeValue = getLatestValue(duration['runtime_duration']);
@@ -238,7 +224,6 @@ describe('On-Demand Integration Tests', () => {
238224
});
239225

240226
it('post_runtime_duration should be reasonable', () => {
241-
if (skipIfNoMetricsApi()) return;
242227
const { duration } = getTelemetry().metrics;
243228
const value = getLatestValue(duration['post_runtime_duration']);
244229
// Skip if metric has no data

integration-tests/tests/otlp.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { invokeAndCollectTelemetry, FunctionConfig } from './utils/default';
2-
import { RuntimeTelemetry } from './utils/datadog';
2+
import { DatadogTelemetry } from './utils/datadog';
33
import { getIdentifier, DATADOG_INDEXING_WAIT_5_MIN_MS } from '../config';
44

55
const runtimes = ['node', 'python', 'java', 'dotnet'] as const;
@@ -9,7 +9,7 @@ const identifier = getIdentifier();
99
const stackName = `integ-${identifier}-otlp`;
1010

1111
describe('OTLP Integration Tests', () => {
12-
let telemetry: Record<string, RuntimeTelemetry>;
12+
let telemetry: Record<string, DatadogTelemetry>;
1313

1414
beforeAll(async () => {
1515
// Build function configs for all runtimes plus response validation

integration-tests/tests/snapstart.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { invokeAndCollectTelemetry, FunctionConfig } from './utils/default';
2-
import { RuntimeTelemetry } from './utils/datadog';
2+
import { DatadogTelemetry } from './utils/datadog';
33
import { publishVersion, waitForSnapStartReady } from './utils/lambda';
44
import { getIdentifier } from '../config';
55

@@ -10,7 +10,7 @@ const identifier = getIdentifier();
1010
const stackName = `integ-${identifier}-snapstart`;
1111

1212
describe('Snapstart Integration Tests', () => {
13-
let telemetry: Record<string, RuntimeTelemetry>;
13+
let telemetry: Record<string, DatadogTelemetry>;
1414

1515
beforeAll(async () => {
1616
// Publish new versions and wait for SnapStart optimization

integration-tests/tests/utils/datadog.ts

Lines changed: 47 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ const datadogClient: AxiosInstance = axios.create({
1818
});
1919

2020
export interface DatadogTelemetry {
21+
threads: InvocationTracesLogs[][]; // [thread][invocation]
22+
metrics: EnhancedMetrics;
23+
}
24+
25+
export interface InvocationTracesLogs {
2126
requestId: string;
2227
statusCode?: number;
2328
traces?: DatadogTrace[];
@@ -41,6 +46,27 @@ export interface DatadogLog {
4146
tags: string[];
4247
}
4348

49+
export const ENHANCED_METRICS_CONFIG = {
50+
duration: [
51+
'aws.lambda.enhanced.runtime_duration',
52+
'aws.lambda.enhanced.billed_duration',
53+
'aws.lambda.enhanced.duration',
54+
'aws.lambda.enhanced.post_runtime_duration',
55+
'aws.lambda.enhanced.init_duration',
56+
],
57+
} as const;
58+
59+
export type MetricCategory = keyof typeof ENHANCED_METRICS_CONFIG;
60+
61+
export type EnhancedMetrics = {
62+
[K in MetricCategory]: Record<string, MetricPoint[]>;
63+
};
64+
65+
export interface MetricPoint {
66+
timestamp: number;
67+
value: number;
68+
}
69+
4470
/**
4571
* Extracts the base service name from a function name by stripping any
4672
* version qualifier (:N) or alias qualifier (:alias)
@@ -53,7 +79,7 @@ function getServiceName(functionName: string): string {
5379
return functionName.substring(0, colonIndex);
5480
}
5581

56-
export async function getDatadogTelemetryByRequestId(functionName: string, requestId: string): Promise<DatadogTelemetry> {
82+
export async function getInvocationTracesLogsByRequestId(functionName: string, requestId: string): Promise<InvocationTracesLogs> {
5783
const serviceName = getServiceName(functionName);
5884
const traces = await getTraces(serviceName, requestId);
5985
const logs = await getLogs(serviceName, requestId);
@@ -220,53 +246,6 @@ export async function getLogs(
220246
}
221247
}
222248

223-
// ============================================================================
224-
// Enhanced Metrics
225-
// ============================================================================
226-
227-
/**
228-
* Configuration for which metrics to fetch.
229-
* Add new metrics here - no code changes needed.
230-
*/
231-
export const ENHANCED_METRICS_CONFIG = {
232-
duration: [
233-
'aws.lambda.enhanced.runtime_duration',
234-
'aws.lambda.enhanced.billed_duration',
235-
'aws.lambda.enhanced.duration',
236-
'aws.lambda.enhanced.post_runtime_duration',
237-
'aws.lambda.enhanced.init_duration',
238-
],
239-
// Future categories - just add metric names:
240-
// memory: [
241-
// 'aws.lambda.enhanced.max_memory_used',
242-
// 'aws.lambda.enhanced.memory_size',
243-
// ],
244-
} as const;
245-
246-
export type MetricCategory = keyof typeof ENHANCED_METRICS_CONFIG;
247-
248-
export interface MetricPoint {
249-
timestamp: number;
250-
value: number;
251-
}
252-
253-
/**
254-
* Wrapper combining per-invocation telemetry with aggregated metrics.
255-
* Threads are preserved for tests that use concurrency > 1.
256-
*/
257-
export interface RuntimeTelemetry {
258-
threads: DatadogTelemetry[][]; // [thread][invocation]
259-
metrics: EnhancedMetrics;
260-
}
261-
262-
/**
263-
* Enhanced metrics organized by category.
264-
* Each category maps metric names to their points (for count validation).
265-
*/
266-
export type EnhancedMetrics = {
267-
[K in MetricCategory]: Record<string, MetricPoint[]>;
268-
};
269-
270249
/**
271250
* Fetch all enhanced metrics for a function based on config
272251
*/
@@ -309,7 +288,7 @@ async function fetchMetricCategory(
309288
toTime: number
310289
): Promise<Record<string, MetricPoint[]>> {
311290
const promises = metricNames.map(async (metricName) => {
312-
const points = await getMetricPoints(metricName, functionName, fromTime, toTime);
291+
const points = await getMetrics(metricName, functionName, fromTime, toTime);
313292
// Use short name (last part after the last dot)
314293
const shortName = metricName.split('.').pop()!;
315294
return { shortName, points };
@@ -325,84 +304,39 @@ async function fetchMetricCategory(
325304
return metrics;
326305
}
327306

328-
// Track if metrics API is available (set once on first failure)
329-
let metricsApiAvailable: boolean | null = null;
330-
331307
/**
332308
* Query Datadog Metrics API v1 for a specific metric.
333309
* Requires the DD_API_KEY to have 'timeseries_query' scope.
334-
* Returns empty array if API is unavailable (permissions issue).
335310
*/
336311
async function getMetrics(
337312
metricName: string,
338313
functionName: string,
339314
fromTime: number,
340315
toTime: number
341316
): Promise<MetricPoint[]> {
342-
// Skip if we've already determined the API is unavailable
343-
if (metricsApiAvailable === false) {
344-
return [];
345-
}
317+
const functionNameLower = functionName.toLowerCase();
318+
const query = `avg:${metricName}{functionname:${functionNameLower}}`;
346319

347-
try {
348-
const functionNameLower = functionName.toLowerCase();
349-
const query = `avg:${metricName}{functionname:${functionNameLower}}`;
350-
351-
console.log(`Querying metrics: ${query}`);
352-
353-
const response = await datadogClient.get('/api/v1/query', {
354-
params: {
355-
query,
356-
from: Math.floor(fromTime / 1000),
357-
to: Math.floor(toTime / 1000),
358-
},
359-
});
320+
console.log(`Querying metrics: ${query}`);
360321

361-
metricsApiAvailable = true;
362-
363-
const series = response.data.series || [];
364-
console.log(`Found ${series.length} series for ${metricName}`);
322+
const response = await datadogClient.get('/api/v1/query', {
323+
params: {
324+
query,
325+
from: Math.floor(fromTime / 1000),
326+
to: Math.floor(toTime / 1000),
327+
},
328+
});
365329

366-
if (series.length === 0) {
367-
return [];
368-
}
330+
const series = response.data.series || [];
331+
console.log(`Found ${series.length} series for ${metricName}`);
369332

370-
// Return points from first series
371-
return (series[0].pointlist || []).map((p: [number, number]) => ({
372-
timestamp: p[0],
373-
value: p[1],
374-
}));
375-
} catch (error: any) {
376-
const errorData = error.response?.data;
377-
// Check if this is a permissions error
378-
if (errorData?.errors?.some((e: string) => e.includes('Forbidden') || e.includes('permission'))) {
379-
if (metricsApiAvailable === null) {
380-
console.warn('⚠️ Metrics API unavailable (missing timeseries_query scope). Metrics tests will be skipped.');
381-
console.warn(' To enable metrics tests, ensure DD_API_KEY has the timeseries_query scope.');
382-
}
383-
metricsApiAvailable = false;
384-
return [];
385-
}
386-
console.error('Error querying metrics:', errorData || error.message);
387-
throw error;
333+
if (series.length === 0) {
334+
return [];
388335
}
389-
}
390-
391-
/**
392-
* Check if metrics API is available
393-
*/
394-
export function isMetricsApiAvailable(): boolean {
395-
return metricsApiAvailable === true;
396-
}
397336

398-
/**
399-
* Get all metric points in time window
400-
*/
401-
async function getMetricPoints(
402-
metricName: string,
403-
functionName: string,
404-
fromTime: number,
405-
toTime: number
406-
): Promise<MetricPoint[]> {
407-
return getMetrics(metricName, functionName, fromTime, toTime);
337+
// Return points from first series
338+
return (series[0].pointlist || []).map((p: [number, number]) => ({
339+
timestamp: p[0],
340+
value: p[1],
341+
}));
408342
}

0 commit comments

Comments
 (0)