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