@@ -18,6 +18,11 @@ const datadogClient: AxiosInstance = axios.create({
1818} ) ;
1919
2020export 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 */
336311async 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