44 */
55
66'use strict'
7+
78const assert = require ( 'node:assert' )
89const test = require ( 'node:test' )
9- const helper = require ( '../../lib/agent_helper' )
1010const otel = require ( '@opentelemetry/api' )
1111const { hrTimeToMilliseconds } = require ( '@opentelemetry/core' )
12+
13+ const helper = require ( '../../lib/agent_helper' )
1214const { otelSynthesis } = require ( '../../../lib/symbols' )
13- const { SEMATTRS_HTTP_HOST , SEMATTRS_HTTP_METHOD , SEMATTRS_DB_NAME , SEMATTRS_DB_STATEMENT , SEMATTRS_DB_SYSTEM , SEMATTRS_NET_PEER_PORT , SEMATTRS_NET_PEER_NAME , DbSystemValues } = require ( '@opentelemetry/semantic-conventions' )
15+
16+ const {
17+ ATTR_DB_NAME ,
18+ ATTR_DB_STATEMENT ,
19+ ATTR_DB_SYSTEM ,
20+ ATTR_HTTP_HOST ,
21+ ATTR_HTTP_METHOD ,
22+ ATTR_HTTP_REQUEST_METHOD ,
23+ ATTR_HTTP_ROUTE ,
24+ ATTR_NET_PEER_NAME ,
25+ ATTR_NET_PEER_PORT ,
26+ ATTR_RPC_METHOD ,
27+ ATTR_RPC_SERVICE ,
28+ ATTR_RPC_SYSTEM ,
29+ ATTR_SERVER_ADDRESS ,
30+ ATTR_URL_PATH ,
31+ ATTR_URL_SCHEME ,
32+ DB_SYSTEM_VALUES
33+ } = require ( '../../../lib/otel/constants.js' )
1434
1535test . beforeEach ( ( ctx ) => {
1636 const agent = helper . instrumentMockedAgent ( {
@@ -85,7 +105,7 @@ test('Otel http external span test', (t, end) => {
85105 const { agent, tracer } = t . nr
86106 helper . runInTransaction ( agent , ( tx ) => {
87107 tx . name = 'http-external-test'
88- tracer . startActiveSpan ( 'http-outbound' , { kind : otel . SpanKind . CLIENT , attributes : { [ SEMATTRS_HTTP_HOST ] : 'newrelic.com' , [ SEMATTRS_HTTP_METHOD ] : 'GET' } } , ( span ) => {
108+ tracer . startActiveSpan ( 'http-outbound' , { kind : otel . SpanKind . CLIENT , attributes : { [ ATTR_HTTP_HOST ] : 'newrelic.com' , [ ATTR_HTTP_METHOD ] : 'GET' } } , ( span ) => {
89109 const segment = agent . tracer . getSegment ( )
90110 assert . equal ( segment . name , 'External/newrelic.com' )
91111 span . end ( )
@@ -107,11 +127,11 @@ test('Otel http external span test', (t, end) => {
107127test ( 'Otel db client span statement test' , ( t , end ) => {
108128 const { agent, tracer } = t . nr
109129 const attributes = {
110- [ SEMATTRS_DB_NAME ] : 'test-db' ,
111- [ SEMATTRS_DB_SYSTEM ] : 'postgresql' ,
112- [ SEMATTRS_DB_STATEMENT ] : "select foo from test where foo = 'bar';" ,
113- [ SEMATTRS_NET_PEER_PORT ] : 5436 ,
114- [ SEMATTRS_NET_PEER_NAME ] : '127.0.0.1'
130+ [ ATTR_DB_NAME ] : 'test-db' ,
131+ [ ATTR_DB_SYSTEM ] : 'postgresql' ,
132+ [ ATTR_DB_STATEMENT ] : "select foo from test where foo = 'bar';" ,
133+ [ ATTR_NET_PEER_PORT ] : 5436 ,
134+ [ ATTR_NET_PEER_NAME ] : '127.0.0.1'
115135 }
116136 const expectedHost = agent . config . getHostnameSafe ( '127.0.0.1' )
117137 helper . runInTransaction ( agent , ( tx ) => {
@@ -152,10 +172,10 @@ test('Otel db client span statement test', (t, end) => {
152172test ( 'Otel db client span operation test' , ( t , end ) => {
153173 const { agent, tracer } = t . nr
154174 const attributes = {
155- [ SEMATTRS_DB_SYSTEM ] : DbSystemValues . REDIS ,
156- [ SEMATTRS_DB_STATEMENT ] : 'hset has random random' ,
157- [ SEMATTRS_NET_PEER_PORT ] : 5436 ,
158- [ SEMATTRS_NET_PEER_NAME ] : '127.0.0.1'
175+ [ ATTR_DB_SYSTEM ] : DB_SYSTEM_VALUES . REDIS ,
176+ [ ATTR_DB_STATEMENT ] : 'hset has random random' ,
177+ [ ATTR_NET_PEER_PORT ] : 5436 ,
178+ [ ATTR_NET_PEER_NAME ] : '127.0.0.1'
159179 }
160180 const expectedHost = agent . config . getHostnameSafe ( '127.0.0.1' )
161181 helper . runInTransaction ( agent , ( tx ) => {
@@ -189,3 +209,147 @@ test('Otel db client span operation test', (t, end) => {
189209 } )
190210 } )
191211} )
212+
213+ test ( 'http metrics are bridged correctly' , ( t , end ) => {
214+ const { agent, tracer } = t . nr
215+
216+ // Required span attributes for incoming HTTP server spans as defined by:
217+ // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server-semantic-conventions
218+ const attributes = {
219+ [ ATTR_URL_SCHEME ] : 'http' ,
220+ [ ATTR_SERVER_ADDRESS ] : 'newrelic.com' ,
221+ [ ATTR_HTTP_REQUEST_METHOD ] : 'GET' ,
222+ [ ATTR_URL_PATH ] : '/foo/bar' ,
223+ [ ATTR_HTTP_ROUTE ] : '/foo/:param'
224+ }
225+
226+ tracer . startActiveSpan ( 'http-test' , { kind : otel . SpanKind . SERVER , attributes } , ( span ) => {
227+ const tx = agent . getTransaction ( )
228+ const segment = agent . tracer . getSegment ( )
229+ assert . equal ( segment . name , 'WebTransaction/Nodejs/GET//foo/:param' )
230+ span . end ( )
231+
232+ const duration = hrTimeToMilliseconds ( span . duration )
233+ assert . equal ( duration , segment . getDurationInMillis ( ) )
234+ tx . end ( )
235+
236+ const attrs = segment . getAttributes ( )
237+ assert . equal ( attrs . host , 'newrelic.com' )
238+ assert . equal ( attrs [ 'http.request.method' ] , 'GET' )
239+ assert . equal ( attrs [ 'http.route' ] , '/foo/:param' )
240+ assert . equal ( attrs [ 'url.path' ] , '/foo/bar' )
241+ assert . equal ( attrs [ 'url.scheme' ] , 'http' )
242+ assert . equal ( attrs . nr_exclusive_duration_millis , duration )
243+
244+ const unscopedMetrics = tx . metrics . unscoped
245+ const expectedMetrics = [
246+ 'HttpDispatcher' ,
247+ 'WebTransaction' ,
248+ 'WebTransaction/Nodejs/GET//foo/:param' ,
249+ 'WebTransactionTotalTime' ,
250+ 'WebTransactionTotalTime/null' ,
251+ segment . name
252+ ]
253+ for ( const expectedMetric of expectedMetrics ) {
254+ assert . equal ( unscopedMetrics [ expectedMetric ] . callCount , 1 , `${ expectedMetric } has correct callCount` )
255+ }
256+ assert . equal ( unscopedMetrics . Apdex . apdexT , 0.1 )
257+ assert . equal ( unscopedMetrics [ 'Apdex/null' ] . apdexT , 0.1 )
258+
259+ end ( )
260+ } )
261+ } )
262+
263+ test ( 'rpc server metrics are bridged correctly' , ( t , end ) => {
264+ const { agent, tracer } = t . nr
265+
266+ // Required span attributes for incoming HTTP server spans as defined by:
267+ // https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#client-attributes
268+ const attributes = {
269+ [ ATTR_RPC_SYSTEM ] : 'foo' ,
270+ [ ATTR_RPC_METHOD ] : 'getData' ,
271+ [ ATTR_RPC_SERVICE ] : 'test.service' ,
272+ [ ATTR_SERVER_ADDRESS ] : 'newrelic.com' ,
273+ [ ATTR_URL_PATH ] : '/foo/bar'
274+ }
275+
276+ tracer . startActiveSpan ( 'http-test' , { kind : otel . SpanKind . SERVER , attributes } , ( span ) => {
277+ const tx = agent . getTransaction ( )
278+ const segment = agent . tracer . getSegment ( )
279+ assert . equal ( segment . name , 'WebTransaction/WebFrameworkUri/foo/test.service.getData' )
280+ span . end ( )
281+
282+ const duration = hrTimeToMilliseconds ( span . duration )
283+ assert . equal ( duration , segment . getDurationInMillis ( ) )
284+ tx . end ( )
285+
286+ const attrs = segment . getAttributes ( )
287+ assert . equal ( attrs . host , 'newrelic.com' )
288+ assert . equal ( attrs [ 'rpc.system' ] , 'foo' )
289+ assert . equal ( attrs [ 'rpc.method' ] , 'getData' )
290+ assert . equal ( attrs [ 'rpc.service' ] , 'test.service' )
291+ assert . equal ( attrs [ 'url.path' ] , '/foo/bar' )
292+ assert . equal ( attrs . nr_exclusive_duration_millis , duration )
293+
294+ const unscopedMetrics = tx . metrics . unscoped
295+ const expectedMetrics = [
296+ 'HttpDispatcher' ,
297+ 'WebTransaction' ,
298+ 'WebTransaction/WebFrameworkUri/foo/test.service.getData' ,
299+ 'WebTransactionTotalTime' ,
300+ 'WebTransactionTotalTime/null' ,
301+ segment . name
302+ ]
303+ for ( const expectedMetric of expectedMetrics ) {
304+ assert . equal ( unscopedMetrics [ expectedMetric ] . callCount , 1 , `${ expectedMetric } has correct callCount` )
305+ }
306+ assert . equal ( unscopedMetrics . Apdex . apdexT , 0.1 )
307+ assert . equal ( unscopedMetrics [ 'Apdex/null' ] . apdexT , 0.1 )
308+
309+ end ( )
310+ } )
311+ } )
312+
313+ test ( 'fallback metrics are bridged correctly' , ( t , end ) => {
314+ const { agent, tracer } = t . nr
315+
316+ const attributes = {
317+ [ ATTR_URL_SCHEME ] : 'gopher' ,
318+ [ ATTR_SERVER_ADDRESS ] : 'newrelic.com' ,
319+ [ ATTR_URL_PATH ] : '/foo/bar' ,
320+ }
321+
322+ tracer . startActiveSpan ( 'http-test' , { kind : otel . SpanKind . SERVER , attributes } , ( span ) => {
323+ const tx = agent . getTransaction ( )
324+ const segment = agent . tracer . getSegment ( )
325+ assert . equal ( segment . name , 'WebTransaction/NormalizedUri/*' )
326+ span . end ( )
327+
328+ const duration = hrTimeToMilliseconds ( span . duration )
329+ assert . equal ( duration , segment . getDurationInMillis ( ) )
330+ tx . end ( )
331+
332+ const attrs = segment . getAttributes ( )
333+ assert . equal ( attrs . host , 'newrelic.com' )
334+ assert . equal ( attrs [ 'url.path' ] , '/foo/bar' )
335+ assert . equal ( attrs [ 'url.scheme' ] , 'gopher' )
336+ assert . equal ( attrs . nr_exclusive_duration_millis , duration )
337+
338+ const unscopedMetrics = tx . metrics . unscoped
339+ const expectedMetrics = [
340+ 'HttpDispatcher' ,
341+ 'WebTransaction' ,
342+ 'WebTransaction/NormalizedUri/*' ,
343+ 'WebTransactionTotalTime' ,
344+ 'WebTransactionTotalTime/null' ,
345+ segment . name
346+ ]
347+ for ( const expectedMetric of expectedMetrics ) {
348+ assert . equal ( unscopedMetrics [ expectedMetric ] . callCount , 1 , `${ expectedMetric } has correct callCount` )
349+ }
350+ assert . equal ( unscopedMetrics . Apdex . apdexT , 0.1 )
351+ assert . equal ( unscopedMetrics [ 'Apdex/null' ] . apdexT , 0.1 )
352+
353+ end ( )
354+ } )
355+ } )
0 commit comments