From a1dd5c1926220712f113fbe01618b47424b06dc6 Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:51:36 -0600 Subject: [PATCH 1/7] add metrics for partial instrumented and kept --- lib/metrics/names.js | 6 +++++- lib/spans/span-event-aggregator.js | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/metrics/names.js b/lib/metrics/names.js index ed30cd2bae..3504fc470b 100644 --- a/lib/metrics/names.js +++ b/lib/metrics/names.js @@ -291,11 +291,15 @@ const DISTRIBUTED_TRACE = { const SPAN_EVENT_PREFIX = 'SpanEvent/' +const PARTIAL_GRANULARITY = 'PartialGranularity/' + const SPAN_EVENTS = { SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen', SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent', DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded', - LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit' + LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit', + KEPT: SUPPORTABILITY.PREFIX + 'DistributedTrace/' + PARTIAL_GRANULARITY + '%s' + '/Span/Kept', + INSTRUMENTED: SUPPORTABILITY.PREFIX + 'DistributedTrace/' + PARTIAL_GRANULARITY + '%s' + '/Span/Instrumented', } const INFINITE_TRACING = { diff --git a/lib/spans/span-event-aggregator.js b/lib/spans/span-event-aggregator.js index edf8602a4d..a9a5e10a05 100644 --- a/lib/spans/span-event-aggregator.js +++ b/lib/spans/span-event-aggregator.js @@ -9,6 +9,7 @@ const logger = require('../logger').child({ component: 'span_aggregator' }) const EventAggregator = require('../aggregators/event-aggregator') const SpanEvent = require('./span-event') const NAMES = require('../metrics/names') +const util = require('util') const DEFAULT_SPAN_EVENT_LIMIT = 2000 // Used only when server value missing @@ -76,10 +77,14 @@ class SpanEventAggregator extends EventAggregator { return false } + const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans, partialGranularityMode: this.partialGranularityMode }) + this._metrics.getOrCreateMetric(util.format(this._metricNames.INSTRUMENTED, this.partialGranularityMode)).incrementCallCount() + if (span) { this.add(span, transaction.priority) + this._metrics.getOrCreateMetric(util.format(this._metricNames.KEPT, this.partialGranularityMode)).incrementCallCount() } if (segment.spanLinks.length > 0) { From 76777c1189fcfc6a97412c1bf3b3fe66c3ca521d Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:30:00 -0600 Subject: [PATCH 2/7] move up instrumented metrics --- lib/spans/span-event-aggregator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/spans/span-event-aggregator.js b/lib/spans/span-event-aggregator.js index a9a5e10a05..371f9d6d07 100644 --- a/lib/spans/span-event-aggregator.js +++ b/lib/spans/span-event-aggregator.js @@ -78,10 +78,10 @@ class SpanEventAggregator extends EventAggregator { return false } - const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans, partialGranularityMode: this.partialGranularityMode }) - this._metrics.getOrCreateMetric(util.format(this._metricNames.INSTRUMENTED, this.partialGranularityMode)).incrementCallCount() + const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans, partialGranularityMode: this.partialGranularityMode }) + if (span) { this.add(span, transaction.priority) this._metrics.getOrCreateMetric(util.format(this._metricNames.KEPT, this.partialGranularityMode)).incrementCallCount() From 925dd115c849fbc2b5b6e708a600626791ca4f9e Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:35:14 -0600 Subject: [PATCH 3/7] add unit tests for metrics --- .../spans/partial-granularity-spans.test.js | 98 +++++++++++++++++-- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/test/unit/spans/partial-granularity-spans.test.js b/test/unit/spans/partial-granularity-spans.test.js index becabfa316..762cb2ff1f 100644 --- a/test/unit/spans/partial-granularity-spans.test.js +++ b/test/unit/spans/partial-granularity-spans.test.js @@ -9,23 +9,50 @@ const test = require('node:test') const helper = require('#testlib/agent_helper.js') const SpanEvent = require('#agentlib/spans/span-event.js') const MODES = ['reduced', 'essential'] +const SpanEventAggregator = require('../../../lib/spans/span-event-aggregator') +const Metrics = require('../../../lib/metrics') + +const RUN_ID = 1337 +const DEFAULT_LIMIT = 2000 +const DEFAULT_PERIOD = 60000 for (const mode of MODES) { test(`Partial Granularity Spans - ${mode} mode`, async (t) => { t.beforeEach((ctx) => { - const agent = helper.loadMockedAgent({ + ctx.nr = {} + ctx.nr.spanEventAggregator = new SpanEventAggregator( + { + runId: RUN_ID, + limit: DEFAULT_LIMIT, + periodMs: DEFAULT_PERIOD + }, + { + config: { + distributed_tracing: { + in_process_spans: { + enabled: true + } + } + }, + collector: {}, + metrics: new Metrics(5, {}, {}), + harvester: { add() {} } + } + ) + ctx.nr.agent = helper.loadMockedAgent({ distributed_tracing: { enabled: true, - full_granularity: { - enabled: false - }, - partial_granularity: { - enabled: true, - type: mode + sampler: { + full_granularity: { + enabled: false + }, + partial_granularity: { + enabled: true, + type: mode + } } } }) - ctx.nr = { agent } }) t.afterEach((ctx) => { @@ -172,5 +199,60 @@ for (const mode of MODES) { end() }) }) + + await t.test('should record a instrumented and kept metric for exit span that has entity relationship attrs', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + transaction.isPartialTrace = true + const segment = transaction.trace.add('Datastore/operation/Redis/SET') + segment.addAttribute('host', 'redis-service') + segment.addAttribute('port_path_or_id', 6379) + segment.addAttribute('foo', 'bar') + const spanContext = segment.getSpanContext() + spanContext.addCustomAttribute('custom', 'test') + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + assert.ok(span) + transaction.end() + + const unscopedMetrics = agent.metrics._metrics.unscoped + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Instrumented`].callCount, 1) + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Kept`].callCount, 1) + end() + }) + }) + + await t.test('should record instrumented metric only for dropped exit span that does not have entity relationship attrs', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + transaction.isPartialTrace = true + const segment = transaction.trace.add('Datastore/operation/Redis/SET') + segment.addAttribute('foo', 'bar') + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + assert.ok(!span) + transaction.end() + const unscopedMetrics = agent.metrics._metrics.unscoped + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Instrumented`].callCount, 1) + // span was dropped so kept metric was not recorded + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Kept`], undefined) + end() + }) + }) + + await t.test('should record instrumented metric only for dropped in process span', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + transaction.isPartialTrace = true + const segment = transaction.trace.add('test-segment') + segment.addAttribute('foo', 'bar') + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + assert.ok(!span) + transaction.end() + const unscopedMetrics = agent.metrics._metrics.unscoped + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Instrumented`].callCount, 1) + // span was dropped so kept metric was not recorded + assert.equal(unscopedMetrics[`Supportability/DistributedTrace/PartialGranularity/${mode}/Span/Kept`], undefined) + end() + }) + }) }) } From a4615b7ca533d6f8580bf19d495acd463db76f34 Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:47:35 -0600 Subject: [PATCH 4/7] fix tests after main merge --- test/unit/spans/partial-granularity-spans.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/spans/partial-granularity-spans.test.js b/test/unit/spans/partial-granularity-spans.test.js index 0566ac1b1c..70e0c16056 100644 --- a/test/unit/spans/partial-granularity-spans.test.js +++ b/test/unit/spans/partial-granularity-spans.test.js @@ -203,14 +203,14 @@ for (const mode of MODES) { await t.test('should record a instrumented and kept metric for exit span that has entity relationship attrs', (t, end) => { const { agent } = t.nr helper.runInTransaction(agent, (transaction) => { - transaction.isPartialTrace = true + transaction.partialType = mode const segment = transaction.trace.add('Datastore/operation/Redis/SET') segment.addAttribute('host', 'redis-service') segment.addAttribute('port_path_or_id', 6379) segment.addAttribute('foo', 'bar') const spanContext = segment.getSpanContext() spanContext.addCustomAttribute('custom', 'test') - const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) assert.ok(span) transaction.end() @@ -224,10 +224,10 @@ for (const mode of MODES) { await t.test('should record instrumented metric only for dropped exit span that does not have entity relationship attrs', (t, end) => { const { agent } = t.nr helper.runInTransaction(agent, (transaction) => { - transaction.isPartialTrace = true + transaction.partialType = mode const segment = transaction.trace.add('Datastore/operation/Redis/SET') segment.addAttribute('foo', 'bar') - const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) assert.ok(!span) transaction.end() const unscopedMetrics = agent.metrics._metrics.unscoped @@ -241,10 +241,10 @@ for (const mode of MODES) { await t.test('should record instrumented metric only for dropped in process span', (t, end) => { const { agent } = t.nr helper.runInTransaction(agent, (transaction) => { - transaction.isPartialTrace = true + transaction.partialType = mode const segment = transaction.trace.add('test-segment') segment.addAttribute('foo', 'bar') - const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true, partialGranularityMode: mode }) + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) assert.ok(!span) transaction.end() const unscopedMetrics = agent.metrics._metrics.unscoped From 404d1c5bfee32d92ea90fde25d43684eba5a6b76 Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:49:55 -0600 Subject: [PATCH 5/7] remove unneeded span event aggregator in tests --- .../spans/partial-granularity-spans.test.js | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/test/unit/spans/partial-granularity-spans.test.js b/test/unit/spans/partial-granularity-spans.test.js index 70e0c16056..5de6bbb12a 100644 --- a/test/unit/spans/partial-granularity-spans.test.js +++ b/test/unit/spans/partial-granularity-spans.test.js @@ -9,37 +9,11 @@ const test = require('node:test') const helper = require('#testlib/agent_helper.js') const SpanEvent = require('#agentlib/spans/span-event.js') const MODES = ['reduced', 'essential'] -const SpanEventAggregator = require('../../../lib/spans/span-event-aggregator') -const Metrics = require('../../../lib/metrics') - -const RUN_ID = 1337 -const DEFAULT_LIMIT = 2000 -const DEFAULT_PERIOD = 60000 for (const mode of MODES) { test(`Partial Granularity Spans - ${mode} mode`, async (t) => { t.beforeEach((ctx) => { - ctx.nr = {} - ctx.nr.spanEventAggregator = new SpanEventAggregator( - { - runId: RUN_ID, - limit: DEFAULT_LIMIT, - periodMs: DEFAULT_PERIOD - }, - { - config: { - distributed_tracing: { - in_process_spans: { - enabled: true - } - } - }, - collector: {}, - metrics: new Metrics(5, {}, {}), - harvester: { add() {} } - } - ) - ctx.nr.agent = helper.loadMockedAgent({ + const agent = helper.loadMockedAgent({ distributed_tracing: { enabled: true, sampler: { @@ -53,6 +27,7 @@ for (const mode of MODES) { } } }) + ctx.nr = { agent } }) t.afterEach((ctx) => { From a48a15ed92c8204708ca7dd628f3f946a856e391 Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:52:03 -0600 Subject: [PATCH 6/7] move metrics to be recorded for partial trace only and add test for it --- lib/spans/span-event-aggregator.js | 8 ++- .../unit/spans/full-granularity-spans.test.js | 49 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/unit/spans/full-granularity-spans.test.js diff --git a/lib/spans/span-event-aggregator.js b/lib/spans/span-event-aggregator.js index a004735280..e4311d0d3b 100644 --- a/lib/spans/span-event-aggregator.js +++ b/lib/spans/span-event-aggregator.js @@ -77,13 +77,17 @@ class SpanEventAggregator extends EventAggregator { return false } - this._metrics.getOrCreateMetric(util.format(this._metricNames.INSTRUMENTED, transaction.partialType)).incrementCallCount() + if (transaction.partialType) { + this._metrics.getOrCreateMetric(util.format(this._metricNames.INSTRUMENTED, transaction.partialType)).incrementCallCount() + } const span = SpanEvent.fromSegment({ segment, transaction, parentId, isRoot, inProcessSpans: this.inProcessSpans }) if (span) { this.add(span, transaction.priority) - this._metrics.getOrCreateMetric(util.format(this._metricNames.KEPT, transaction.partialType)).incrementCallCount() + if (transaction.partialType) { + this._metrics.getOrCreateMetric(util.format(this._metricNames.KEPT, transaction.partialType)).incrementCallCount() + } } if (segment.spanLinks.length > 0) { diff --git a/test/unit/spans/full-granularity-spans.test.js b/test/unit/spans/full-granularity-spans.test.js new file mode 100644 index 0000000000..b14faf30c1 --- /dev/null +++ b/test/unit/spans/full-granularity-spans.test.js @@ -0,0 +1,49 @@ +/* + * Copyright 2025 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const helper = require('#testlib/agent_helper.js') +const SpanEvent = require('#agentlib/spans/span-event.js') + +test('Partial Granularity metrics with Full Granularity settings', async (t) => { + t.beforeEach((ctx) => { + const agent = helper.loadMockedAgent({ + distributed_tracing: { + enabled: true, + sampler: { + full_granularity: { + enabled: true + }, + partial_granularity: { + enabled: false, + } + } + } + }) + ctx.nr = { agent } + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + }) + + await t.test('should not record partial granularity metrics when not part of partialTrace', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + const segment = transaction.trace.add('Datastore/operation/Redis/SET') + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) + assert.ok(span) + transaction.end() + const unscopedMetrics = agent.metrics._metrics.unscoped + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Instrumented'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Kept'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Instrumented'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Kept'], undefined) + end() + }) + }) +}) From f7272850425f76b9ba01b19d8087e994b3a20e74 Mon Sep 17 00:00:00 2001 From: Svetlana Brennan <50715937+svetlanabrennan@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:46:15 -0600 Subject: [PATCH 7/7] move full granularity test to another existing file --- .../unit/spans/full-granularity-spans.test.js | 49 ------------------- test/unit/spans/span-event.test.js | 16 ++++++ 2 files changed, 16 insertions(+), 49 deletions(-) delete mode 100644 test/unit/spans/full-granularity-spans.test.js diff --git a/test/unit/spans/full-granularity-spans.test.js b/test/unit/spans/full-granularity-spans.test.js deleted file mode 100644 index b14faf30c1..0000000000 --- a/test/unit/spans/full-granularity-spans.test.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2025 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' -const assert = require('node:assert') -const test = require('node:test') -const helper = require('#testlib/agent_helper.js') -const SpanEvent = require('#agentlib/spans/span-event.js') - -test('Partial Granularity metrics with Full Granularity settings', async (t) => { - t.beforeEach((ctx) => { - const agent = helper.loadMockedAgent({ - distributed_tracing: { - enabled: true, - sampler: { - full_granularity: { - enabled: true - }, - partial_granularity: { - enabled: false, - } - } - } - }) - ctx.nr = { agent } - }) - - t.afterEach((ctx) => { - helper.unloadAgent(ctx.nr.agent) - }) - - await t.test('should not record partial granularity metrics when not part of partialTrace', (t, end) => { - const { agent } = t.nr - helper.runInTransaction(agent, (transaction) => { - const segment = transaction.trace.add('Datastore/operation/Redis/SET') - const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) - assert.ok(span) - transaction.end() - const unscopedMetrics = agent.metrics._metrics.unscoped - assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Instrumented'], undefined) - assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Kept'], undefined) - assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Instrumented'], undefined) - assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Kept'], undefined) - end() - }) - }) -}) diff --git a/test/unit/spans/span-event.test.js b/test/unit/spans/span-event.test.js index 76a4ff34db..bbcc2e2ac9 100644 --- a/test/unit/spans/span-event.test.js +++ b/test/unit/spans/span-event.test.js @@ -468,4 +468,20 @@ test('fromSegment()', async (t) => { }) }) } + + await t.test('should not record partial granularity metrics when not part of partialTrace', (t, end) => { + const { agent } = t.nr + helper.runInTransaction(agent, (transaction) => { + const segment = transaction.trace.add('Datastore/operation/Redis/SET') + const span = SpanEvent.fromSegment({ segment, transaction, inProcessSpans: true }) + assert.ok(span) + transaction.end() + const unscopedMetrics = agent.metrics._metrics.unscoped + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Instrumented'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/reduced/Span/Kept'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Instrumented'], undefined) + assert.equal(unscopedMetrics['Supportability/DistributedTrace/PartialGranularity/essential/Span/Kept'], undefined) + end() + }) + }) })