Skip to content

Commit bfefb5c

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 1338d85 commit bfefb5c

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
@@ -55,6 +55,11 @@ function formatDatadogError(error: unknown, query: string): string {
5555
}
5656

5757
export interface DatadogTelemetry {
58+
threads: InvocationTracesLogs[][]; // [thread][invocation]
59+
metrics: EnhancedMetrics;
60+
}
61+
62+
export interface InvocationTracesLogs {
5863
requestId: string;
5964
statusCode?: number;
6065
traces?: DatadogTrace[];
@@ -78,6 +83,27 @@ export interface DatadogLog {
7883
tags: string[];
7984
}
8085

86+
export const ENHANCED_METRICS_CONFIG = {
87+
duration: [
88+
'aws.lambda.enhanced.runtime_duration',
89+
'aws.lambda.enhanced.billed_duration',
90+
'aws.lambda.enhanced.duration',
91+
'aws.lambda.enhanced.post_runtime_duration',
92+
'aws.lambda.enhanced.init_duration',
93+
],
94+
} as const;
95+
96+
export type MetricCategory = keyof typeof ENHANCED_METRICS_CONFIG;
97+
98+
export type EnhancedMetrics = {
99+
[K in MetricCategory]: Record<string, MetricPoint[]>;
100+
};
101+
102+
export interface MetricPoint {
103+
timestamp: number;
104+
value: number;
105+
}
106+
81107
/**
82108
* Extracts the base service name from a function name by stripping any
83109
* version qualifier (:N) or alias qualifier (:alias)
@@ -90,7 +116,7 @@ function getServiceName(functionName: string): string {
90116
return functionName.substring(0, colonIndex);
91117
}
92118

93-
export async function getDatadogTelemetryByRequestId(functionName: string, requestId: string): Promise<DatadogTelemetry> {
119+
export async function getInvocationTracesLogsByRequestId(functionName: string, requestId: string): Promise<InvocationTracesLogs> {
94120
const serviceName = getServiceName(functionName);
95121
const traces = await getTraces(serviceName, requestId);
96122
const logs = await getLogs(serviceName, requestId);
@@ -256,53 +282,6 @@ export async function getLogs(
256282
}
257283
}
258284

259-
// ============================================================================
260-
// Enhanced Metrics
261-
// ============================================================================
262-
263-
/**
264-
* Configuration for which metrics to fetch.
265-
* Add new metrics here - no code changes needed.
266-
*/
267-
export const ENHANCED_METRICS_CONFIG = {
268-
duration: [
269-
'aws.lambda.enhanced.runtime_duration',
270-
'aws.lambda.enhanced.billed_duration',
271-
'aws.lambda.enhanced.duration',
272-
'aws.lambda.enhanced.post_runtime_duration',
273-
'aws.lambda.enhanced.init_duration',
274-
],
275-
// Future categories - just add metric names:
276-
// memory: [
277-
// 'aws.lambda.enhanced.max_memory_used',
278-
// 'aws.lambda.enhanced.memory_size',
279-
// ],
280-
} as const;
281-
282-
export type MetricCategory = keyof typeof ENHANCED_METRICS_CONFIG;
283-
284-
export interface MetricPoint {
285-
timestamp: number;
286-
value: number;
287-
}
288-
289-
/**
290-
* Wrapper combining per-invocation telemetry with aggregated metrics.
291-
* Threads are preserved for tests that use concurrency > 1.
292-
*/
293-
export interface RuntimeTelemetry {
294-
threads: DatadogTelemetry[][]; // [thread][invocation]
295-
metrics: EnhancedMetrics;
296-
}
297-
298-
/**
299-
* Enhanced metrics organized by category.
300-
* Each category maps metric names to their points (for count validation).
301-
*/
302-
export type EnhancedMetrics = {
303-
[K in MetricCategory]: Record<string, MetricPoint[]>;
304-
};
305-
306285
/**
307286
* Fetch all enhanced metrics for a function based on config
308287
*/
@@ -345,7 +324,7 @@ async function fetchMetricCategory(
345324
toTime: number
346325
): Promise<Record<string, MetricPoint[]>> {
347326
const promises = metricNames.map(async (metricName) => {
348-
const points = await getMetricPoints(metricName, functionName, fromTime, toTime);
327+
const points = await getMetrics(metricName, functionName, fromTime, toTime);
349328
// Use short name (last part after the last dot)
350329
const shortName = metricName.split('.').pop()!;
351330
return { shortName, points };
@@ -361,84 +340,39 @@ async function fetchMetricCategory(
361340
return metrics;
362341
}
363342

364-
// Track if metrics API is available (set once on first failure)
365-
let metricsApiAvailable: boolean | null = null;
366-
367343
/**
368344
* Query Datadog Metrics API v1 for a specific metric.
369345
* Requires the DD_API_KEY to have 'timeseries_query' scope.
370-
* Returns empty array if API is unavailable (permissions issue).
371346
*/
372347
async function getMetrics(
373348
metricName: string,
374349
functionName: string,
375350
fromTime: number,
376351
toTime: number
377352
): Promise<MetricPoint[]> {
378-
// Skip if we've already determined the API is unavailable
379-
if (metricsApiAvailable === false) {
380-
return [];
381-
}
382-
383-
try {
384-
const functionNameLower = functionName.toLowerCase();
385-
const query = `avg:${metricName}{functionname:${functionNameLower}}`;
353+
const functionNameLower = functionName.toLowerCase();
354+
const query = `avg:${metricName}{functionname:${functionNameLower}}`;
386355

387-
console.log(`Querying metrics: ${query}`);
356+
console.log(`Querying metrics: ${query}`);
388357

389-
const response = await datadogClient.get('/api/v1/query', {
390-
params: {
391-
query,
392-
from: Math.floor(fromTime / 1000),
393-
to: Math.floor(toTime / 1000),
394-
},
395-
});
396-
397-
metricsApiAvailable = true;
358+
const response = await datadogClient.get('/api/v1/query', {
359+
params: {
360+
query,
361+
from: Math.floor(fromTime / 1000),
362+
to: Math.floor(toTime / 1000),
363+
},
364+
});
398365

399-
const series = response.data.series || [];
400-
console.log(`Found ${series.length} series for ${metricName}`);
366+
const series = response.data.series || [];
367+
console.log(`Found ${series.length} series for ${metricName}`);
401368

402-
if (series.length === 0) {
403-
return [];
404-
}
405-
406-
// Return points from first series
407-
return (series[0].pointlist || []).map((p: [number, number]) => ({
408-
timestamp: p[0],
409-
value: p[1],
410-
}));
411-
} catch (error: any) {
412-
const errorData = error.response?.data;
413-
// Check if this is a permissions error
414-
if (errorData?.errors?.some((e: string) => e.includes('Forbidden') || e.includes('permission'))) {
415-
if (metricsApiAvailable === null) {
416-
console.warn('⚠️ Metrics API unavailable (missing timeseries_query scope). Metrics tests will be skipped.');
417-
console.warn(' To enable metrics tests, ensure DD_API_KEY has the timeseries_query scope.');
418-
}
419-
metricsApiAvailable = false;
420-
return [];
421-
}
422-
console.error('Error querying metrics:', errorData || error.message);
423-
throw error;
369+
if (series.length === 0) {
370+
return [];
424371
}
425-
}
426-
427-
/**
428-
* Check if metrics API is available
429-
*/
430-
export function isMetricsApiAvailable(): boolean {
431-
return metricsApiAvailable === true;
432-
}
433372

434-
/**
435-
* Get all metric points in time window
436-
*/
437-
async function getMetricPoints(
438-
metricName: string,
439-
functionName: string,
440-
fromTime: number,
441-
toTime: number
442-
): Promise<MetricPoint[]> {
443-
return getMetrics(metricName, functionName, fromTime, toTime);
373+
// Return points from first series
374+
return (series[0].pointlist || []).map((p: [number, number]) => ({
375+
timestamp: p[0],
376+
value: p[1],
377+
}));
444378
}

0 commit comments

Comments
 (0)