From 66f140f36b0cf5ed1028a4cbda672bc603573ae7 Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Thu, 31 Oct 2024 14:56:02 -0400 Subject: [PATCH] feat: Removed children from segments. (#2689) --- lib/instrumentation/nextjs/utils.js | 13 - lib/metrics/recorders/custom.js | 2 +- lib/metrics/recorders/database-operation.js | 2 +- lib/metrics/recorders/database.js | 2 +- lib/metrics/recorders/generic.js | 2 +- lib/metrics/recorders/http.js | 2 +- lib/metrics/recorders/http_external.js | 2 +- lib/metrics/recorders/message-transaction.js | 2 +- lib/metrics/recorders/middleware.js | 2 +- lib/metrics/recorders/other.js | 2 +- lib/shim/shim.js | 3 +- .../trace/exclusive-time-calculator.js | 6 +- lib/transaction/trace/index.js | 175 +++++--- lib/transaction/trace/segment.js | 168 +------- lib/transaction/tracer/index.js | 9 +- test/benchmark/shim/segments.bench.js | 1 - test/benchmark/trace/segment.bench.js | 70 +-- test/integration/cat/cat.test.js | 8 +- test/integration/core/crypto.test.js | 13 +- test/integration/core/fs.test.js | 20 +- test/integration/core/net.tap.js | 210 --------- test/integration/core/net.test.js | 60 +-- test/integration/core/timers.test.js | 35 +- test/integration/core/verify.js | 14 +- .../integration/instrumentation/fetch.test.js | 16 +- .../instrumentation/http-outbound.test.js | 39 +- test/integration/transaction/tracer.test.js | 73 +++- test/lib/agent_helper.js | 8 - test/lib/custom-assertions/assert-segments.js | 19 +- .../lib/custom-assertions/compare-segments.js | 21 +- test/lib/metrics_helper.js | 11 +- test/smoke/client-s3.test.js | 4 +- test/unit/agent/agent.test.js | 5 - .../api-start-background-transaction.test.js | 30 +- test/unit/api/api-start-segment.test.js | 4 + .../api/api-start-web-transaction.test.js | 5 +- .../instrumentation/core/promises.test.js | 2 +- .../instrumentation/http/outbound.test.js | 24 +- .../instrumentation/prisma-client.test.js | 6 +- test/unit/instrumentation/redis.test.js | 2 +- test/unit/llm-events/openai/common.js | 6 +- test/unit/shim/datastore-shim.test.js | 40 +- test/unit/shim/message-shim.test.js | 36 +- test/unit/shim/shim.test.js | 62 +-- test/unit/spans/span-event.test.js | 10 +- test/unit/spans/streaming-span-event.test.js | 10 +- test/unit/transaction/trace/index.test.js | 398 +++++++----------- test/unit/transaction/trace/segment.test.js | 150 ++----- test/unit/transaction/tracer.test.js | 15 + test/versioned-external/external-repos.js | 2 +- test/versioned/amqplib/amqp-utils.js | 28 +- test/versioned/amqplib/callback.test.js | 2 +- test/versioned/amqplib/promises.test.js | 2 +- .../aws-sdk-v2/amazon-dax-client.test.js | 12 +- test/versioned/aws-sdk-v2/dynamodb.test.js | 12 +- .../aws-sdk-v2/http-services.test.js | 6 +- test/versioned/aws-sdk-v2/s3.test.js | 6 +- test/versioned/aws-sdk-v2/sns.test.js | 12 +- test/versioned/aws-sdk-v2/sqs.test.js | 12 +- .../bedrock-chat-completions.test.js | 4 + .../aws-sdk-v3/bedrock-embeddings.test.js | 12 +- .../aws-sdk-v3/client-dynamodb.test.js | 18 +- test/versioned/aws-sdk-v3/common.js | 28 +- test/versioned/aws-sdk-v3/lambda.test.js | 4 +- .../versioned/aws-sdk-v3/lib-dynamodb.test.js | 12 +- test/versioned/aws-sdk-v3/sns.test.js | 12 +- test/versioned/aws-sdk-v3/sqs.test.js | 12 +- test/versioned/cassandra-driver/query.test.js | 30 +- test/versioned/connect/route.test.js | 4 +- .../disabled-express.test.js | 2 +- .../disabled-ioredis.test.js | 4 +- test/versioned/elastic/elasticsearch.test.js | 30 +- .../elastic/elasticsearchNoop.test.js | 2 +- test/versioned/express-esm/segments.test.mjs | 30 +- .../express-esm/transaction-naming.test.mjs | 3 +- test/versioned/express/async-handlers.test.js | 4 +- test/versioned/express/bare-router.test.js | 2 +- .../express/client-disconnect.test.js | 1 + test/versioned/express/router-params.test.js | 2 +- test/versioned/express/segments.test.js | 128 +++--- .../express/transaction-naming.test.js | 3 +- test/versioned/fastify/add-hook.test.js | 4 +- .../fastify/code-level-metrics-hooks.test.js | 11 +- .../code-level-metrics-middleware.test.js | 17 +- test/versioned/fastify/naming-common.js | 2 +- test/versioned/grpc/util.cjs | 4 +- test/versioned/hapi/render.test.js | 7 +- test/versioned/hapi/router.test.js | 5 +- test/versioned/hapi/segments.test.js | 30 +- test/versioned/ioredis/ioredis.test.js | 10 +- test/versioned/kafkajs/kafka.test.js | 39 +- test/versioned/kafkajs/utils.js | 4 +- test/versioned/koa/code-level-metrics.test.js | 39 +- test/versioned/koa/koa-route.test.js | 12 +- test/versioned/koa/koa.test.js | 3 + test/versioned/koa/router-common.js | 52 +-- test/versioned/langchain/common.js | 16 +- .../langchain/runnables-streaming.test.js | 2 +- test/versioned/langchain/runnables.test.js | 2 +- test/versioned/langchain/tools.test.js | 7 +- test/versioned/langchain/vectorstore.test.js | 2 +- test/versioned/memcached/memcached.test.js | 37 +- test/versioned/mongodb-esm/db.test.mjs | 7 +- .../versioned/mongodb-esm/test-assertions.mjs | 15 +- test/versioned/mongodb/collection-common.js | 36 +- test/versioned/mongodb/db-common.js | 11 +- test/versioned/mysql/basic-pool.js | 44 +- test/versioned/mysql/basic.js | 27 +- test/versioned/mysql/pooling.js | 14 +- test/versioned/mysql2/promises.test.js | 2 +- test/versioned/nextjs/attributes.test.js | 26 +- test/versioned/nextjs/helpers.js | 25 +- test/versioned/nextjs/segments.test.js | 6 +- .../versioned/openai/chat-completions.test.js | 3 + test/versioned/openai/common.js | 8 +- test/versioned/openai/embeddings.test.js | 7 +- test/versioned/opensearch/opensearch.test.js | 30 +- test/versioned/pg-esm/pg.common.mjs | 14 +- test/versioned/pg/pg.common.js | 14 +- test/versioned/prisma/prisma.test.js | 6 +- test/versioned/prisma/utils.js | 6 +- test/versioned/q/q.test.js | 3 +- .../redis/redis-v4-legacy-mode.test.js | 27 +- test/versioned/redis/redis-v4.test.js | 28 +- test/versioned/redis/redis.test.js | 42 +- test/versioned/restify/router.test.js | 2 +- test/versioned/superagent/async-await.test.js | 5 +- test/versioned/superagent/superagent.test.js | 10 +- test/versioned/undici/requests.test.js | 24 +- test/versioned/when/segments.test.js | 55 ++- 130 files changed, 1484 insertions(+), 1578 deletions(-) delete mode 100644 test/integration/core/net.tap.js diff --git a/lib/instrumentation/nextjs/utils.js b/lib/instrumentation/nextjs/utils.js index 95c0645a32..3e0aef73f0 100644 --- a/lib/instrumentation/nextjs/utils.js +++ b/lib/instrumentation/nextjs/utils.js @@ -56,16 +56,3 @@ utils.isMiddlewareInstrumentationSupported = function isMiddlewareInstrumentatio semver.gte(version, MIN_MW_SUPPORTED_VERSION) && semver.lte(version, MAX_MW_SUPPORTED_VERSION) ) } - -/** - * Depending on the Next.js version the segment tree varies as it adds setTimeout segments. - * This util will find the segment that has `getServerSideProps` in the name - * - * @param {object} rootSegment trace root - * @returns {object} getServerSideProps segment - */ -utils.getServerSidePropsSegment = function getServerSidePropsSegment(rootSegment) { - return rootSegment.children[0].children.find((segment) => - segment.name.includes('getServerSideProps') - ) -} diff --git a/lib/metrics/recorders/custom.js b/lib/metrics/recorders/custom.js index c36cebe44b..f4623202e1 100644 --- a/lib/metrics/recorders/custom.js +++ b/lib/metrics/recorders/custom.js @@ -9,7 +9,7 @@ const NAMES = require('../names') function record(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) const name = NAMES.CUSTOM + NAMES.ACTION_DELIMITER + segment.name if (scope) { diff --git a/lib/metrics/recorders/database-operation.js b/lib/metrics/recorders/database-operation.js index 358e9e98d1..c602aec2b2 100644 --- a/lib/metrics/recorders/database-operation.js +++ b/lib/metrics/recorders/database-operation.js @@ -22,7 +22,7 @@ const metrics = require('../names') */ function recordOperationMetrics(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) const type = transaction.isWeb() ? 'allWeb' : 'allOther' const operation = segment.name diff --git a/lib/metrics/recorders/database.js b/lib/metrics/recorders/database.js index 5b98cca835..c53a1e73d0 100644 --- a/lib/metrics/recorders/database.js +++ b/lib/metrics/recorders/database.js @@ -16,7 +16,7 @@ const { DESTINATIONS } = require('../../config/attribute-filter') function recordQueryMetrics(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) const type = transaction.isWeb() ? DB.WEB : DB.OTHER const thisTypeSlash = this.type + '/' const operation = DB.OPERATION + '/' + thisTypeSlash + this.operation diff --git a/lib/metrics/recorders/generic.js b/lib/metrics/recorders/generic.js index f46293c16c..945b61c3c6 100644 --- a/lib/metrics/recorders/generic.js +++ b/lib/metrics/recorders/generic.js @@ -7,7 +7,7 @@ function record(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) if (scope) { transaction.measure(segment.name, scope, duration, exclusive) diff --git a/lib/metrics/recorders/http.js b/lib/metrics/recorders/http.js index 777424f6bf..4ef57aff07 100644 --- a/lib/metrics/recorders/http.js +++ b/lib/metrics/recorders/http.js @@ -23,7 +23,7 @@ function recordWeb(segment, scope, tx) { const duration = segment.getDurationInMillis() const totalTime = tx.trace.getTotalTimeDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(tx.trace) const partial = segment.partialName const config = tx.agent.config // named / key transaction support requires per-name apdexT diff --git a/lib/metrics/recorders/http_external.js b/lib/metrics/recorders/http_external.js index 252a97f1f0..0a656b407f 100644 --- a/lib/metrics/recorders/http_external.js +++ b/lib/metrics/recorders/http_external.js @@ -10,7 +10,7 @@ const EXTERNAL = require('../../metrics/names').EXTERNAL function recordExternal(host, library) { return function externalRecorder(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) const metricName = EXTERNAL.PREFIX + host + '/' + library const rollupType = transaction.isWeb() ? EXTERNAL.WEB : EXTERNAL.OTHER const rollupHost = EXTERNAL.PREFIX + host + '/all' diff --git a/lib/metrics/recorders/message-transaction.js b/lib/metrics/recorders/message-transaction.js index c880226b1d..740223d867 100644 --- a/lib/metrics/recorders/message-transaction.js +++ b/lib/metrics/recorders/message-transaction.js @@ -13,7 +13,7 @@ function recordMessageTransaction(segment, scope, tx) { } const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(tx.trace) const totalTime = tx.trace.getTotalTimeDurationInMillis() if (scope) { diff --git a/lib/metrics/recorders/middleware.js b/lib/metrics/recorders/middleware.js index d8ec3e0493..57adfd0096 100644 --- a/lib/metrics/recorders/middleware.js +++ b/lib/metrics/recorders/middleware.js @@ -16,7 +16,7 @@ function makeMiddlewareRecorder(_shim, metricName) { return function middlewareMetricRecorder(segment, scope, transaction) { const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(transaction.trace) if (scope) { transaction.measure(metricName, scope, duration, exclusive) diff --git a/lib/metrics/recorders/other.js b/lib/metrics/recorders/other.js index 0637ccaaa9..47f0c642c1 100644 --- a/lib/metrics/recorders/other.js +++ b/lib/metrics/recorders/other.js @@ -15,7 +15,7 @@ function recordBackground(segment, scope, tx) { } const duration = segment.getDurationInMillis() - const exclusive = segment.getExclusiveDurationInMillis() + const exclusive = segment.getExclusiveDurationInMillis(tx.trace) const totalTime = tx.trace.getTotalTimeDurationInMillis() const name = segment.partialName diff --git a/lib/shim/shim.js b/lib/shim/shim.js index 734ff9e3bd..07c6cb14e9 100644 --- a/lib/shim/shim.js +++ b/lib/shim/shim.js @@ -1324,8 +1324,7 @@ function createSegment(name, recorder, parent) { * @param {Shim} params.shim instance of shim * @param {Transaction} params.transaction active transaction * @param {TraceSegment} params.parent the segment that will be the parent of the newly created segment - * @param params.spec - * @param {string|specs.SegmentSpec} spec options for creating segment + * @param {string|specs.SegmentSpec} params.spec options for creating segment * @returns {?TraceSegment} A new trace segment if a transaction is active, else * `null` is returned. */ diff --git a/lib/transaction/trace/exclusive-time-calculator.js b/lib/transaction/trace/exclusive-time-calculator.js index dbb5b1d685..69a24bbf2a 100644 --- a/lib/transaction/trace/exclusive-time-calculator.js +++ b/lib/transaction/trace/exclusive-time-calculator.js @@ -6,7 +6,9 @@ 'use strict' class ExclusiveCalculator { - constructor(root) { + constructor(root, trace) { + this.trace = trace + this.id = root.id this.toProcess = [root] // use a second stack to do a post-order traversal this.parentStack = [] @@ -19,7 +21,7 @@ class ExclusiveCalculator { process() { while (this.toProcess.length) { const segment = this.toProcess.pop() - const children = segment.getChildren() + const children = this.trace.getChildren(segment.id) // when we hit a leaf, calc the exclusive time and report the time // range to the parent if (children.length === 0) { diff --git a/lib/transaction/trace/index.js b/lib/transaction/trace/index.js index c7c22121c1..da4c1dceb2 100644 --- a/lib/transaction/trace/index.js +++ b/lib/transaction/trace/index.js @@ -41,7 +41,7 @@ function Trace(transaction) { this.root.start() this.intrinsics = Object.create(null) - this.segmentsSeen = 0 + this.segments = [] this.totalTimeCache = null this.custom = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES) @@ -63,16 +63,11 @@ function Trace(transaction) { * segments that support recording. */ Trace.prototype.end = function end() { - const segments = [this.root] + this.root.finalize(this) + const segments = this.segments - while (segments.length) { - const segment = segments.pop() - segment.finalize() - - const children = segment.getChildren() - for (let i = 0; i < children.length; ++i) { - segments.push(children[i]) - } + for (let i = 0; i < segments.length; i++) { + segments[i].finalize(this) } } @@ -85,14 +80,13 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() { if (!shouldGenerateSpanEvents(config, this.transaction)) { return } - const toProcess = [] // Root segment does not become a span, so we need to process it separately. const spanAggregator = this.transaction.agent.spanEventAggregator - const children = this.root.getChildren() + const segments = this.segments - if (children.length > 0) { + if (segments.length > 0) { // At the point where these attributes are available, we only have a // root span. Adding attributes to first non-root span here. const attributeMap = { @@ -106,41 +100,28 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() { for (const [key, value] of Object.entries(attributeMap)) { if (value !== null) { - children[0].addSpanAttribute(key, value) + segments[0].addSpanAttribute(key, value) } } } - for (let i = 0; i < children.length; ++i) { - toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId, true)) - } - - while (toProcess.length) { - const segmentInfo = toProcess.pop() - const segment = segmentInfo.segment - + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i] + const isRoot = segment.parentId === this.root.id + const parentId = isRoot ? this.transaction.parentSpanId : segment.parentId // Even though at some point we might want to stop adding events because all the priorities // should be the same, we need to count the spans as seen. spanAggregator.addSegment({ segment, transaction: this.transaction, - parentId: segmentInfo.parentId, - isRoot: segmentInfo.isRoot + parentId, + isRoot }) - - const nodes = segment.getChildren() - for (let i = 0; i < nodes.length; ++i) { - const node = new DTTraceNode(nodes[i], segment.id) - toProcess.push(node) - } } } function shouldGenerateSpanEvents(config, txn) { - if (!config.distributed_tracing.enabled) { - return false - } - if (!config.span_events.enabled) { + if (!(config.distributed_tracing.enabled && config.span_events.enabled)) { return false } @@ -148,12 +129,6 @@ function shouldGenerateSpanEvents(config, txn) { return infiniteTracingConfigured || txn.sampled } -function DTTraceNode(segment, parentId, isRoot = false) { - this.segment = segment - this.parentId = parentId - this.isRoot = isRoot -} - /** * Add a child to the list of segments. * @@ -220,7 +195,7 @@ Trace.prototype.addCustomAttribute = function addCustomAttribute(key, value) { * traces, in milliseconds. */ Trace.prototype.getExclusiveDurationInMillis = function getExclusiveDurationInMillis() { - return this.root.getExclusiveDurationInMillis() + return this.root.getExclusiveDurationInMillis(this) } /** @@ -234,16 +209,14 @@ Trace.prototype.getTotalTimeDurationInMillis = function getTotalTimeDurationInMi if (this.totalTimeCache !== null) { return this.totalTimeCache } - if (this.root.children.length === 0) { + const segments = this.segments + if (segments.length === 0) { return 0 } - const segments = this.root.getChildren() - let totalTimeInMillis = 0 - while (segments.length !== 0) { - const segment = segments.pop() - totalTimeInMillis += segment.getExclusiveDurationInMillis() - segment.getChildren().forEach((childSegment) => segments.push(childSegment)) + let totalTimeInMillis = 0 + for (let i = 0; i < segments.length; i++) { + totalTimeInMillis += segments[i].getExclusiveDurationInMillis(this) } if (!this.transaction.isActive()) { @@ -366,6 +339,104 @@ Trace.prototype._getRequestUri = function _getRequestUri() { return requestUri } +/** + * Gets all children of a segment. + * + * @param {number} id of segment + * @returns {Array.} list of all segments that have the parentId of the segment + */ +Trace.prototype.getChildren = function getChildren(id) { + return this.segments.filter((segment) => segment.parentId === id) +} + +/** + * Gets all children of a segment that should be collected and not ignored. + * + * @param {number} id of segment + * @returns {Array.} list of all segments that have the parentId of the segment + */ +Trace.prototype.getCollectedChildren = function getCollectedChildren(id) { + return this.segments.filter( + (segment) => segment.parentId === id && segment._collect && !segment.ignore + ) +} + +/** + * Gets the parent segment from list of segments on trace by passing in the `parentId` + * and matching on the `segment.id` + * + * @param {number} parentId id of parent segment you want to retrieve + * @returns {TraceSegment} parent segment + */ +Trace.prototype.getParent = function getParent(parentId) { + return this.segments.filter((segment) => segment.id === parentId)[0] +} + +/** + * This is perhaps the most poorly-documented element of transaction traces: + * what do each of the segment representations look like prior to encoding? + * Spelunking in the code for the other agents has revealed that each child + * node is an array with the following field in the following order: + * + * 0: entry timestamp relative to transaction start time + * 1: exit timestamp + * 2: metric name + * 3: parameters as a name -> value JSON dictionary + * 4: any child segments + * + * Other agents include further fields in this. I haven't gotten to the bottom + * of all of them (and Ruby, of course, sends marshalled Ruby object), but + * here's what I know so far: + * + * in Java: + * 5: class name + * 6: method name + * + * in Python: + * 5: a "label" + * + * FIXME: I don't know if it makes sense to add custom fields for Node. TBD + */ +Trace.prototype.toJSON = function toJSON() { + // use depth-first search on the segment tree using stack + const resultDest = [] + // array of objects relating a segment and the destination for its + // serialized data. + const segmentsToProcess = [ + { + segment: this.root, + destination: resultDest + } + ] + + while (segmentsToProcess.length !== 0) { + const { segment, destination } = segmentsToProcess.pop() + const start = segment.timer.startedRelativeTo(this.root.timer) + const duration = segment.getDurationInMillis() + + const segmentChildren = this.getCollectedChildren(segment.id) + const childArray = [] + + // push serialized data into the specified destination + destination.push([start, start + duration, segment.name, segment.getAttributes(), childArray]) + + if (segmentChildren.length) { + // push the children and the parent's children array into the stack. + // to preserve the chronological order of the children, push them + // onto the stack backwards (so the first one created is on top). + for (let i = segmentChildren.length - 1; i >= 0; --i) { + segmentsToProcess.push({ + segment: segmentChildren[i], + destination: childArray + }) + } + } + } + + // pull the result out of the array we serialized it into + return resultDest[0] +} + /** * Serializes the trace into the expected JSON format to be sent. * @@ -379,17 +450,21 @@ Trace.prototype._serializeTrace = function _serializeTrace() { intrinsics: this.intrinsics } - return [ + const trace = [ this.root.timer.start * FROM_MILLIS, {}, // moved to agentAttributes { // hint to RPM for how to display this trace's segments nr_flatten_leading: false }, // moved to userAttributes - this.root.toJSON(), + this.toJSON(), attributes, [] // FIXME: parameter groups ] + + // clear out segments + this.segments = [] + return trace } module.exports = Trace diff --git a/lib/transaction/trace/segment.js b/lib/transaction/trace/segment.js index d80bfe96bd..de9d4a8552 100644 --- a/lib/transaction/trace/segment.js +++ b/lib/transaction/trace/segment.js @@ -6,7 +6,6 @@ 'use strict' const { DESTINATIONS } = require('../../config/attribute-filter') -const logger = require('../../logger').child({ component: 'segment' }) const Timer = require('../../timer') const hashes = require('../../util/hashes') @@ -34,20 +33,21 @@ const ATTRIBUTE_SCOPE = 'segment' * @param {object} params.config agent config * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express', * 'mysql', etc). + * @param {number} params.parentId parent id of segment * @param {boolean} params.collect flag to collect as part of transaction trace * @param {TraceSegment} params.root root segment * @param {boolean} params.isRoot flag to indicate it is the root segment */ -function TraceSegment({ config, name, collect, root, isRoot = false }) { +function TraceSegment({ config, name, collect, parentId, root, isRoot = false }) { this.isRoot = isRoot this.root = root this.name = name this.attributes = new Attributes(ATTRIBUTE_SCOPE) - this.children = [] this.spansEnabled = config?.distributed_tracing?.enabled && config?.span_events?.enabled // Generate a unique id for use in span events. this.id = hashes.makeId() + this.parentId = parentId this.timer = new Timer() this.internal = false @@ -173,7 +173,7 @@ TraceSegment.prototype.end = function end() { this._updateRootTimer() } -TraceSegment.prototype.finalize = function finalize() { +TraceSegment.prototype.finalize = function finalize(trace) { if (this.timer.softEnd()) { this._updateRootTimer() // timer.softEnd() returns true if the timer was ended prematurely, so @@ -181,7 +181,7 @@ TraceSegment.prototype.finalize = function finalize() { this.name = NAMES.TRUNCATED.PREFIX + this.name } - this.addAttribute('nr_exclusive_duration_millis', this.getExclusiveDurationInMillis()) + this.addAttribute('nr_exclusive_duration_millis', this.getExclusiveDurationInMillis(trace)) } /** @@ -206,36 +206,6 @@ TraceSegment.prototype._isEnded = function _isEnded() { return !this.timer.isActive() || this.timer.touched } -/** - * Add a new segment to a parent(scope) implicitly bounded by this segment. - * - * @param {object} params to function - * @param {object} params.config agent config - * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express', - * 'mysql', etc). - * @param {boolean} params.collect flag to collect as part of transaction trace - * @param {TraceSegment} params.root root segment - * @returns {TraceSegment} New nested TraceSegment. - */ -TraceSegment.prototype.add = function add({ config, name, collect, root }) { - // this is needed here to check when add is called directly on segment - if (this.opaque) { - logger.trace('Skipping child addition on opaque segment') - return this - } - - const segment = new TraceSegment({ config, name, collect, root }) - - this.children.push(segment) - - // This should only be used in testing - if (config.debug && config.debug.double_linked_transactions) { - segment.parent = this - } - - return segment -} - /** * Set the duration of the segment explicitly. * @@ -271,139 +241,15 @@ function _setExclusiveDurationInMillis(duration) { */ TraceSegment.prototype.getExclusiveDurationInMillis = getExclusiveDurationInMillis -function getExclusiveDurationInMillis() { +function getExclusiveDurationInMillis(trace) { if (this._exclusiveDuration == null) { // Calculate the exclusive time for the subtree rooted at `this` - const calculator = new ExclusiveCalculator(this) + const calculator = new ExclusiveCalculator(this, trace) calculator.process() } return this._exclusiveDuration } -TraceSegment.prototype.getChildren = function getChildren() { - const children = [] - for (let i = 0, len = this.children.length; i < len; ++i) { - if (!this.children[i].ignore) { - children.push(this.children[i]) - } - } - return children -} - -TraceSegment.prototype.getCollectedChildren = function getCollectedChildren() { - const children = [] - for (let i = 0, len = this.children.length; i < len; ++i) { - if (this.children[i]._collect && !this.children[i].ignore) { - children.push(this.children[i]) - } - } - return children -} - -/** - * Enumerate the timings of this segment's descendants. - * - * @param {number} end The end of this segment, to keep the calculated - * duration from exceeding the duration of the - * parent. Defaults to Infinity. - * @returns {Array} Unsorted list of [start, end] pairs, with no pair - * having an end greater than the passed in end time. - */ -TraceSegment.prototype._getChildPairs = function _getChildPairs(end) { - // quick optimization - if (this.children.length < 1) { - return [] - } - if (!end) { - end = Infinity - } - - let children = this.getChildren() - const childPairs = [] - while (children.length) { - const child = children.pop() - const pair = child.timer.toRange() - - if (pair[0] >= end) { - continue - } - - children = children.concat(child.getChildren()) - - pair[1] = Math.min(pair[1], end) - childPairs.push(pair) - } - - return childPairs -} - -/** - * This is perhaps the most poorly-documented element of transaction traces: - * what do each of the segment representations look like prior to encoding? - * Spelunking in the code for the other agents has revealed that each child - * node is an array with the following field in the following order: - * - * 0: entry timestamp relative to transaction start time - * 1: exit timestamp - * 2: metric name - * 3: parameters as a name -> value JSON dictionary - * 4: any child segments - * - * Other agents include further fields in this. I haven't gotten to the bottom - * of all of them (and Ruby, of course, sends marshalled Ruby object), but - * here's what I know so far: - * - * in Java: - * 5: class name - * 6: method name - * - * in Python: - * 5: a "label" - * - * FIXME: I don't know if it makes sense to add custom fields for Node. TBD - */ -TraceSegment.prototype.toJSON = function toJSON() { - const root = this.isRoot ? this : this.root - // use depth-first search on the segment tree using stack - const resultDest = [] - // array of objects relating a segment and the destination for its - // serialized data. - const segmentsToProcess = [ - { - segment: this, - destination: resultDest - } - ] - - while (segmentsToProcess.length !== 0) { - const { segment, destination } = segmentsToProcess.pop() - - const start = segment.timer.startedRelativeTo(root.timer) - const duration = segment.getDurationInMillis() - - const segmentChildren = segment.getCollectedChildren() - const childArray = [] - - // push serialized data into the specified destination - destination.push([start, start + duration, segment.name, segment.getAttributes(), childArray]) - - if (segmentChildren.length) { - // push the children and the parent's children array into the stack. - // to preserve the chronological order of the children, push them - // onto the stack backwards (so the first one created is on top). - for (let i = segmentChildren.length - 1; i >= 0; --i) { - segmentsToProcess.push({ - segment: segmentChildren[i], - destination: childArray - }) - } - } - } - - // pull the result out of the array we serialized it into - return resultDest[0] -} - /** * Adds all the relevant segment attributes for an External http request * diff --git a/lib/transaction/tracer/index.js b/lib/transaction/tracer/index.js index 72e789d1bf..f61330880d 100644 --- a/lib/transaction/tracer/index.js +++ b/lib/transaction/tracer/index.js @@ -13,6 +13,7 @@ const SKIP_WRAPPING_FUNCTION_MESSAGE = 'Not wrapping "%s" because it was not a f const CREATE_SEGMENT_MESSAGE = 'Creating "%s" segment for transaction %s.' const { addCLMAttributes: maybeAddCLMAttributes } = require('../../util/code-level-metrics') const AsyncLocalContextManager = require('../../context-manager/async-local-context-manager') +const TraceSegment = require('../trace/segment') module.exports = Tracer @@ -101,22 +102,24 @@ function createSegment({ name, recorder, parent, transaction }) { logger.trace('Adding segment %s to %s in %s', name, parent.name, transaction.id) let collect = true - if (transaction.trace.segmentsSeen++ >= this.agent.config.max_trace_segments) { + if (transaction.trace.segments.length >= this.agent.config.max_trace_segments) { collect = false } transaction.incrementCounters() - const segment = parent.add({ + const segment = new TraceSegment({ config: this.agent.config, name, collect, - root: transaction.trace.root + root: transaction.trace.root, + parentId: parent.id }) if (recorder) { transaction.addRecorder(recorder.bind(null, segment)) } + transaction.trace.segments.push(segment) return segment } diff --git a/test/benchmark/shim/segments.bench.js b/test/benchmark/shim/segments.bench.js index 095f656539..f747b9bcbb 100644 --- a/test/benchmark/shim/segments.bench.js +++ b/test/benchmark/shim/segments.bench.js @@ -94,7 +94,6 @@ suite.add({ fn: function () { const test = shared.getTest() shim.createSegment('foo', test.func, tx.trace.root) - tx.trace.root.children = [] return test } }) diff --git a/test/benchmark/trace/segment.bench.js b/test/benchmark/trace/segment.bench.js index 6794b9f155..579748dca9 100644 --- a/test/benchmark/trace/segment.bench.js +++ b/test/benchmark/trace/segment.bench.js @@ -7,26 +7,20 @@ const helper = require('../../lib/agent_helper') const benchmark = require('../../lib/benchmark') -const Segment = require('../../../lib/transaction/trace/segment') +const Transaction = require('../../../lib/transaction') const agent = helper.loadMockedAgent() const suite = benchmark.createBenchmark({ name: 'trace segments' }) -let root - -function addChildren(rootSegment, numChildren) { - const queue = [rootSegment] +let trace +function addChildren(trace, numChildren) { + const queue = [trace.root] for (let numSegments = 1; numSegments < 900; numSegments += numChildren) { const parent = queue.shift() for (let i = 0; i < numChildren; ++i) { - const child = parent.add({ - name: 'child ' + (numSegments + i), - root: rootSegment, - collect: true, - config: agent.config - }) + const child = trace.add('child ' + (numSegments + i), null, parent) child.timer.setDurationInMillis( (0.99 + Math.random() / 100) * parent.timer.durationInMillis, parent.timer.start + 1 @@ -40,12 +34,13 @@ suite.add({ name: 'toJSON flat', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 899) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 899) }, fn: function () { - return root.toJSON() + return trace.toJSON() } }) @@ -53,12 +48,13 @@ suite.add({ name: 'toJSON linear', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 1) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 1) }, fn: function () { - return root.toJSON() + return trace.toJSON() } }) @@ -66,12 +62,13 @@ suite.add({ name: 'toJSON binary', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 2) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 2) }, fn: function () { - return root.toJSON() + return trace.toJSON() } }) @@ -79,12 +76,13 @@ suite.add({ name: 'getExclusiveDurationInMillis flat', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 899) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 899) }, fn: function () { - return root.getExclusiveDurationInMillis() + return trace.getExclusiveDurationInMillis() } }) @@ -92,12 +90,13 @@ suite.add({ name: 'getExclusiveDurationInMillis linear', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 1) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 1) }, fn: function () { - return root.getExclusiveDurationInMillis() + return trace.getExclusiveDurationInMillis() } }) @@ -105,12 +104,13 @@ suite.add({ name: 'getExclusiveDurationInMillis binary', before: function buildTree() { - root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) - root.timer.setDurationInMillis(10000, Date.now()) - addChildren(root, 2) + const transaction = new Transaction(agent) + trace = transaction.trace + trace.root.timer.setDurationInMillis(10000, Date.now()) + addChildren(trace, 2) }, fn: function () { - return root.getExclusiveDurationInMillis() + return trace.getExclusiveDurationInMillis() } }) diff --git a/test/integration/cat/cat.test.js b/test/integration/cat/cat.test.js index d3faf8ec0e..c839171ad6 100644 --- a/test/integration/cat/cat.test.js +++ b/test/integration/cat/cat.test.js @@ -172,8 +172,8 @@ test('cross application tracing full integration', async function (t) { ) // check the external segment for its properties - const externalSegment = - trace.root.children[0].children[trace.root.children[0].children.length - 1] + const [webSegment] = trace.getChildren(trace.root.id) + const [externalSegment] = trace.getChildren(webSegment.id) plan.equal( externalSegment.name.split('/')[0], 'ExternalTransaction', @@ -246,8 +246,8 @@ test('cross application tracing full integration', async function (t) { ) // check the external segment for its properties - const externalSegment = - trace.root.children[0].children[trace.root.children[0].children.length - 1] + const [webSegment] = trace.getChildren(trace.root.id) + const [externalSegment] = trace.getChildren(webSegment.id) plan.equal( externalSegment.name.split('/')[0], 'ExternalTransaction', diff --git a/test/integration/core/crypto.test.js b/test/integration/core/crypto.test.js index 5ebcd35d4d..4c2a40fb14 100644 --- a/test/integration/core/crypto.test.js +++ b/test/integration/core/crypto.test.js @@ -26,6 +26,7 @@ test('pbkdf2', function (t, end) { crypto.pbkdf2('hunter2', 'saltine', 5, 32, 'sha1', function (err, key) { assert.ok(!err, 'should not error') assert.equal(key.length, 32) + debugger verifySegments({ agent, end, name: 'crypto.pbkdf2' }) }) }) @@ -48,7 +49,8 @@ test('sync randomBytes', function (t, end) { const bytes = crypto.randomBytes(32) assert.ok(bytes instanceof Buffer) assert.equal(bytes.length, 32) - assert.equal(transaction.trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) end() }) }) @@ -72,7 +74,8 @@ test('sync pseudoRandomBytes', function (t, end) { const bytes = crypto.pseudoRandomBytes(32) assert.ok(bytes instanceof Buffer) assert.equal(bytes.length, 32) - assert.equal(transaction.trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) end() }) }) @@ -96,7 +99,8 @@ test('sync randomFill', function (t, end) { crypto.randomFillSync(buf) assert.ok(buf instanceof Buffer) assert.equal(buf.length, 10) - assert.equal(transaction.trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) end() }) }) @@ -118,7 +122,8 @@ test('scryptSync', (t, end) => { const buf = crypto.scryptSync('secret', 'salt', 10) assert.ok(buf instanceof Buffer) assert.equal(buf.length, 10) - assert.equal(transaction.trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) end() }) }) diff --git a/test/integration/core/fs.test.js b/test/integration/core/fs.test.js index f5a5515369..e27e627319 100644 --- a/test/integration/core/fs.test.js +++ b/test/integration/core/fs.test.js @@ -852,7 +852,9 @@ test('read', async function (t) { plan.equal(len, 12, 'should read correct number of bytes') plan.equal(data.toString('utf8'), content) plan.equal(agent.getTransaction(), trans, 'should preserve transaction') - plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 0, 'should not create any segments') + }) }) @@ -875,7 +877,8 @@ test('write', async function (t) { plan.equal(len, 12, 'should write correct number of bytes') plan.equal(fs.readFileSync(name, 'utf8'), content) plan.equal(agent.getTransaction(), trans, 'should preserve transaction') - plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 0, 'should not create any segments') }) }) @@ -898,7 +901,8 @@ test('watch (file)', async function (t) { plan.equal(file, 'watch-file', 'should have correct file name') plan.equal(agent.getTransaction(), trans, 'should preserve transaction') - plan.equal(trans.trace.root.children.length, 1, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 1, 'should not create any segments') watcher.close() }) fs.writeFile(name, content + 'more', function (err) { @@ -923,7 +927,8 @@ test('watch (dir)', async function (t) { plan.equal(ev, 'rename') plan.equal(file, 'watch-dir') plan.equal(agent.getTransaction(), trans, 'should preserve transaction') - plan.equal(trans.trace.root.children.length, 1, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 1, 'should not create any segments') watcher.close() }) fs.writeFile(name, content, function (err) { @@ -951,9 +956,9 @@ test('watch emitter', async function (t) { plan.equal(file, 'watch', 'should be for correct directory') const tx = agent.getTransaction() - const root = trans.trace.root plan.equal(tx && tx.id, trans.id, 'should preserve transaction') - plan.equal(root.children.length, 1, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 1, 'should not create any segments') watcher.close() }) @@ -985,7 +990,8 @@ test('watchFile', async function (t) { plan.ok(cur.size > prev.size, 'content modified') plan.equal(agent.getTransaction(), trans, 'should preserve transaction') - plan.equal(trans.trace.root.children.length, 0, 'should not create any segments') + const children = trans.trace.getChildren(trans.trace.root.id) + plan.equal(children.length, 0, 'should not create any segments') fs.unwatchFile(name, onChange) } }) diff --git a/test/integration/core/net.tap.js b/test/integration/core/net.tap.js deleted file mode 100644 index b75da4afdc..0000000000 --- a/test/integration/core/net.tap.js +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const net = require('net') -const helper = require('../../lib/agent_helper') - -function id(tx) { - return tx && tx.id -} - -test('createServer', function createServerTest(t) { - const { agent, tracer } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - const server = net.createServer(handler) - - server.listen(4123, function listening() { - const socket = net.connect({ port: 4123 }) - socket.write('test123') - socket.end() - }) - - function handler(socket) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.end('test') - t.equal( - tracer.getSegment().name, - 'net.Server.onconnection', - 'child segment should have correct name' - ) - - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'test123') - socket.end() - setTimeout(server.close.bind(server, onClose), 0) - }) - } - - function onClose() { - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 2, 'should have a single child') - const child = root.children[1] - t.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name') - t.ok(child.timer.touched, 'child should started and ended') - t.equal(child.children.length, 1, 'child should have a single child segment') - const timeout = child.children[0] - t.equal(timeout.name, 'timers.setTimeout', 'timeout segment should have correct name') - t.ok(timeout.timer.touched, 'timeout should started and ended') - t.equal(timeout.children.length, 1, 'timeout should have a single callback segment') - t.end() - } - }) -}) - -test('connect', function connectTest(t) { - const { agent } = setupAgent(t) - - const server = net.createServer(function connectionHandler(socket) { - socket.on('data', function onData(data) { - t.equal(data.toString(), 'some data') - socket.end('end data') - }) - }) - - t.teardown(function () { - server.close() - }) - - server.listen(4123, function listening() { - helper.runInTransaction(agent, transactionWrapper) - }) - - function transactionWrapper(transaction) { - let count = 0 - const socket = net.createConnection({ port: 4123 }) - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'end data') - ++count - }) - socket.on('end', function onEnd() { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(count, 1) - setTimeout(verify, 0) - }) - - socket.on('connect', function onConnet() { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.write('some data') - socket.end() - }) - - function verify() { - if (!t.passing()) { - return t.end() - } - - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 1, 'should have a single child') - let connectSegment = root.children[0] - t.equal( - connectSegment.name, - 'net.createConnection', - 'connect segment should have correct name' - ) - t.ok(connectSegment.timer.touched, 'connect should started and ended') - - // Depending on the version of Node there may be another connection segment - // floating in the trace. - if (connectSegment.children[0].name === 'net.Socket.connect') { - connectSegment = connectSegment.children[0] - } - - t.equal(connectSegment.children.length, 2, 'connect should have a two child segment') - const dnsSegment = connectSegment.children[0] - const timeoutSegment = connectSegment.children[1] - - t.equal(dnsSegment.name, 'dns.lookup', 'dns segment should have correct name') - t.ok(dnsSegment.timer.touched, 'dns segment should started and ended') - t.equal(dnsSegment.children.length, 1, 'dns should have a single callback segment') - t.equal(timeoutSegment.name, 'timers.setTimeout', 'timeout segment should have correct name') - t.ok(timeoutSegment.timer.touched, 'timeout should started and ended') - t.equal(timeoutSegment.children.length, 1, 'timeout should have a single callback segment') - t.end() - } - } -}) - -test('createServer and connect', function createServerTest(t) { - const { agent, tracer } = setupAgent(t) - - helper.runInTransaction(agent, function transactionWrapper(transaction) { - const server = net.createServer(handler) - - server.listen(4123, function listening() { - const socket = net.connect({ port: 4123 }) - socket.write('test123') - socket.end() - }) - - function handler(socket) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - socket.end('test') - t.equal( - tracer.getSegment().name, - 'net.Server.onconnection', - 'child segment should have correct name' - ) - - socket.on('data', function onData(data) { - t.equal(id(agent.getTransaction()), id(transaction), 'should maintain tx') - t.equal(data.toString(), 'test123') - socket.end() - server.close(onClose) - }) - } - - function onClose() { - const root = agent.getTransaction().trace.root - t.equal(root.children.length, 2, 'should have 2 children') - let clientSegment = root.children[0] - t.equal(clientSegment.name, 'net.connect', 'server segment should have correct name') - t.ok(clientSegment.timer.touched, 'server should started and ended') - - // Depending on the version of Node there may be another connection segment - // floating in the trace. - if (clientSegment.children[0].name === 'net.Socket.connect') { - clientSegment = clientSegment.children[0] - } - - t.equal(clientSegment.children.length, 1, 'clientSegment should only have one child') - const dnsSegment = clientSegment.children[0] - if (dnsSegment) { - t.equal(dnsSegment.name, 'dns.lookup', 'dnsSegment is named properly') - } else { - t.fail('did not have children, prevent undefined property lookup') - } - - const serverSegment = root.children[1] - t.equal( - serverSegment.name, - 'net.Server.onconnection', - 'server segment should have correct name' - ) - t.ok(serverSegment.timer.touched, 'server should started and ended') - t.equal(serverSegment.children.length, 0, 'should not have any server segments') - t.end() - } - }) -}) - -function setupAgent(t) { - const agent = helper.instrumentMockedAgent() - const tracer = helper.getTracer() - - t.teardown(function tearDown() { - helper.unloadAgent(agent) - }) - - return { - agent, - tracer - } -} diff --git a/test/integration/core/net.test.js b/test/integration/core/net.test.js index 33fd24cf7c..27eb99e25e 100644 --- a/test/integration/core/net.test.js +++ b/test/integration/core/net.test.js @@ -54,16 +54,18 @@ test('createServer', function createServerTest(t, end) { } function onClose() { - const root = agent.getTransaction().trace.root - assert.equal(root.children.length, 2, 'should have a single child') - const child = root.children[1] + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 2, 'should have a single child') + const child = children[1] + const childChildren = transaction.trace.getChildren(child.id) assert.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name') assert.ok(child.timer.touched, 'child should started and ended') - assert.equal(child.children.length, 1, 'child should have a single child segment') - const timeout = child.children[0] + assert.equal(childChildren.length, 1, 'child should have a single child segment') + const timeout = childChildren[0] + const timeoutChildren = transaction.trace.getChildren(timeout.id) assert.equal(timeout.name, 'timers.setTimeout', 'timeout segment should have correct name') assert.ok(timeout.timer.touched, 'timeout should started and ended') - assert.equal(timeout.children.length, 1, 'timeout should have a single callback segment') + assert.equal(timeoutChildren.length, 1, 'timeout should have a single callback segment') end() } }) @@ -108,37 +110,41 @@ test('connect', function connectTest(t, end) { }) function verify() { - const root = agent.getTransaction().trace.root - assert.equal(root.children.length, 1, 'should have a single child') - let connectSegment = root.children[0] + const transaction = agent.getTransaction() + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 1, 'should have a single child') + let connectSegment = children[0] assert.equal( connectSegment.name, 'net.createConnection', 'connect segment should have correct name' ) assert.ok(connectSegment.timer.touched, 'connect should started and ended') + let connectChildren = transaction.trace.getChildren(connectSegment.id) // Depending on the version of Node there may be another connection segment // floating in the trace. - if (connectSegment.children[0].name === 'net.Socket.connect') { - connectSegment = connectSegment.children[0] + if (connectChildren[0].name === 'net.Socket.connect') { + connectSegment = connectChildren[0] } + connectChildren = transaction.trace.getChildren(connectSegment.id) - assert.equal(connectSegment.children.length, 2, 'connect should have a two child segment') - const dnsSegment = connectSegment.children[0] - const timeoutSegment = connectSegment.children[1] + assert.equal(connectChildren.length, 2, 'connect should have a two child segment') + const [dnsSegment, timeoutSegment] = connectChildren assert.equal(dnsSegment.name, 'dns.lookup', 'dns segment should have correct name') assert.ok(dnsSegment.timer.touched, 'dns segment should started and ended') - assert.equal(dnsSegment.children.length, 1, 'dns should have a single callback segment') + const dnsChildren = transaction.trace.getChildren(dnsSegment.id) + assert.equal(dnsChildren.length, 1, 'dns should have a single callback segment') assert.equal( timeoutSegment.name, 'timers.setTimeout', 'timeout segment should have correct name' ) assert.ok(timeoutSegment.timer.touched, 'timeout should started and ended') + const timeoutChildren = transaction.trace.getChildren(timeoutSegment.id) assert.equal( - timeoutSegment.children.length, + timeoutChildren.length, 1, 'timeout should have a single callback segment' ) @@ -177,34 +183,38 @@ test('createServer and connect', function createServerTest(t, end) { } function onClose() { - const root = agent.getTransaction().trace.root - assert.equal(root.children.length, 2, 'should have 2 children') - let clientSegment = root.children[0] + const transaction = agent.getTransaction() + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 2, 'should have 2 children') + let clientSegment = children[0] assert.equal(clientSegment.name, 'net.connect', 'server segment should have correct name') assert.ok(clientSegment.timer.touched, 'server should started and ended') + let clientChildren = transaction.trace.getChildren(clientSegment.id) // Depending on the version of Node there may be another connection segment // floating in the trace. - if (clientSegment.children[0].name === 'net.Socket.connect') { - clientSegment = clientSegment.children[0] + if (clientChildren[0].name === 'net.Socket.connect') { + clientSegment = clientChildren[0] } + clientChildren = transaction.trace.getChildren(clientSegment.id) - assert.equal(clientSegment.children.length, 1, 'clientSegment should only have one child') - const dnsSegment = clientSegment.children[0] + assert.equal(clientChildren.length, 1, 'clientSegment should only have one child') + const [dnsSegment ] = clientChildren if (dnsSegment) { assert.equal(dnsSegment.name, 'dns.lookup', 'dnsSegment is named properly') } else { assert.ok(0, 'did not have children, prevent undefined property lookup') } - const serverSegment = root.children[1] + const serverSegment = children[1] assert.equal( serverSegment.name, 'net.Server.onconnection', 'server segment should have correct name' ) assert.ok(serverSegment.timer.touched, 'server should started and ended') - assert.equal(serverSegment.children.length, 0, 'should not have any server segments') + const serverChildren = transaction.trace.getChildren(serverSegment.id) + assert.equal(serverChildren.length, 0, 'should not have any server segments') end() } }) diff --git a/test/integration/core/timers.test.js b/test/integration/core/timers.test.js index 205378cb20..c85bc06ea5 100644 --- a/test/integration/core/timers.test.js +++ b/test/integration/core/timers.test.js @@ -36,10 +36,9 @@ test('setImmediate: segments', async function (t) { helper.runInTransaction(agent, function transactionWrapper(tx) { timers.setImmediate(function anonymous() { plan.equal(agent.getTransaction().id, tx.id, 'should be in expected transaction') - plan.ok( - !agent.getTransaction().trace.root.children.length, - 'should not have any segment for setImmediate' - ) + const transaction = agent.getTransaction() + const children = transaction.trace.getChildren(transaction.trace.root.id) + plan.equal(children.length, 0) }) }) @@ -118,15 +117,13 @@ test('setImmediate: should not propagate segments for ended transaction', async helper.runInSegment(agent, 'test-segment', () => { const segment = agent.tracer.getSegment() plan.notEqual(segment.name, 'test-segment') - plan.equal(segment.children.length, 0, 'should not propagate segments when transaction ends') + const children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 0, 'should not propagate segments when transaction ends') setImmediate(() => { const segment = agent.tracer.getSegment() plan.notEqual(segment.name, 'test-segment') - plan.equal( - segment.children.length, - 0, - 'should not propagate segments when transaction ends' - ) + const children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 0, 'should not propagate segments when transaction ends') }) }) }) @@ -158,7 +155,8 @@ test('global setImmediate', function testSetImmediate(t, end) { helper.runInTransaction(agent, function transactionWrapper(transaction) { setImmediate(function anonymous() { assert.equal(agent.getTransaction(), transaction) - assert.equal(agent.getTransaction().trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) end() }) }) @@ -180,7 +178,8 @@ test('nextTick', async function testNextTick(t) { helper.runInTransaction(agent, function transactionWrapper(transaction) { process.nextTick(function callback() { plan.equal(agent.getTransaction(), transaction) - plan.equal(agent.getTransaction().trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + plan.equal(children.length, 0) }) }) @@ -196,7 +195,8 @@ test('nextTick with extra args', async function testNextTick(t) { process.nextTick( function callback() { plan.equal(agent.getTransaction(), transaction) - plan.equal(agent.getTransaction().trace.root.children.length, 0) + const children = transaction.trace.getChildren(transaction.trace.root.id) + plan.equal(children.length, 0) plan.deepEqual([].slice.call(arguments), [1, 2, 3]) process.nextTick = original }, @@ -225,7 +225,8 @@ test('clearImmediate', (t, end) => { helper.runInTransaction(agent, function transactionWrapper(transaction) { process.nextTick(function callback() { const timer2 = setImmediate(assert.fail) - assert.ok(!transaction.trace.root.children[0]) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 0) clearImmediate(timer2) setImmediate(end) }) @@ -248,7 +249,7 @@ test('clearTimeout should ignore segment created for timer', async (t) => { process.nextTick(function callback() { const timer = setTimeout(plan.fail) - const timerSegment = transaction.trace.root.children[0] + const [timerSegment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal(timerSegment.name, 'timers.setTimeout') plan.equal(timerSegment.ignore, false) @@ -272,7 +273,7 @@ test('clearTimeout should not ignore parent segment when opaque', async (t) => { segment.opaque = true const timer = setTimeout(plan.fail) - const parentSegment = transaction.trace.root.children[0] + const [parentSegment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal(parentSegment.name, expectedParentName) plan.equal(parentSegment.ignore, false) @@ -298,7 +299,7 @@ test('clearTimeout should not ignore parent segment when internal', async (t) => const timer = setTimeout(plan.fail) - const parentSegment = transaction.trace.root.children[0] + const [parentSegment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal(parentSegment.name, expectedParentName) plan.equal(parentSegment.ignore, false) diff --git a/test/integration/core/verify.js b/test/integration/core/verify.js index f2f0a1ea0a..bfcb5c1856 100644 --- a/test/integration/core/verify.js +++ b/test/integration/core/verify.js @@ -8,22 +8,24 @@ module.exports = verifySegments function verifySegments({ agent, name, children = [], end, assert = require('node:assert') }) { - const root = agent.getTransaction().trace.root - assert.equal(root.children.length, 1, 'should have a single child') - const child = root.children[0] + const { trace } = agent.getTransaction() + const traceChildren = trace.getChildren(trace.root.id) + assert.equal(traceChildren.length, 1, 'should have a single child') + const child = traceChildren[0] + const childChildren = trace.getChildren(child.id) assert.equal(child.name, name, 'child segment should have correct name') assert.ok(child.timer.touched, 'child should started and ended') assert.equal( - child.children.length, + childChildren.length, 1 + children.length, 'child should have a single callback segment' ) for (let i = 0; i < children.length; ++i) { - assert.equal(child.children[i].name, children[i]) + assert.equal(childChildren[i].name, children[i]) } - const callback = child.children[child.children.length - 1] + const callback = childChildren[childChildren.length - 1] assert.ok( callback.name === 'Callback: anonymous' || callback.name === 'Callback: ', 'callback segment should have correct name' diff --git a/test/integration/instrumentation/fetch.test.js b/test/integration/instrumentation/fetch.test.js index 96ff6e8192..668ef0700d 100644 --- a/test/integration/instrumentation/fetch.test.js +++ b/test/integration/instrumentation/fetch.test.js @@ -79,7 +79,7 @@ test('fetch', async function (t) { }) assert.equal(status, 200) - assertSegments(tx.trace.root, [`External/${HOST}/post`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`], { exact: false }) tx.end() }) }) @@ -88,7 +88,7 @@ test('fetch', async function (t) { await helper.runInTransaction(agent, async (tx) => { const { status } = await fetch(`${REQUEST_URL}/get?a=b&c=d`) assert.equal(status, 200) - const segment = metrics.findSegment(tx.trace.root, `External/${HOST}/get`) + const segment = metrics.findSegment(tx.trace, tx.trace.root, `External/${HOST}/get`) const attrs = segment.getAttributes() assert.equal(attrs.url, `${REQUEST_URL}/get`) assert.equal(attrs.procedure, 'GET') @@ -144,7 +144,7 @@ test('fetch', async function (t) { const [{ status }, { status: status2 }] = await Promise.all([req1, req2]) assert.equal(status, 200) assert.equal(status2, 200) - assertSegments(tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], { + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], { exact: false }) tx.end() @@ -159,7 +159,7 @@ test('fetch', async function (t) { }) } catch (err) { assert.equal(err.message, 'fetch failed') - assertSegments(tx.trace.root, ['External/invalidurl/foo'], { exact: false }) + assertSegments(tx.trace, tx.trace.root, ['External/invalidurl/foo'], { exact: false }) assert.equal(tx.exceptions.length, 1) tx.end() } @@ -179,7 +179,7 @@ test('fetch', async function (t) { await req } catch (err) { assert.match(err.message, /This operation was aborted/) - assertSegments(tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false }) assert.equal(tx.exceptions.length, 1) assert.equal(tx.exceptions[0].error.name, 'AbortError') tx.end() @@ -206,11 +206,11 @@ test('fetch', async function (t) { await req } catch (error) { assert.match(error.message, /fetch failed/) - assertSegments(transaction.trace.root, [`External/localhost:${port}/`], { + assertSegments(transaction.trace, transaction.trace.root, [`External/localhost:${port}/`], { exact: false }) - const segments = transaction.trace.root.children + const segments = transaction.trace.getChildren(transaction.trace.root.id) const segment = segments[segments.length - 1] assert.ok(segment.timer.start, 'should have started') @@ -225,7 +225,7 @@ test('fetch', async function (t) { await helper.runInTransaction(agent, async (tx) => { const { status } = await fetch(`${REQUEST_URL}/status/400`) assert.equal(status, 400) - assertSegments(tx.trace.root, [`External/${HOST}/status/400`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/status/400`], { exact: false }) tx.end() }) }) diff --git a/test/integration/instrumentation/http-outbound.test.js b/test/integration/instrumentation/http-outbound.test.js index 2dd1e1ddf0..d099fa6df4 100644 --- a/test/integration/instrumentation/http-outbound.test.js +++ b/test/integration/instrumentation/http-outbound.test.js @@ -36,11 +36,11 @@ test('external requests', async function (t) { notVeryReliable.listen(0) - helper.runInTransaction(agent, function inTransaction() { + helper.runInTransaction(agent, function inTransaction(tx) { const req = http.get(notVeryReliable.address()) req.on('error', function onError() { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal( segment.name, @@ -86,7 +86,7 @@ test('external requests', async function (t) { }) function check(tx) { - const external = tx.trace.root.children[0] + const [external] = tx.trace.getChildren(tx.trace.root.id) assert.equal( external.name, 'External/localhost:' + server.address().port + '/some/path', @@ -94,21 +94,24 @@ test('external requests', async function (t) { ) assert.ok(external.timer.start, 'should have started') assert.ok(external.timer.hasEnd(), 'should have ended') - assert.ok(external.children.length, 'should have children') + const externalChildren = tx.trace.getChildren(external.id) + assert.ok(externalChildren.length, 'should have children') - let connect = external.children[0] + let connect = externalChildren[0] assert.equal(connect.name, 'http.Agent#createConnection', 'should be connect segment') - assert.equal(connect.children.length, 1, 'connect should have 1 child') + let connectChildren = tx.trace.getChildren(connect.id) + assert.equal(connectChildren.length, 1, 'connect should have 1 child') // There is potentially an extra layer of create/connect segments. - if (connect.children[0].name === 'net.Socket.connect') { - connect = connect.children[0] + if (connectChildren[0].name === 'net.Socket.connect') { + connect = connectChildren[0] } + connectChildren = tx.trace.getChildren(connect.id) - const dnsLookup = connect.children[0] + const dnsLookup = connectChildren[0] assert.equal(dnsLookup.name, 'dns.lookup', 'should be dns.lookup segment') - const callback = external.children[external.children.length - 1] + const callback = externalChildren[externalChildren.length - 1] assert.equal(callback.name, 'timers.setTimeout', 'should have timeout segment') end() @@ -138,7 +141,8 @@ test('external requests', async function (t) { const req = http.get(opts, function onResponse(res) { res.resume() res.once('end', function () { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const { trace } = agent.tracer.getTransaction() + const [segment] = trace.getChildren(trace.root.id) assert.equal( segment.name, 'External/www.google.com/proxy/path', @@ -167,15 +171,16 @@ test('external requests', async function (t) { }) function check() { - const root = agent.tracer.getTransaction().trace.root - const segment = root.children[0] + const tx = agent.getTransaction() + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal(segment.name, 'External/example.com/', 'should be named') assert.ok(segment.timer.start, 'should have started') assert.ok(segment.timer.hasEnd(), 'should have ended') - assert.equal(segment.children.length, 1, 'should have 1 child') + const segmentChildren = tx.trace.getChildren(segment.id) + assert.equal(segmentChildren.length, 1, 'should have 1 child') - const notDuped = segment.children[0] + const notDuped = segmentChildren[0] assert.notEqual( notDuped.name, segment.name, @@ -214,7 +219,7 @@ test('external requests', async function (t) { http.get('http://example.com', (res) => { res.resume() res.on('end', () => { - const segment = tx.trace.root.children[0] + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal(segment.name, 'External/example.com/', 'should create external segment') end() }) @@ -229,7 +234,7 @@ test('external requests', async function (t) { const req = http.get('http://example.com', (res) => { res.resume() res.on('end', () => { - const segment = tx.trace.root.children[0] + const [segment] = tx.trace.getChildren(tx.trace.root.id) const attrs = segment.getAttributes() assert.deepEqual(attrs, { url: 'http://example.com/', diff --git a/test/integration/transaction/tracer.test.js b/test/integration/transaction/tracer.test.js index 0953f827b9..31e61becfb 100644 --- a/test/integration/transaction/tracer.test.js +++ b/test/integration/transaction/tracer.test.js @@ -280,9 +280,12 @@ test('getSegment', async function testGetTransaction(t) { plan.equal(tracer.getSegment(), root) setTimeout(function onTimeout() { - const segment = root.children[0].children[0] - plan.equal(tracer.getSegment(), segment) - plan.equal(tracer.getSegment().name, 'Callback: onTimeout') + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + const [setTimeoutCb] = transaction.trace.getChildren(child.id) + const segment = tracer.getSegment() + + plan.equal(segment, setTimeoutCb) + plan.equal(segment.name, 'Callback: onTimeout') }, 0) }) @@ -369,12 +372,15 @@ test('addSegment', async function addSegmentTest(t) { plan.equal(segment.name, 'inside') root = transaction.trace.root - plan.equal(root.children[0], segment) + let [child] = transaction.trace.getChildren(root.id) + plan.equal(child, segment) + const outside = tracer.addSegment('outside', null, root, false, check) + ;[, child] = transaction.trace.getChildren(root.id) plan.equal(outside.name, 'outside') - plan.equal(root.children[1], outside) + plan.equal(child, outside) }) function check(segment) { @@ -397,7 +403,9 @@ test('addSegment + recorder', async function addSegmentTest(t) { plan.equal(segment.name, 'inside') plan.equal(segment.timer.hrDuration, null) - plan.equal(root.children[0], segment) + const [child] = transaction.trace.getChildren(root.id) + plan.equal(child, segment) + transaction.end() }) @@ -427,7 +435,9 @@ test('addSegment + full', async function addSegmentTest(t) { plan.equal(segment.name, 'inside') plan.ok(segment.timer.hrDuration) - plan.equal(root.children[0], segment) + const [child] = transaction.trace.getChildren(root.id) + plan.equal(child, segment) + transaction.end() }) @@ -610,12 +620,14 @@ test('wrapFunction', async function testwrapFunction(t) { function makeCallback(val) { return function callback(parent, arg) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() plan.equal(arg, val) plan.equal(this, inner) if (parent) { + const children = transaction.trace.getChildren(parent.id) plan.ok(segment.timer.hrstart) plan.ok(!segment.timer.hrDuration) - plan.notEqual(parent.children.indexOf(segment), -1) + plan.notEqual(children.indexOf(segment), -1) } return val @@ -636,8 +648,10 @@ test('wrapFunction', async function testwrapFunction(t) { plan.equal(this, outer) process.nextTick(function next() { + let children if (segment) { - plan.equal(segment.children.length, 0) + children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 0) } plan.equal(a.call(inner, segment, 'a'), 'a') @@ -645,7 +659,8 @@ test('wrapFunction', async function testwrapFunction(t) { plan.equal(c.call(inner, segment, 'c'), 'c') if (segment) { - segment.children.forEach(function (child) { + children = transaction.trace.getChildren(segment.id) + children.forEach(function (child) { plan.ok(child.timer.hrstart) plan.ok(child.timer.hrDuration) }) @@ -704,13 +719,15 @@ test('wrapFunctionLast', async function testwrapFunctionLast(t) { function callback(parent, callbackArgs) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() plan.deepEqual(callbackArgs, [1, 2, 3]) plan.equal(this, inner) if (parent) { plan.ok(segment.timer.hrstart) plan.ok(!segment.timer.hrDuration) - plan.equal(parent.children[0], segment) + const [child] = transaction.trace.getChildren(parent.id) + plan.equal(child, segment) } return innerReturn @@ -732,16 +749,19 @@ test('wrapFunctionLast', async function testwrapFunctionLast(t) { plan.equal(this, outer) process.nextTick(function next() { + let children if (segment) { - plan.equal(segment.children.length, 0) + children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 0) } plan.equal(cb.call(inner, segment, cbArgs), innerReturn) if (segment) { - plan.equal(segment.children.length, 1) - plan.ok(segment.children[0].timer.hrstart) - plan.ok(segment.children[0].timer.hrDuration) + children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 1) + plan.ok(children[0].timer.hrstart) + plan.ok(children[0].timer.hrDuration) plan.ok(segment.timer.hrDuration) transaction.end() } @@ -779,13 +799,15 @@ test('wrapFunctionFirst', async function testwrapFunctionFirst(t) { function callback(parent, args) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() plan.deepEqual(args, [1, 2, 3]) plan.equal(this, inner) if (parent) { plan.ok(segment.timer.hrstart) plan.ok(!segment.timer.hrDuration) - plan.equal(parent.children[0], segment) + const [child] = transaction.trace.getChildren(parent.id) + plan.equal(child, segment) } return innerReturn @@ -806,16 +828,19 @@ test('wrapFunctionFirst', async function testwrapFunctionFirst(t) { plan.equal(this, outer) process.nextTick(function next() { + let children if (segment) { - plan.equal(segment.children.length, 0) + children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 0) } plan.equal(cb.call(inner, segment, args), innerReturn) if (segment) { - plan.equal(segment.children.length, 1) - plan.ok(segment.children[0].timer.hrstart) - plan.ok(segment.children[0].timer.hrDuration) + children = transaction.trace.getChildren(segment.id) + plan.equal(children.length, 1) + plan.ok(children[0].timer.hrstart) + plan.ok(children[0].timer.hrDuration) plan.ok(segment.timer.hrDuration) transaction.end() } @@ -841,8 +866,9 @@ test('wrapSyncFunction', async function testwrapSyncFunction(t) { helper.runInTransaction(agent, function inTrans(transaction) { wrapped(transaction, [4], 4) - plan.ok(transaction.trace.root.children[0].timer.hrstart) - plan.ok(transaction.trace.root.children[0].timer.hrDuration) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + plan.ok(child.timer.hrstart) + plan.ok(child.timer.hrDuration) transaction.end() }) @@ -857,7 +883,8 @@ test('wrapSyncFunction', async function testwrapSyncFunction(t) { } function record(segment, scope, transaction) { - plan.equal(segment, transaction.trace.root.children[0]) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + plan.equal(segment, child) plan.equal(segment.name, 'my segment') } }) diff --git a/test/lib/agent_helper.js b/test/lib/agent_helper.js index bf3bc58fb3..470cd493bf 100644 --- a/test/lib/agent_helper.js +++ b/test/lib/agent_helper.js @@ -84,14 +84,6 @@ helper.loadMockedAgent = function loadMockedAgent(conf, setState = true) { // agent needs a 'real' configuration const configurator = require('../../lib/config') const config = configurator.createInstance(conf) - - if (!config.debug) { - config.debug = {} - } - - // adds link to parents node in traces for easier testing - config.debug.double_linked_transactions = true - // stub applications config.applications = () => ['New Relic for Node.js tests'] diff --git a/test/lib/custom-assertions/assert-segments.js b/test/lib/custom-assertions/assert-segments.js index 0bdaa7aac1..b42bf2b3ec 100644 --- a/test/lib/custom-assertions/assert-segments.js +++ b/test/lib/custom-assertions/assert-segments.js @@ -63,6 +63,7 @@ * followed by an array of strings indicates that the first string is a parent * element, and the subsequent array of strings is its child elements. * + * @param {Trace} trace Transaction trace * @param {TraceSegment} parent Parent segment * @param {Array} expected Array of strings that represent segment names. * If an item in the array is another array, it @@ -77,12 +78,9 @@ * directly under test. Only used when `exact` is true. * @param {object} [deps] Injected dependencies. * @param {object} [deps.assert] Assertion library to use. - * @param options - * @param root0 - * @param root0.assert - * @param options.assert */ module.exports = function assertSegments( // eslint-disable-line sonarjs/cognitive-complexity + trace, parent, expected, options, @@ -100,7 +98,8 @@ module.exports = function assertSegments( // eslint-disable-line sonarjs/cogniti } function getChildren(_parent) { - return _parent.children.filter(function (item) { + const children = trace.getChildren(_parent.id) + return children.filter(function (item) { if (exact && options && options.exclude) { return options.exclude.indexOf(item.name) === -1 } @@ -135,7 +134,7 @@ module.exports = function assertSegments( // eslint-disable-line sonarjs/cogniti ) } } else if (typeof sequenceItem === 'object') { - assertSegments(child, sequenceItem, options, { assert }) + assertSegments(trace, child, sequenceItem, options, { assert }) } } @@ -147,14 +146,14 @@ module.exports = function assertSegments( // eslint-disable-line sonarjs/cogniti if (typeof sequenceItem === 'string') { // find corresponding child in parent - for (let j = 0; j < parent.children.length; j++) { - if (parent.children[j].name === sequenceItem) { - child = parent.children[j] + for (let j = 0; j < children.length; j++) { + if (children[j].name === sequenceItem) { + child = children[j] } } assert.ok(child, 'segment "' + parent.name + '" should have child "' + sequenceItem + '"') if (typeof expected[i + 1] === 'object') { - assertSegments(child, expected[i + 1], { exact }, { assert }) + assertSegments(trace, child, expected[i + 1], { exact }, { assert }) } } } diff --git a/test/lib/custom-assertions/compare-segments.js b/test/lib/custom-assertions/compare-segments.js index fde765b060..690707de37 100644 --- a/test/lib/custom-assertions/compare-segments.js +++ b/test/lib/custom-assertions/compare-segments.js @@ -9,18 +9,21 @@ * Verifies the expected length of children segments and that every * id matches between a segment array and the children * - * @param {Object} parent trace - * @param {Array} segments list of expected segments - * @param {object} [deps] Injected dependencies. - * @param {object} [deps.assert] Assertion library to use. + * @param {object} params to function + * @param {TraceSegment} params.parent segment + * @param {Array} params.segments list of expected segments + * @param {Trace} params.trace transaction trace + * @param {object} [params.assert] Assertion library to use. */ -module.exports = function compareSegments( +module.exports = function compareSegments({ parent, segments, - { assert = require('node:assert') } = {} -) { - assert.ok(parent.children.length, segments.length, 'should be the same amount of children') + trace, + assert = require('node:assert') +}) { + const parentChildren = trace.getChildren(parent.id) + assert.ok(parentChildren.length, segments.length, 'should be the same amount of children') segments.forEach((segment, index) => { - assert.equal(parent.children[index].id, segment.id, 'should have same ids') + assert.equal(parentChildren[index].id, segment.id, 'should have same ids') }) } diff --git a/test/lib/metrics_helper.js b/test/lib/metrics_helper.js index 9feba35598..1853dc0d16 100644 --- a/test/lib/metrics_helper.js +++ b/test/lib/metrics_helper.js @@ -10,13 +10,14 @@ const urltils = require('../../lib/util/urltils') exports.findSegment = findSegment exports.getMetricHostName = getMetricHostName -function findSegment(root, name) { +function findSegment(trace, root, name) { + const children = trace.getChildren(root.id) if (root.name === name) { return root - } else if (root.children && root.children.length) { - for (let i = 0; i < root.children.length; i++) { - const child = root.children[i] - const found = findSegment(child, name) + } else if (children.length) { + for (let i = 0; i < children.length; i++) { + const child = children[i] + const found = findSegment(trace, child, name) if (found) { return found } diff --git a/test/smoke/client-s3.test.js b/test/smoke/client-s3.test.js index 13e5f3b4ce..2b38eb0516 100644 --- a/test/smoke/client-s3.test.js +++ b/test/smoke/client-s3.test.js @@ -41,8 +41,8 @@ test('@aws-sdk/client-s3 functionality', async (t) => { transaction.end() - const { url, procedure, ...awsAttributes } = - transaction.trace.root.children[1].attributes.get(TRANS_SEGMENT) + const [, child] = transaction.trace.getChildren(transaction.trace.root.id) + const { url, procedure, ...awsAttributes } = child.attributes.get(TRANS_SEGMENT) delete awsAttributes.nr_exclusive_duration_millis diff --git a/test/unit/agent/agent.test.js b/test/unit/agent/agent.test.js index e9fc1ca89a..5c704021a8 100644 --- a/test/unit/agent/agent.test.js +++ b/test/unit/agent/agent.test.js @@ -101,11 +101,6 @@ test('when loaded with defaults', async (t) => { const { agent } = t.nr assert.throws(() => agent.setState('bogus'), /Invalid state bogus/) }) - - await t.test('has some debugging configuration by default', (t) => { - const { agent } = t.nr - assert.equal(Object.hasOwn(agent.config, 'debug'), true) - }) }) test('should load naming rules when configured', () => { diff --git a/test/unit/api/api-start-background-transaction.test.js b/test/unit/api/api-start-background-transaction.test.js index bbbfda97d5..2479e4fc1e 100644 --- a/test/unit/api/api-start-background-transaction.test.js +++ b/test/unit/api/api-start-background-transaction.test.js @@ -51,7 +51,7 @@ test('Agent API - startBackgroundTransaction', async (t) => { assert.ok(transaction.isActive()) const currentSegment = tracer.getSegment() - const nestedSegment = currentSegment.children[0] + const [nestedSegment] = transaction.trace.getChildren(currentSegment.id) assert.equal(nestedSegment.name, 'Nodejs/nested') }) @@ -182,6 +182,31 @@ test('Agent API - startBackgroundTransaction', async (t) => { }) }) + await t.test('should record metrics', (t, end) => { + const { agent, api } = t.nr + let transaction + api.startBackgroundTransaction('test', function () { + transaction = agent.tracer.getTransaction() + }) + + transaction.end() + const metrics = transaction.metrics.unscoped + ;[ + 'OtherTransaction/Nodejs/test', + 'OtherTransactionTotalTime/Nodejs/test', + 'OtherTransaction/all', + 'OtherTransactionTotalTime', + 'OtherTransactionTotalTime', + 'DurationByCaller/Unknown/Unknown/Unknown/Unknown/all', + 'DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther' + ].forEach((metric) => { + assert.ok(metrics[metric].total, `${metric} has total`) + assert.ok(metrics[metric].totalExclusive, `${metric} has totalExclusive`) + }) + + end() + }) + await t.test('should not throw when no handler is supplied', (t, end) => { const { api } = t.nr assert.doesNotThrow(() => api.startBackgroundTransaction('test')) @@ -221,7 +246,8 @@ test('Agent API - startBackgroundTransaction', async (t) => { api.startBackgroundTransaction('nested-clm-test', function () { nested({ api }) const currentSegment = tracer.getSegment() - const nestedSegment = currentSegment.children[0] + const transaction = agent.tracer.getTransaction() + const [nestedSegment] = transaction.trace.getChildren(currentSegment.id) assertCLMAttrs({ segments: [ { diff --git a/test/unit/api/api-start-segment.test.js b/test/unit/api/api-start-segment.test.js index b8efac74b4..503ccf8300 100644 --- a/test/unit/api/api-start-segment.test.js +++ b/test/unit/api/api-start-segment.test.js @@ -88,9 +88,13 @@ test('Agent API - startSegment', async (t) => { const transactionScopedCustomMetric = transactionNameMetric['Custom/foobar'] assert.ok(transactionScopedCustomMetric) + assert.ok(transactionScopedCustomMetric.total) + assert.ok(transactionScopedCustomMetric.totalExclusive) const unscopedCustomMetric = tx.metrics.unscoped['Custom/foobar'] assert.ok(unscopedCustomMetric) + assert.ok(unscopedCustomMetric.total) + assert.ok(unscopedCustomMetric.totalExclusive) end() }) diff --git a/test/unit/api/api-start-web-transaction.test.js b/test/unit/api/api-start-web-transaction.test.js index 95d1dc839c..7eb39274fa 100644 --- a/test/unit/api/api-start-web-transaction.test.js +++ b/test/unit/api/api-start-web-transaction.test.js @@ -49,7 +49,7 @@ test('Agent API - startWebTransaction', async (t) => { assert.ok(transaction.isActive()) const currentSegment = tracer.getSegment() - const nestedSegment = currentSegment.children[0] + const [nestedSegment] = transaction.trace.getChildren(currentSegment.id) assert.equal(nestedSegment.name, 'nested') }) @@ -174,9 +174,10 @@ test('Agent API - startWebTransaction', async (t) => { const { agent, api, tracer } = t.nr agent.config.code_level_metrics.enabled = enabled api.startWebTransaction('clm-nested-test', function () { + const tx = agent.tracer.getTransaction() nested({ api }) const currentSegment = tracer.getSegment() - const nestedSegment = currentSegment.children[0] + const [nestedSegment] = tx.trace.getChildren(currentSegment.id) assertCLMAttrs({ segments: [ { diff --git a/test/unit/instrumentation/core/promises.test.js b/test/unit/instrumentation/core/promises.test.js index 73074d322b..89cc6fad25 100644 --- a/test/unit/instrumentation/core/promises.test.js +++ b/test/unit/instrumentation/core/promises.test.js @@ -158,7 +158,7 @@ function checkTrace(t, tx) { const expectedSegment = tracer.getSegment() const segment = tx.trace.root assert.equal(segment.name, 'a') - assert.equal(segment.children.length, 0) + assert.equal(tx.trace.getChildren(segment.id).length, 0) // verify current segment is same as trace root assert.deepEqual(segment.name, expectedSegment.name, 'current segment is same as one in tracer') return Promise.resolve() diff --git a/test/unit/instrumentation/http/outbound.test.js b/test/unit/instrumentation/http/outbound.test.js index aab7ec3a12..82d4deadd4 100644 --- a/test/unit/instrumentation/http/outbound.test.js +++ b/test/unit/instrumentation/http/outbound.test.js @@ -49,7 +49,8 @@ test('instrumentOutbound', async (t) => { const req = new events.EventEmitter() helper.runInTransaction(agent, function (transaction) { instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.deepEqual(transaction.trace.root.children[0].getAttributes(), {}) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.deepEqual(child.getAttributes(), {}) function makeFakeRequest() { req.path = '/asdf?a=b&another=yourself&thing&grownup=true' @@ -66,7 +67,8 @@ test('instrumentOutbound', async (t) => { const req = new events.EventEmitter() helper.runInTransaction(agent, function (transaction) { instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.deepEqual(transaction.trace.root.children[0].getAttributes(), { + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.deepEqual(child.getAttributes(), { procedure: 'GET', url: `http://${HOSTNAME}:${PORT}/asdf` }) @@ -91,7 +93,8 @@ test('instrumentOutbound', async (t) => { const req = new events.EventEmitter() helper.runInTransaction(agent, function (transaction) { instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.deepEqual(transaction.trace.root.children[0].getAttributes(), { + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.deepEqual(child.getAttributes(), { procedure: 'GET', url: `http://${HOSTNAME}:${PORT}/***` }) @@ -112,7 +115,8 @@ test('instrumentOutbound', async (t) => { const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + path instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.equal(transaction.trace.root.children[0].name, name) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(child.name, name) function makeFakeRequest() { req.path = '/asdf?a=b&another=yourself&thing&grownup=true' @@ -128,8 +132,9 @@ test('instrumentOutbound', async (t) => { helper.runInTransaction(agent, function (transaction) { agent.config.attributes.enabled = true instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) assert.deepEqual( - transaction.trace.root.children[0].attributes.get(DESTINATIONS.SPAN_EVENT), + child.attributes.get(DESTINATIONS.SPAN_EVENT), { hostname: HOSTNAME, port: PORT, @@ -175,7 +180,8 @@ test('instrumentOutbound', async (t) => { const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + path req.path = path instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.equal(transaction.trace.root.children[0].name, name) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(child.name, name) end() }) @@ -193,7 +199,8 @@ test('instrumentOutbound', async (t) => { const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + '/newrelic' req.path = path instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest) - assert.equal(transaction.trace.root.children[0].name, name) + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(child.name, name) }) function makeFakeRequest() { @@ -473,7 +480,8 @@ test('when working with http.request', async (t) => { opts.method = 'POST' const req = http.request(opts, function (res) { - const attributes = transaction.trace.root.children[0].getAttributes() + const [child] = transaction.trace.getChildren(transaction.trace.root.id) + const attributes = child.getAttributes() assert.equal(attributes.url, 'http://www.google.com/index.html') assert.equal(attributes.procedure, 'POST') res.resume() diff --git a/test/unit/instrumentation/prisma-client.test.js b/test/unit/instrumentation/prisma-client.test.js index 4101d0e06f..dd299cbecf 100644 --- a/test/unit/instrumentation/prisma-client.test.js +++ b/test/unit/instrumentation/prisma-client.test.js @@ -127,7 +127,7 @@ test('PrismaClient unit.tests', async (t) => { args: { query: 'select test from schema.unit-test;' }, action: 'executeRaw' }) - const { children } = tx.trace.root + const children = tx.trace.getChildren(tx.trace.root.id) assert.equal(children.length, 3, 'should have 3 segments') const [firstSegment, secondSegment, thirdSegment] = children assert.equal(firstSegment.name, 'Datastore/statement/Prisma/user/create') @@ -180,7 +180,7 @@ test('PrismaClient unit.tests', async (t) => { helper.runInTransaction(agent, async (tx) => { await client._executeRequest({ action: 'executeRaw' }) - const { children } = tx.trace.root + const children = tx.trace.getChildren(tx.trace.root.id) const [firstSegment] = children assert.equal(firstSegment.name, 'Datastore/statement/Prisma/other/other') end() @@ -228,7 +228,7 @@ test('PrismaClient unit.tests', async (t) => { args: [['select test from unit-test;']], action: 'executeRaw' }) - const { children } = tx.trace.root + const children = tx.trace.getChildren(tx.trace.root.id) assert.equal(children.length, 2, 'should have 3 segments') const [firstSegment, secondSegment] = children assert.equal(firstSegment.name, 'Datastore/statement/Prisma/user/create') diff --git a/test/unit/instrumentation/redis.test.js b/test/unit/instrumentation/redis.test.js index 36fa840b83..5f53feefdf 100644 --- a/test/unit/instrumentation/redis.test.js +++ b/test/unit/instrumentation/redis.test.js @@ -184,7 +184,7 @@ test('createClient saves connection options', async function (t) { helper.runInTransaction(agent, async function (tx) { await client.queue.addCommand(['test', 'key', 'value']) await client2.queue.addCommand(['test2', 'key2', 'value2']) - const [redisSegment, redisSegment2] = tx.trace.root.children + const [redisSegment, redisSegment2] = tx.trace.getChildren(tx.trace.root.id) const attrs = redisSegment.getAttributes() assert.deepEqual( attrs, diff --git a/test/unit/llm-events/openai/common.js b/test/unit/llm-events/openai/common.js index dcf655bbd8..2f828c92b7 100644 --- a/test/unit/llm-events/openai/common.js +++ b/test/unit/llm-events/openai/common.js @@ -45,18 +45,20 @@ const req = { function getExpectedResult(tx, event, type, completionId) { const trace = tx.trace.root + const [child] = tx.trace.getChildren(trace.id) + const spanId = child.id let expected = { id: event.id, appName: 'New Relic for Node.js tests', request_id: 'req-id', trace_id: tx.traceId, - span_id: trace.children[0].id, + span_id: spanId, 'response.model': 'gpt-3.5-turbo-0613', vendor: 'openai', ingest_source: 'Node' } const resKeys = { - duration: trace.children[0].getDurationInMillis(), + duration: child.getDurationInMillis(), 'request.model': 'gpt-3.5-turbo-0613', 'response.organization': 'new-relic', 'response.headers.llmVersion': '1.0.0', diff --git a/test/unit/shim/datastore-shim.test.js b/test/unit/shim/datastore-shim.test.js index 344367a5af..83c96766b8 100644 --- a/test/unit/shim/datastore-shim.test.js +++ b/test/unit/shim/datastore-shim.test.js @@ -33,14 +33,9 @@ test('DatastoreShim', async function (t) { return agent.tracer.getSegment() }, withNested: function () { - const transaction = agent.tracer.getTransaction() + const tx = agent.tracer.getTransaction() const segment = agent.tracer.getSegment() - segment.add({ - config: agent.config, - name: 'ChildSegment', - root: transaction.trace.root - }) - + tx.trace.add('ChildSegment', null, segment) return segment } } @@ -381,13 +376,15 @@ test('DatastoreShim', async function (t) { shim.recordOperation(wrappable, 'withNested', () => { return new OperationSpec({ name: 'test', opaque: false }) }) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { const startingSegment = agent.tracer.getSegment() const segment = wrappable.withNested() assert.notEqual(segment, startingSegment) assert.equal(segment.name, 'Datastore/operation/Cassandra/test') - assert.equal(segment.children.length, 1) - const [childSegment] = segment.children + + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 1) + const [childSegment] = children assert.equal(childSegment.name, 'ChildSegment') end() }) @@ -398,12 +395,13 @@ test('DatastoreShim', async function (t) { shim.recordOperation(wrappable, 'withNested', () => { return new OperationSpec({ name: 'test', opaque: true }) }) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { const startingSegment = agent.tracer.getSegment() const segment = wrappable.withNested() assert.notEqual(segment, startingSegment) assert.equal(segment.name, 'Datastore/operation/Cassandra/test') - assert.equal(segment.children.length, 0) + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 0) end() }) }) @@ -778,7 +776,8 @@ test('DatastoreShim', async function (t) { helper.runInTransaction(agent, (tx) => { wrappable.bar() const rootSegment = agent.tracer.getSegment() - const attrs = rootSegment.children[0].getAttributes() + const [child] = tx.trace.getChildren(rootSegment.id) + const attrs = child.getAttributes() assert.equal( attrs['test-attr'], 'unit-test', @@ -923,7 +922,7 @@ test('DatastoreShim', async function (t) { await t.test('should create a new segment on the first call', function (t, end) { const { agent, shim, wrappable } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [1, 2, wrappable.getActiveSegment] shim.bindRowCallbackSegment(args, shim.LAST) @@ -931,14 +930,15 @@ test('DatastoreShim', async function (t) { const segment = shim.getSegment() const cbSegment = args[2]() assert.notEqual(cbSegment, segment) - assert.ok(segment.children.includes(cbSegment)) + const children = tx.trace.getChildren(segment.id) + assert.ok(children.includes(cbSegment)) end() }) }) await t.test('should not create a new segment for calls after the first', function (t, end) { const { agent, shim, wrappable } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [1, 2, wrappable.getActiveSegment] shim.bindRowCallbackSegment(args, shim.LAST) @@ -946,13 +946,15 @@ test('DatastoreShim', async function (t) { const segment = shim.getSegment() const cbSegment = args[2]() assert.notEqual(cbSegment, segment) - assert.ok(segment.children.includes(cbSegment)) - assert.equal(segment.children.length, 1) + let children = tx.trace.getChildren(segment.id) + assert.ok(children.includes(cbSegment)) + assert.equal(children.length, 1) // Call it a second time and see if we have the same segment. const cbSegment2 = args[2]() assert.equal(cbSegment2, cbSegment) - assert.equal(segment.children.length, 1) + children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 1) end() }) }) diff --git a/test/unit/shim/message-shim.test.js b/test/unit/shim/message-shim.test.js index 9eac8f1642..cffc0687df 100644 --- a/test/unit/shim/message-shim.test.js +++ b/test/unit/shim/message-shim.test.js @@ -44,12 +44,7 @@ test('MessageShim', async function (t) { withNested: function () { const transaction = agent.tracer.getTransaction() const segment = agent.tracer.getSegment() - segment.add({ - config: agent.config, - name: 'ChildSegment', - root: transaction.trace.root - }) - + transaction.trace.add('ChildSegment', null, segment) return segment } } @@ -329,12 +324,13 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: false }) }) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { const segment = wrappable.withNested() assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar') - assert.equal(segment.children.length, 1) - const [childSegment] = segment.children + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 1) + const [childSegment] = children assert.equal(childSegment.name, 'ChildSegment') end() }) @@ -346,11 +342,12 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: true }) }) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { const segment = wrappable.withNested() assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar') - assert.equal(segment.children.length, 0) + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 0) end() }) }) @@ -623,12 +620,13 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: false }) }) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const segment = wrappable.withNested() assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar') - assert.equal(segment.children.length, 1) - const [childSegment] = segment.children + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 1) + const [childSegment] = children assert.equal(childSegment.name, 'ChildSegment') end() }) @@ -640,10 +638,11 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: true }) }) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const segment = wrappable.withNested() assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar') - assert.equal(segment.children.length, 0) + const children = tx.trace.getChildren(segment.id) + assert.equal(children.length, 0) end() }) }) @@ -1213,11 +1212,12 @@ test('MessageShim', async function (t) { await t.test('should bind the subscribe callback', function (t, end) { const { agent, shim, wrapped } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { + const { trace } = tx const parent = wrapped('my.queue', null, function subCb() { const segment = shim.getSegment() assert.equal(segment.name, 'Callback: subCb') - compareSegments(parent, [segment]) + compareSegments({ parent, segments: [segment], trace }) end() }) assert.ok(parent) diff --git a/test/unit/shim/shim.test.js b/test/unit/shim/shim.test.js index 4c2cd2e42e..759952bbc6 100644 --- a/test/unit/shim/shim.test.js +++ b/test/unit/shim/shim.test.js @@ -1113,7 +1113,9 @@ test('Shim', async function (t) { stream.on('foobar', function () { const emitSegment = shim.getSegment() - assert.equal(emitSegment.parent, stream.segment) + const tx = agent.tracer.getTransaction() + const children = tx.trace.getChildren(stream.segment.id) + assert.ok(children.includes(emitSegment)) end() }) @@ -1130,28 +1132,32 @@ test('Shim', async function (t) { return new RecorderSpec({ name: 'test segment', stream: 'foobar' }) }) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const ret = wrapped() assert.equal(ret, stream) // Emit the event and check the segment name. - assert.equal(stream.segment.children.length, 0) + let children = tx.trace.getChildren(stream.segment.id) + assert.equal(children.length, 0) stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) + children = tx.trace.getChildren(stream.segment.id) + assert.equal(children.length, 1) - const [eventSegment] = stream.segment.children + const [eventSegment] = children assert.match(eventSegment.name, /Event callback: foobar/) assert.equal(eventSegment.getAttributes().count, 1) // Emit it again and see if the name updated. stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) - assert.equal(stream.segment.children[0], eventSegment) + children = tx.trace.getChildren(stream.segment.id) + assert.equal(children.length, 1) + assert.equal(children[0], eventSegment) assert.equal(eventSegment.getAttributes().count, 2) // Emit it once more and see if the name updated again. stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) - assert.equal(stream.segment.children[0], eventSegment) + children = tx.trace.getChildren(stream.segment.id) + assert.equal(children.length, 1) + assert.equal(children[0], eventSegment) assert.equal(eventSegment.getAttributes().count, 3) }) }) @@ -1563,12 +1569,14 @@ test('Shim', async function (t) { }) }) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const parentSegment = shim.getSegment() const resultingSegment = wrapped(cb) assert.notEqual(resultingSegment, parentSegment) - assert.ok(parentSegment.children.includes(resultingSegment)) + + const children = tx.trace.getChildren(parentSegment.id) + assert.ok(children.includes(resultingSegment)) end() }) }) @@ -1589,12 +1597,13 @@ test('Shim', async function (t) { }) }) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const parentSegment = shim.getSegment() const resultingSegment = wrapped() assert.equal(resultingSegment, parentSegment) - assert.ok(!parentSegment.children.includes(resultingSegment)) + const children = tx.trace.getChildren(parentSegment.id) + assert.ok(!children.includes(resultingSegment)) end() }) }) @@ -2101,7 +2110,7 @@ test('Shim', async function (t) { await t.test('should create a new segment', function (t, end) { const { agent, shim, wrappable } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [wrappable.getActiveSegment] const segment = wrappable.getActiveSegment() const parent = shim.createSegment({ name: 'test segment', parent: segment }) @@ -2110,7 +2119,7 @@ test('Shim', async function (t) { assert.notEqual(cbSegment, segment) assert.notEqual(cbSegment, parent) - compareSegments(parent, [cbSegment]) + compareSegments({ parent, segments: [cbSegment], trace: tx.trace }) end() }) }) @@ -2125,7 +2134,7 @@ test('Shim', async function (t) { const cbSegment = args[0]() assert.notEqual(cbSegment, parent) - compareSegments(parent, [cbSegment]) + compareSegments({ parent, segments: [cbSegment], trace: tx.trace }) assert.equal(parent.opaque, false) end() }) @@ -2133,14 +2142,14 @@ test('Shim', async function (t) { await t.test('should default the `parentSegment` to the current one', function (t, end) { const { agent, shim, wrappable } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [wrappable.getActiveSegment] const segment = wrappable.getActiveSegment() shim.bindCallbackSegment({}, args, shim.LAST) const cbSegment = args[0]() assert.notEqual(cbSegment, segment) - compareSegments(segment, [cbSegment]) + compareSegments({ parent: segment, segments: [cbSegment], trace: tx.trace }) end() }) }) @@ -2153,14 +2162,14 @@ test('Shim', async function (t) { executed = true } } - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [wrappable.getActiveSegment] const segment = wrappable.getActiveSegment() shim.bindCallbackSegment(spec, args, shim.LAST) const cbSegment = args[0]() assert.notEqual(cbSegment, segment) - compareSegments(segment, [cbSegment]) + compareSegments({ parent: segment, segments: [cbSegment], trace: tx.trace }) assert.equal(executed, true) end() }) @@ -2330,7 +2339,7 @@ test('Shim', async function (t) { const parent = shim.createSegment({ name: 'parent', parent: tx.trace.root }) const child = shim.createSegment('child', parent) assert.equal(child.name, 'child') - compareSegments(parent, [child]) + compareSegments({ parent, segments: [child], trace: tx.trace }) end() }) }) @@ -2341,7 +2350,7 @@ test('Shim', async function (t) { const parent = shim.createSegment('parent', tx.trace.root) const child = shim.createSegment('child', null, parent) assert.equal(child.name, 'child') - compareSegments(parent, [child]) + compareSegments({ parent, segments: [child], trace: tx.trace }) end() }) }) @@ -2353,7 +2362,8 @@ test('Shim', async function (t) { parent.opaque = true const child = shim.createSegment('child', parent) assert.equal(child.name, 'parent') - assert.deepEqual(parent.children, []) + const children = tx.trace.getChildren(parent.id) + assert.deepEqual(children, []) end() }) }) @@ -2376,10 +2386,10 @@ test('Shim', async function (t) { await t.test('should default to the current segment as the parent', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const parent = shim.getSegment() const child = shim.createSegment('child', parent) - compareSegments(parent, [child]) + compareSegments({ parent, segments: [child], trace: tx.trace }) end() }) }) @@ -2406,7 +2416,7 @@ test('Shim', async function (t) { const parent = shim.createSegment('parent', tx.trace.root) const child = shim.createSegment({ name: 'child', parent }) assert.equal(child.name, 'child') - compareSegments(parent, [child]) + compareSegments({ parent, segments: [child], trace: tx.trace }) end() }) }) diff --git a/test/unit/spans/span-event.test.js b/test/unit/spans/span-event.test.js index 33fd151c4d..be81e50fe8 100644 --- a/test/unit/spans/span-event.test.js +++ b/test/unit/spans/span-event.test.js @@ -62,7 +62,8 @@ test('fromSegment()', async (t) => { transaction.priority = 42 setTimeout(() => { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const tx = agent.tracer.getTransaction() + const [segment] = tx.trace.getChildren(tx.trace.root.id) segment.addSpanAttribute('SpiderSpan', 'web') segment.addSpanAttribute('host', 'my-host') segment.addSpanAttribute('port', 222) @@ -135,7 +136,8 @@ test('fromSegment()', async (t) => { https.get('https://example.com?foo=bar', (res) => { res.resume() res.on('end', () => { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const tx = agent.tracer.getTransaction() + const [segment] = tx.trace.getChildren(tx.trace.root.id) const span = SpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. @@ -237,7 +239,7 @@ test('fromSegment()', async (t) => { dsConn.myDbOp(longQuery, () => { transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const span = SpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. @@ -357,7 +359,7 @@ test('fromSegment()', async (t) => { res.resume() res.on('end', () => { - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) assert.ok(segment.name.startsWith('Truncated')) const span = SpanEvent.fromSegment(segment, transaction) diff --git a/test/unit/spans/streaming-span-event.test.js b/test/unit/spans/streaming-span-event.test.js index 3e92aaa3cc..a92380ce71 100644 --- a/test/unit/spans/streaming-span-event.test.js +++ b/test/unit/spans/streaming-span-event.test.js @@ -60,7 +60,8 @@ test('fromSegment()', async (t) => { transaction.priority = 42 setTimeout(() => { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const tx = agent.tracer.getTransaction() + const [segment] = tx.trace.getChildren(tx.trace.root.id) const spanContext = segment.getSpanContext() spanContext.addCustomAttribute('Span Lee', 'no prize') segment.addSpanAttribute('host', 'my-host') @@ -130,7 +131,8 @@ test('fromSegment()', async (t) => { https.get('https://example.com?foo=bar', (res) => { res.resume() res.on('end', () => { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const tx = agent.tracer.getTransaction() + const [segment] = tx.trace.getChildren(tx.trace.root.id) const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. @@ -237,7 +239,7 @@ test('fromSegment()', async (t) => { dsConn.myDbOp(longQuery, () => { transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. @@ -387,7 +389,7 @@ test('fromSegment()', async (t) => { res.resume() res.on('end', () => { - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) assert.ok(segment.name.startsWith('Truncated')) const span = StreamingSpanEvent.fromSegment(segment, transaction) diff --git a/test/unit/transaction/trace/index.test.js b/test/unit/transaction/trace/index.test.js index 12b930e9f3..274340da8e 100644 --- a/test/unit/transaction/trace/index.test.js +++ b/test/unit/transaction/trace/index.test.js @@ -119,7 +119,6 @@ test('Trace', async (t) => { child2.end() trace.root.end() transaction.end() - trace.generateSpanEvents() const events = agent.spanEventAggregator.getEvents() const nested = events[0] @@ -232,7 +231,7 @@ test('Trace', async (t) => { transaction.acceptDistributedTraceHeaders('HTTP', headers) // Create at least one segment - const trace = new Trace(transaction) + const trace = transaction.trace const child = (transaction.baseSegment = trace.add('test')) child.start() @@ -272,7 +271,7 @@ test('Trace', async (t) => { const transaction = new Transaction(agent) transaction.sampled = true - const trace = new Trace(transaction) + const trace = transaction.trace // add a child segment const child = (transaction.baseSegment = trace.add('test')) @@ -311,7 +310,9 @@ test('when serializing synchronously', async (t) => { await t.test('should produce a transaction trace in the expected format', async (t) => { const { details } = t.nr + assert.equal(details.trace.segments.length, 3) const traceJSON = details.trace.generateJSONSync() + assert.equal(details.trace.segments.length, 0) const reconstituted = await codecDecodeAsync(traceJSON[4]) assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON') @@ -382,7 +383,9 @@ test('when serializing asynchronously', async (t) => { await t.test('should produce a transaction trace in the expected format', async (t) => { const { details } = t.nr + assert.equal(details.trace.segments.length, 3) const traceJSON = await details.trace.generateJSONAsync() + assert.equal(details.trace.segments.length, 0) const reconstituted = await codecDecodeAsync(traceJSON[4]) assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON') @@ -478,244 +481,158 @@ test('when inserting segments', async (t) => { }) await t.test('should report total time', (t) => { - const { agent, trace } = t.nr - const root = trace.root + const { trace } = t.nr trace.setDurationInMillis(40, 0) const child = trace.add('Custom/Test18/Child1') child.setDurationInMillis(27, 0) - let seg = child.add({ - config: agent.config, - name: 'UnitTest', - collect: true, - root - }) + let seg = trace.add('UnitTest', null, child) seg.setDurationInMillis(9, 1) - seg = child.add({ - config: agent.config, - name: 'UnitTest1', - collect: true, - root - }) + seg = trace.add('UnitTest1', null, child) seg.setDurationInMillis(13, 1) - seg = child.add({ - config: agent.config, - name: 'UnitTest2', - collect: true, - root - }) + seg = trace.add('UnitTest2', null, child) seg.setDurationInMillis(9, 16) - seg = child.add({ - config: agent.config, - name: 'UnitTest2', - collect: true, - root - }) + seg = trace.add('UnitTest2', null, child) seg.setDurationInMillis(14, 16) assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) await t.test('should report total time on branched traces', (t) => { - const { trace, agent } = t.nr - const root = trace.root + const { trace } = t.nr trace.setDurationInMillis(40, 0) - const child = trace.add('Custom/Test18/Child1') + const child = trace.add('Custom/Test18/Child1', null, trace.root) child.setDurationInMillis(27, 0) - const seg1 = child.add({ - config: agent.config, - name: 'UnitTest', - collect: true, - root - }) + const seg1 = trace.add('UnitTest', null, child) seg1.setDurationInMillis(9, 1) - let seg = child.add({ - config: agent.config, - name: 'UnitTest1', - collect: true, - root - }) + let seg = trace.add('UnitTest1', null, child) seg.setDurationInMillis(13, 1) - seg = seg1.add({ - config: agent.config, - name: 'UnitTest2', - collect: true, - root - }) + seg = trace.add('UnitTest2', null, seg1) seg.setDurationInMillis(9, 16) - seg = seg1.add({ - config: agent.config, - name: 'UnitTest2', - collect: true, - root - }) + seg = trace.add('UnitTest2', null, seg1) seg.setDurationInMillis(14, 16) assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) await t.test('should report the expected trees for trees with uncollected segments', (t) => { - const { agent, trace } = t.nr - const root = trace.root + const { trace } = t.nr const expectedTrace = [ 0, - 27, - 'Root', - { nr_exclusive_duration_millis: 3 }, + 40, + 'ROOT', + { nr_exclusive_duration_millis: 10 }, [ [ - 1, - 10, - 'first', - { nr_exclusive_duration_millis: 9 }, - [[16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []]] - ], - [ - 1, - 14, - 'second', - { nr_exclusive_duration_millis: 13 }, + 0, + 27, + 'Root', + { nr_exclusive_duration_millis: 3 }, [ - [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []], - [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []] + [ + 1, + 10, + 'first', + { nr_exclusive_duration_millis: 9 }, + [[16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []]] + ], + [ + 1, + 14, + 'second', + { nr_exclusive_duration_millis: 13 }, + [ + [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []], + [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []] + ] + ] ] ] ] ] trace.setDurationInMillis(40, 0) - const child = trace.add('Root') + const child = trace.add('Root', null, trace.root) child.setDurationInMillis(27, 0) - const seg1 = child.add({ - config: agent.config, - name: 'first', - collect: true, - root - }) + const seg1 = trace.add('first', null, child) seg1.setDurationInMillis(9, 1) - const seg2 = child.add({ - config: agent.config, - name: 'second', - collect: true, - root - }) + const seg2 = trace.add('second', null, child) seg2.setDurationInMillis(13, 1) - let seg = seg1.add({ - config: agent.config, - name: 'first-first', - collect: true, - root - }) + let seg = trace.add('first-first', null, seg1) seg.setDurationInMillis(9, 16) - seg = seg1.add({ - config: agent.config, - name: 'first-second', - collect: true, - root - }) + seg = trace.add('first-second', null, seg1) seg.setDurationInMillis(14, 16) seg._collect = false - seg = seg2.add({ - config: agent.config, - name: 'second-first', - collect: true, - root - }) + seg = trace.add('second-first', null, seg2) seg.setDurationInMillis(9, 16) - seg = seg2.add({ - config: agent.config, - name: 'second-second', - collect: true, - root - }) + seg = trace.add('second-second', null, seg2) seg.setDurationInMillis(9, 16) trace.end() - assert.deepEqual(child.toJSON(), expectedTrace) + assert.deepEqual(trace.toJSON(), expectedTrace) }) await t.test('should report the expected trees for branched trees', (t) => { - const { agent, trace } = t.nr + const { trace } = t.nr const expectedTrace = [ 0, - 27, - 'Root', - { nr_exclusive_duration_millis: 3 }, + 40, + 'ROOT', + { nr_exclusive_duration_millis: 10 }, [ [ - 1, - 10, - 'first', - { nr_exclusive_duration_millis: 9 }, + 0, + 27, + 'Root', + { nr_exclusive_duration_millis: 3 }, [ - [16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []], - [16, 30, 'first-second', { nr_exclusive_duration_millis: 14 }, []] - ] - ], - [ - 1, - 14, - 'second', - { nr_exclusive_duration_millis: 13 }, - [ - [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []], - [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []] + [ + 1, + 10, + 'first', + { nr_exclusive_duration_millis: 9 }, + [ + [16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []], + [16, 30, 'first-second', { nr_exclusive_duration_millis: 14 }, []] + ] + ], + [ + 1, + 14, + 'second', + { nr_exclusive_duration_millis: 13 }, + [ + [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []], + [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []] + ] + ] ] ] ] ] + trace.setDurationInMillis(40, 0) - const child = trace.add('Root') - const root = trace.root + const child = trace.add('Root', null, trace.root) child.setDurationInMillis(27, 0) - const seg1 = child.add({ - config: agent.config, - name: 'first', - collect: true, - root - }) + const seg1 = trace.add('first', null, child) + seg1.setDurationInMillis(9, 1) - const seg2 = child.add({ - config: agent.config, - name: 'second', - collect: true, - root - }) + const seg2 = trace.add('second', null, child) seg2.setDurationInMillis(13, 1) - let seg = seg1.add({ - config: agent.config, - name: 'first-first', - collect: true, - root - }) + let seg = trace.add('first-first', null, seg1) seg.setDurationInMillis(9, 16) - seg = seg1.add({ - config: agent.config, - name: 'first-second', - collect: true, - root - }) + seg = trace.add('first-second', null, seg1) seg.setDurationInMillis(14, 16) - seg = seg2.add({ - config: agent.config, - name: 'second-first', - collect: true, - root - }) + seg = trace.add('second-first', null, seg2) seg.setDurationInMillis(9, 16) - seg = seg2.add({ - config: agent.config, - name: 'second-second', - collect: true, - root - }) + seg = trace.add('second-second', null, seg2) seg.setDurationInMillis(9, 16) trace.end() - assert.deepEqual(child.toJSON(), expectedTrace) + assert.deepEqual(trace.toJSON(), expectedTrace) }) await t.test('should measure exclusive time vs total time at each level of the graph', (t) => { @@ -755,11 +672,10 @@ test('when inserting segments', async (t) => { }) await t.test('should accurately sum overlapping subtrees', (t) => { - const { agent, trace } = t.nr + const { trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() - const root = trace.root // create a long child on its own const child1 = trace.add('Custom/Test20/Child1') @@ -767,39 +683,21 @@ test('when inserting segments', async (t) => { child1.setDurationInMillis(33, now) // add another, short child as a sibling - const child2 = child1.add({ - config: agent.config, - name: 'Custom/Test20/Child2', - collect: true, - root - }) - + const child2 = trace.add('Custom/Test20/Child2', null, child1) child2.setDurationInMillis(5, now) // add two disjoint children of the second segment encompassed by the first segment - const child3 = child2.add({ - config: agent.config, - name: 'Custom/Test20/Child3', - collect: true, - root - }) - + const child3 = trace.add('Custom/Test20/Child3', null, child2) child3.setDurationInMillis(11, now) - const child4 = child2.add({ - config: agent.config, - name: 'Custom/Test20/Child3', - collect: true, - root - }) - + const child4 = trace.add('Custom/Test20/Child3', null, child2) child4.setDurationInMillis(11, now + 16) assert.equal(trace.getExclusiveDurationInMillis(), 9) - assert.equal(child4.getExclusiveDurationInMillis(), 11) - assert.equal(child3.getExclusiveDurationInMillis(), 11) - assert.equal(child2.getExclusiveDurationInMillis(), 0) - assert.equal(child1.getExclusiveDurationInMillis(), 11) + assert.equal(child4.getExclusiveDurationInMillis(trace), 11) + assert.equal(child3.getExclusiveDurationInMillis(trace), 11) + assert.equal(child2.getExclusiveDurationInMillis(trace), 0) + assert.equal(child1.getExclusiveDurationInMillis(trace), 11) }) await t.test('should accurately sum partially overlapping segments', (t) => { @@ -852,14 +750,53 @@ test('when inserting segments', async (t) => { } } - assert.equal(trace.root.children.length, 950) + assert.equal(trace.segments.length, 950) assert.equal(transaction._recorders.length, 950) - trace.segmentCount = 0 - trace.root.children = [] - trace.recorders = [] - + trace.end() function noop() {} }) + + await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => { + const { trace } = t.nr + for (let i = 0; i < 9000; ++i) { + trace.add(`Child ${i}`) + } + + assert.doesNotThrow(function () { + trace.toJSON() + }) + }) + + await t.test('should get all children for a segment', (t) => { + const { trace } = t.nr + assert.deepEqual(trace.segments, []) + const segment = trace.add('base') + const segment2 = trace.add('1', null, segment) + const segment3 = trace.add('2', null, segment) + const children = trace.getChildren(segment.id) + assert.deepEqual(children, [segment2, segment3]) + }) + + await t.test('should get all collected children for a segment', (t) => { + const { trace } = t.nr + const segment = trace.add('base') + const segment2 = trace.add('1', null, segment) + const segment3 = trace.add('2', null, segment) + const segment4 = trace.add('3', null, segment) + segment4._collect = false + const segment5 = trace.add('4', null, segment) + segment5.ignore = true + const children = trace.getCollectedChildren(segment.id) + assert.deepEqual(children, [segment2, segment3]) + }) + + await t.test('should get parent segment for a segment', (t) => { + const { trace } = t.nr + const segment = trace.add('base') + const segment2 = trace.add('1', null, segment) + const parent = trace.getParent(segment2.parentId) + assert.equal(parent, segment) + }) }) test('should set URI to null when request.uri attribute is excluded globally', async (t) => { @@ -1031,14 +968,9 @@ test('infinite tracing', async (t) => { function addTwoSegments(transaction) { const trace = transaction.trace - const root = trace.root const child1 = (transaction.baseSegment = trace.add('test')) child1.start() - const child2 = child1.add({ - config: transaction.agent.config, - name: 'nested', - root - }) + const child2 = trace.add('nested', null, child1) child2.start() child1.end() child2.end() @@ -1057,10 +989,7 @@ async function makeTrace(agent) { transaction.url = URL transaction.verb = 'GET' - transaction.timer.setDurationInMillis(DURATION) - const trace = transaction.trace - const root = trace.root // promisifying `trace.generateJSON` so tests do not have to call done // and instead use async/await @@ -1069,33 +998,19 @@ async function makeTrace(agent) { assert.ok(start > 0, "root segment's start time") trace.setDurationInMillis(DURATION, 0) - const web = trace.root.add({ - config: agent.config, - name: URL, - collect: true, - root - }) + const web = trace.add(URL) transaction.baseSegment = web transaction.finalizeNameFromUri(URL, 200) // top-level element will share a duration with the quasi-ROOT node web.setDurationInMillis(DURATION, 0) - const db = web.add({ - config: agent.config, - name: 'Database/statement/AntiSQL/select/getSome', - collect: true, - root - }) + const db = trace.add('Database/statement/AntiSQL/select/getSome', null, web) db.setDurationInMillis(14, 3) - const memcache = web.add({ - config: agent.config, - name: 'Datastore/operation/Memcache/lookup', - collect: true, - root - }) + const memcache = trace.add('Datastore/operation/Memcache/lookup', null, web) memcache.setDurationInMillis(20, 8) + transaction.timer.setDurationInMillis(DURATION) trace.end() /* @@ -1103,6 +1018,21 @@ async function makeTrace(agent) { * outermost version having its scope always set to 'ROOT'. The null bits * are parameters, which are optional, and so far, unimplemented for Node. */ + const dbSegment = [ + 3, + 17, + 'Database/statement/AntiSQL/select/getSome', + { nr_exclusive_duration_millis: 14 }, + [] + ] + const memcacheSegment = [ + 8, + 28, + 'Datastore/operation/Memcache/lookup', + { nr_exclusive_duration_millis: 20 }, + [] + ] + const rootSegment = [ 0, DURATION, @@ -1118,15 +1048,10 @@ async function makeTrace(agent) { 'request.parameters.test': 'value', nr_exclusive_duration_millis: 8 }, - [ - // TODO: ensure that the ordering is correct WRT start time - db.toJSON(), - memcache.toJSON() - ] + [dbSegment, memcacheSegment] ] ] ] - const rootNode = [ trace.root.timer.start / 1000, {}, @@ -1147,7 +1072,6 @@ async function makeTrace(agent) { return { transaction, trace, - rootSegment, rootNode, expectedEncoding: [ 0, diff --git a/test/unit/transaction/trace/segment.test.js b/test/unit/transaction/trace/segment.test.js index b7b0fa03ec..66a0652de3 100644 --- a/test/unit/transaction/trace/segment.test.js +++ b/test/unit/transaction/trace/segment.test.js @@ -27,36 +27,6 @@ test('TraceSegment', async (t) => { t.beforeEach(beforeEach) t.afterEach(afterEach) - await t.test('should not add new children when marked as opaque', (t) => { - const { agent } = t.nr - const trans = new Transaction(agent) - const root = trans.trace.root - const segment = new TraceSegment({ - config: agent.config, - name: 'UnitTest', - collect: true, - root - }) - assert.ok(!segment.opaque) - segment.opaque = true - segment.add({ - config: agent.config, - name: 'child', - collect: true, - root - }) - assert.equal(segment.children.length, 0) - segment.opaque = false - segment.add({ - config: agent.config, - name: 'child', - collect: true, - root - }) - assert.equal(segment.children.length, 1) - trans.end() - }) - await t.test('has a name', (t) => { const { agent } = t.nr const trans = new Transaction(agent) @@ -70,19 +40,6 @@ test('TraceSegment', async (t) => { assert.equal(success.name, 'UnitTest') }) - await t.test('is created with no children', (t) => { - const { agent } = t.nr - const trans = new Transaction(agent) - const root = trans.trace.root - const segment = new TraceSegment({ - config: agent.config, - name: 'UnitTest', - collect: true, - root - }) - assert.equal(segment.children.length, 0) - }) - await t.test('has a timer', (t) => { const { agent } = t.nr const trans = new Transaction(agent) @@ -202,7 +159,7 @@ test('TraceSegment', async (t) => { collect: true, root }) - segment.toJSON() + transaction.trace.toJSON() assert.deepEqual(segment.getAttributes(), {}) }) @@ -231,8 +188,9 @@ test('TraceSegment', async (t) => { trace.end() - // See documentation on TraceSegment.toJSON for what goes in which field. - assert.deepEqual(segment.toJSON(), [ + // get serialized segment from trace + const serializedSegment = trace.toJSON()[4][0] + assert.deepEqual(serializedSegment, [ 3, 17, 'DB/select/getSome', @@ -283,7 +241,7 @@ test('TraceSegment', async (t) => { segment.timer.start = 1001 segment.overwriteDurationInMillis(3) - segment.finalize() + segment.finalize(transaction.trace) assert.equal(segment.name, `Truncated/${segmentName}`) assert.equal(root.getDurationInMillis(), 4) @@ -299,17 +257,9 @@ test('with children created from URLs', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace - const root = transaction.trace.root - const segment = trace.add('UnitTest') - const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add({ - config: ctx.nr.agent, - name: url, - collect: true, - root - }) + const webChild = trace.add(url) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -318,6 +268,7 @@ test('with children created from URLs', async (t) => { trace.end() ctx.nr.webChild = webChild + ctx.nr.trace = trace }) t.afterEach(afterEach) @@ -350,8 +301,10 @@ test('with children created from URLs', async (t) => { }) await t.test('should serialize the segment with the parameters', (t) => { - const { webChild } = t.nr - assert.deepEqual(webChild.toJSON(), [ + const { trace } = t.nr + // get serialized segment from trace + const serializedSegment = trace.toJSON()[4][0] + assert.deepEqual(serializedSegment, [ 0, 1, 'WebTransaction/NormalizedUri/*', @@ -374,11 +327,8 @@ test('with parameters parsed out by framework', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace - const root = trace.root trace.mer = 6 - const segment = trace.add('UnitTest') - const url = '/test' const params = {} @@ -387,12 +337,7 @@ test('with parameters parsed out by framework', async (t) => { params[1] = 'another' params.test3 = '50' - const webChild = segment.add({ - config: ctx.nr.agent.config, - name: url, - collect: true, - root - }) + const webChild = trace.add(url) transaction.trace.attributes.addAttributes(DESTINATIONS.TRANS_SCOPE, params) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -429,7 +374,7 @@ test('with parameters parsed out by framework', async (t) => { }) await t.test('should serialize the segment with the parameters', (t) => { - const { webChild } = t.nr + const { trace } = t.nr const expected = [ 0, 1, @@ -442,7 +387,9 @@ test('with parameters parsed out by framework', async (t) => { }, [] ] - assert.deepEqual(webChild.toJSON(), expected) + // get serialized segment from trace + const serializedSegment = trace.toJSON()[4][0] + assert.deepEqual(serializedSegment, expected) }) }) @@ -453,17 +400,9 @@ test('with attributes.enabled set to false', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace - const root = trace.root - const segment = trace.add('UnitTest') const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add({ - config: ctx.nr.agent.config, - name: url, - collect: true, - - root - }) + const webChild = trace.add(url) webChild.addAttribute('test', 'non-null value') transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -471,6 +410,7 @@ test('with attributes.enabled set to false', async (t) => { trace.setDurationInMillis(1, 0) webChild.setDurationInMillis(1, 0) ctx.nr.webChild = webChild + ctx.nr.trace = trace }) t.afterEach(afterEach) @@ -485,9 +425,11 @@ test('with attributes.enabled set to false', async (t) => { }) await t.test('should serialize the segment without the parameters', (t) => { - const { webChild } = t.nr + const { trace } = t.nr const expected = [0, 1, 'WebTransaction/NormalizedUri/*', {}, []] - assert.deepEqual(webChild.toJSON(), expected) + // get serialized segment from trace + const serializedSegment = trace.toJSON()[4][0] + assert.deepEqual(serializedSegment, expected) }) }) @@ -504,18 +446,9 @@ test('with attributes.enabled set', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace - const root = trace.root - const segment = trace.add('UnitTest') - const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add({ - config: ctx.nr.agent.config, - name: url, - collect: true, - - root - }) + const webChild = trace.add(url) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) webChild.markAsWeb(transaction) @@ -526,6 +459,7 @@ test('with attributes.enabled set', async (t) => { ctx.nr.webChild = webChild trace.end() + ctx.nr.trace = trace }) t.afterEach(afterEach) @@ -558,8 +492,10 @@ test('with attributes.enabled set', async (t) => { }) await t.test('should serialize the segment with the parameters', (t) => { - const { webChild } = t.nr - assert.deepEqual(webChild.toJSON(), [ + const { trace } = t.nr + // get the specific segment from serialized trace + const serializedSegment = trace.toJSON()[4][0] + assert.deepEqual(serializedSegment, [ 0, 1, 'WebTransaction/NormalizedUri/*', @@ -578,12 +514,7 @@ test('when serialized', async (t) => { const agent = helper.loadMockedAgent() const transaction = new Transaction(agent) const root = transaction.trace.root - const segment = new TraceSegment({ - config: agent.config, - name: 'UnitTest', - collect: true, - root - }) + const segment = transaction.trace.add('UnitTest') ctx.nr = { agent, @@ -600,9 +531,9 @@ test('when serialized', async (t) => { }) await t.test('should create a plain JS array', (t) => { - const { segment } = t.nr + const { segment, transaction } = t.nr segment.end() - const js = segment.toJSON() + const js = transaction.trace.toJSON()[4][0] assert.ok(Array.isArray(js)) assert.equal(typeof js[0], 'number') @@ -615,25 +546,6 @@ test('when serialized', async (t) => { assert.ok(Array.isArray(js[4])) assert.equal(js[4].length, 0) }) - - await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => { - const { segment, agent, root } = t.nr - let parent = segment - for (let i = 0; i < 9000; ++i) { - const child = new TraceSegment({ - config: agent.config, - name: 'Child ' + i, - collect: true, - root - }) - parent.children.push(child) - parent = child - } - - assert.doesNotThrow(function () { - segment.toJSON() - }) - }) }) test('getSpanContext', async (t) => { diff --git a/test/unit/transaction/tracer.test.js b/test/unit/transaction/tracer.test.js index a2ecbd101f..fb8298eba4 100644 --- a/test/unit/transaction/tracer.test.js +++ b/test/unit/transaction/tracer.test.js @@ -209,11 +209,13 @@ test('Tracer', async function (t) { assert.equal(agent.segmentsCreatedInHarvest, 1) assert.equal(tx.numSegments, 1) assert.equal(agent.activeTransactions, 1) + assert.equal(tx.trace.segments.length, 0) tracer.createSegment({ name: 'Test', parent: tx.trace.root, transaction: tx }) assert.equal(agent.totalActiveSegments, 2) assert.equal(agent.segmentsCreatedInHarvest, 2) assert.equal(tx.numSegments, 2) + assert.equal(tx.trace.segments.length, 1) tx.end() assert.equal(agent.activeTransactions, 0) @@ -230,5 +232,18 @@ test('Tracer', async function (t) { }) }, 10) }) + await t.test('skip adding children when parent is opaque', (t) => { + const { agent, tracer } = t.nr + const tx = new Transaction(agent) + tracer.setSegment({ transaction: tx, segment: tx.trace.root }) + const segment = tracer.createSegment({ name: 'Test', parent: tx.trace.root, transaction: tx }) + segment.opaque = true + const segment2 = tracer.createSegment({ name: 'Test1', parent: segment, transaction: tx }) + const segment3 = tracer.createSegment({ name: 'Test2', parent: segment, transaction: tx }) + assert.equal(segment2.id, segment.id) + assert.equal(segment3.id, segment.id) + assert.equal(tx.trace.segments.length, 1) + tx.end() + }) }) }) diff --git a/test/versioned-external/external-repos.js b/test/versioned-external/external-repos.js index c71fb25c2b..ac3ab1c73a 100644 --- a/test/versioned-external/external-repos.js +++ b/test/versioned-external/external-repos.js @@ -15,7 +15,7 @@ const repos = [ { name: 'apollo-server', repository: 'https://github.com/newrelic/newrelic-node-apollo-server-plugin.git', - branch: 'remove-transaction-from-segment', + branch: 'remove-child-segments', additionalFiles: [ 'tests/lib', ] diff --git a/test/versioned/amqplib/amqp-utils.js b/test/versioned/amqplib/amqp-utils.js index 771f6d86d0..4c1c4b1e15 100644 --- a/test/versioned/amqplib/amqp-utils.js +++ b/test/versioned/amqplib/amqp-utils.js @@ -27,7 +27,7 @@ exports.verifyTransaction = verifyTransaction exports.getChannel = getChannel function verifySubscribe(tx, exchange, routingKey) { - const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: ') + const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: ') let segments = [] @@ -39,7 +39,7 @@ function verifySubscribe(tx, exchange, routingKey) { segments = ['MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchange] } - assertSegments(tx.trace.root, segments) + assertSegments(tx.trace, tx.trace.root, segments) assertMetrics( tx.metrics, @@ -51,6 +51,7 @@ function verifySubscribe(tx, exchange, routingKey) { assert.equal(tx.getFullName(), null, 'should not set transaction name') const consume = metrics.findSegment( + tx.trace, tx.trace.root, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchange ) @@ -91,7 +92,7 @@ function verifyDistributedTrace(produceTransaction, consumeTransaction) { consumeTransaction.traceId, 'should have proper trace id' ) - const produceSegment = produceTransaction.trace.root.children[0] + const [produceSegment] = produceTransaction.trace.getChildren(produceTransaction.trace.root.id) assert.equal( produceSegment.id, consumeTransaction.parentSpanId, @@ -121,6 +122,7 @@ function verifyConsumeTransaction(tx, exchange, queue, routingKey) { ) const consume = metrics.findSegment( + tx.trace, tx.trace.root, 'OtherTransaction/Message/RabbitMQ/Exchange/Named/' + exchange ) @@ -143,7 +145,7 @@ function verifyConsumeTransaction(tx, exchange, queue, routingKey) { } function verifySendToQueue(tx) { - assertSegments(tx.trace.root, ['MessageBroker/RabbitMQ/Exchange/Produce/Named/Default']) + assertSegments(tx.trace, tx.trace.root, ['MessageBroker/RabbitMQ/Exchange/Produce/Named/Default']) assertMetrics( tx.metrics, @@ -153,6 +155,7 @@ function verifySendToQueue(tx) { ) const segment = metrics.findSegment( + tx.trace, tx.trace.root, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/Default' ) @@ -165,7 +168,7 @@ function verifySendToQueue(tx) { } function verifyProduce(tx, exchangeName, routingKey) { - const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: ') + const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: ') let segments = [] if (isCallback) { @@ -197,7 +200,7 @@ function verifyProduce(tx, exchangeName, routingKey) { ] } - assertSegments(tx.trace.root, segments, 'should have expected segments') + assertSegments(tx.trace, tx.trace.root, segments, 'should have expected segments') assertMetrics( tx.metrics, @@ -207,6 +210,7 @@ function verifyProduce(tx, exchangeName, routingKey) { ) const segment = metrics.findSegment( + tx.trace, tx.trace.root, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchangeName ) @@ -222,17 +226,17 @@ function verifyProduce(tx, exchangeName, routingKey) { } function verifyGet({ tx, exchangeName, routingKey, queue, assertAttr }) { - const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: ') + const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: ') const produceName = 'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchangeName const consumeName = 'MessageBroker/RabbitMQ/Exchange/Consume/Named/' + queue if (isCallback) { - assertSegments(tx.trace.root, [produceName, consumeName, ['Callback: ']]) + assertSegments(tx.trace, tx.trace.root, [produceName, consumeName, ['Callback: ']]) } else { - assertSegments(tx.trace.root, [produceName, consumeName]) + assertSegments(tx.trace, tx.trace.root, [produceName, consumeName]) } assertMetrics(tx.metrics, [[{ name: produceName }], [{ name: consumeName }]], false, false) if (assertAttr) { - const segment = metrics.findSegment(tx.trace.root, consumeName) + const segment = metrics.findSegment(tx.trace, tx.trace.root, consumeName) const attributes = segment.getAttributes() assert.equal(attributes.host, params.rabbitmq_host, 'should have host on segment') assert.equal(attributes.port, params.rabbitmq_port, 'should have port on segment') @@ -241,7 +245,7 @@ function verifyGet({ tx, exchangeName, routingKey, queue, assertAttr }) { } function verifyPurge(tx) { - const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: ') + const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: ') let segments = [] if (isCallback) { @@ -272,7 +276,7 @@ function verifyPurge(tx) { 'MessageBroker/RabbitMQ/Queue/Purge/Temp' ] } - assertSegments(tx.trace.root, segments, 'should have expected segments') + assertSegments(tx.trace, tx.trace.root, segments, 'should have expected segments') assertMetrics(tx.metrics, [[{ name: 'MessageBroker/RabbitMQ/Queue/Purge/Temp' }]], false, false) } diff --git a/test/versioned/amqplib/callback.test.js b/test/versioned/amqplib/callback.test.js index 9d1e7e87f6..f14f635125 100644 --- a/test/versioned/amqplib/callback.test.js +++ b/test/versioned/amqplib/callback.test.js @@ -74,7 +74,7 @@ test('amqplib callback instrumentation', async function (t) { helper.runInTransaction(agent, function (tx) { amqplib.connect(amqpUtils.CON_STRING, null, function (err, _conn) { assert.ok(!err, 'should not break connection') - const [segment] = tx.trace.root.children + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal(segment.name, 'amqplib.connect') const attrs = segment.getAttributes() assert.equal(attrs.host, 'localhost') diff --git a/test/versioned/amqplib/promises.test.js b/test/versioned/amqplib/promises.test.js index 7999e04a6d..3ab386cdf7 100644 --- a/test/versioned/amqplib/promises.test.js +++ b/test/versioned/amqplib/promises.test.js @@ -65,7 +65,7 @@ test('amqplib promise instrumentation', async function (t) { const { agent, amqplib } = t.nr await helper.runInTransaction(agent, async function (tx) { const _conn = await amqplib.connect(amqpUtils.CON_STRING) - const [segment] = tx.trace.root.children + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal(segment.name, 'amqplib.connect') const attrs = segment.getAttributes() assert.equal(attrs.host, 'localhost') diff --git a/test/versioned/aws-sdk-v2/amazon-dax-client.test.js b/test/versioned/aws-sdk-v2/amazon-dax-client.test.js index 6702f44e9c..169c46a4ac 100644 --- a/test/versioned/aws-sdk-v2/amazon-dax-client.test.js +++ b/test/versioned/aws-sdk-v2/amazon-dax-client.test.js @@ -61,11 +61,19 @@ test('amazon-dax-client', async (t) => { const root = transaction.trace.root // Won't have the attributes cause not making web request... - const segments = common.getMatchingSegments(root, common.DATASTORE_PATTERN) + const segments = common.getMatchingSegments({ + trace: transaction.trace, + segment: root, + pattern: common.DATASTORE_PATTERN + }) assert.equal(segments.length, 1) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: transaction.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const segment = segments[0] diff --git a/test/versioned/aws-sdk-v2/dynamodb.test.js b/test/versioned/aws-sdk-v2/dynamodb.test.js index f9a06c1e1e..eabaf38b3b 100644 --- a/test/versioned/aws-sdk-v2/dynamodb.test.js +++ b/test/versioned/aws-sdk-v2/dynamodb.test.js @@ -92,11 +92,19 @@ test('DynamoDB', async (t) => { function finish(end, tests, tx) { const root = tx.trace.root - const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN) + const segments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.DATASTORE_PATTERN + }) assert.equal(segments.length, tests.length, `should have ${tests.length} aws datastore segments`) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') segments.forEach((segment, i) => { diff --git a/test/versioned/aws-sdk-v2/http-services.test.js b/test/versioned/aws-sdk-v2/http-services.test.js index fef4215c6d..d469799ddf 100644 --- a/test/versioned/aws-sdk-v2/http-services.test.js +++ b/test/versioned/aws-sdk-v2/http-services.test.js @@ -238,7 +238,11 @@ test('AWS HTTP Services', async (t) => { }) function finish(end, service, operation, tx) { - const externals = common.checkAWSAttributes(tx.trace.root, common.EXTERN_PATTERN) + const externals = common.checkAWSAttributes({ + trace: tx.trace, + segment: tx.trace.root, + pattern: common.EXTERN_PATTERN + }) if (assert.equal(externals.length, 1, 'should have an aws external')) { const attrs = externals[0].attributes.get(common.SEGMENT_DESTINATION) match(attrs, { diff --git a/test/versioned/aws-sdk-v2/s3.test.js b/test/versioned/aws-sdk-v2/s3.test.js index 50a2fe6d84..69a45ae9e5 100644 --- a/test/versioned/aws-sdk-v2/s3.test.js +++ b/test/versioned/aws-sdk-v2/s3.test.js @@ -82,7 +82,11 @@ test('S3 buckets', async (t) => { }) function finish(end, tx) { - const externals = common.checkAWSAttributes(tx.trace.root, common.EXTERN_PATTERN) + const externals = common.checkAWSAttributes({ + trace: tx.trace, + segment: tx.trace.root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externals.length, 3, 'should have 3 aws externals') const [head, create, del] = externals checkAttrs(head, 'headBucket') diff --git a/test/versioned/aws-sdk-v2/sns.test.js b/test/versioned/aws-sdk-v2/sns.test.js index 5122db0c84..8ab9e9e38e 100644 --- a/test/versioned/aws-sdk-v2/sns.test.js +++ b/test/versioned/aws-sdk-v2/sns.test.js @@ -72,10 +72,18 @@ test('SNS', async (t) => { function finish(end, tx) { const root = tx.trace.root - const messages = common.checkAWSAttributes(root, common.SNS_PATTERN) + const messages = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.SNS_PATTERN + }) assert.equal(messages.length, 1, 'should have 1 message broker segment') - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const attrs = messages[0].attributes.get(common.SEGMENT_DESTINATION) diff --git a/test/versioned/aws-sdk-v2/sqs.test.js b/test/versioned/aws-sdk-v2/sqs.test.js index db711a4db9..02226e3b6c 100644 --- a/test/versioned/aws-sdk-v2/sqs.test.js +++ b/test/versioned/aws-sdk-v2/sqs.test.js @@ -154,7 +154,11 @@ function finish({ const expectedSegmentCount = 3 const root = transaction.trace.root - const segments = common.checkAWSAttributes(root, common.SQS_PATTERN) + const segments = common.checkAWSAttributes({ + trace: transaction.trace, + segment: root, + pattern: common.SQS_PATTERN + }) assert.equal( segments.length, @@ -162,7 +166,11 @@ function finish({ `should have ${expectedSegmentCount} AWS MessageBroker/SQS segments` ) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: transaction.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const [sendMessage, sendMessageBatch, receiveMessage] = segments diff --git a/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js b/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js index 119189d0a9..e7dc8dcce7 100644 --- a/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js +++ b/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js @@ -110,6 +110,7 @@ test.afterEach(afterEach) assert.equal(response.$metadata.requestId, expected.headers['x-amzn-requestid']) assert.deepEqual(body, expected.body) assertSegments( + tx.trace, tx.trace.root, ['Llm/completion/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]], { exact: false } @@ -301,6 +302,7 @@ test.afterEach(afterEach) }) assertSegments( + tx.trace, tx.trace.root, ['Llm/completion/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]], { exact: false } @@ -438,6 +440,7 @@ test('ai21: should properly create errors on create completion (streamed)', asyn }) assertSegments( + tx.trace, tx.trace.root, [ 'Llm/completion/Bedrock/InvokeModelWithResponseStreamCommand', @@ -501,6 +504,7 @@ test('models that do not support streaming should be handled', async (t) => { }) assertSegments( + tx.trace, tx.trace.root, [ 'Llm/embedding/Bedrock/InvokeModelWithResponseStreamCommand', diff --git a/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js b/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js index 99c96ab7e0..5db0c2c898 100644 --- a/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js +++ b/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js @@ -68,6 +68,7 @@ test.afterEach(afterEach) assert.equal(response.$metadata.requestId, expected.headers['x-amzn-requestid']) assert.deepEqual(body, expected.body) assertSegments( + tx.trace, tx.trace.root, ['Llm/embedding/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]], { exact: false } @@ -87,17 +88,18 @@ test.afterEach(afterEach) const events = agent.customEventAggregator.events.toArray() assert.equal(events.length, 1) const embedding = events.filter(([{ type }]) => type === 'LlmEmbedding')[0] + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedEmbedding = { id: /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/, appName: 'New Relic for Node.js tests', request_id: '743dd35b-744b-4ddf-b5c6-c0f3de2e3142', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': modelId, vendor: 'bedrock', ingest_source: 'Node', 'request.model': modelId, - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), input: prompt, error: false } @@ -157,6 +159,7 @@ test.afterEach(afterEach) }) assertSegments( + tx.trace, tx.trace.root, ['Llm/embedding/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]], { exact: false } @@ -164,17 +167,18 @@ test.afterEach(afterEach) const events = agent.customEventAggregator.events.toArray() assert.equal(events.length, 1) const embedding = events.filter(([{ type }]) => type === 'LlmEmbedding')[0] + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedEmbedding = { id: /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/, appName: 'New Relic for Node.js tests', request_id: '743dd35b-744b-4ddf-b5c6-c0f3de2e3142', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': modelId, vendor: 'bedrock', ingest_source: 'Node', 'request.model': modelId, - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), input: prompt, error: true } diff --git a/test/versioned/aws-sdk-v3/client-dynamodb.test.js b/test/versioned/aws-sdk-v3/client-dynamodb.test.js index 587f9bb619..14c11a7d94 100644 --- a/test/versioned/aws-sdk-v3/client-dynamodb.test.js +++ b/test/versioned/aws-sdk-v3/client-dynamodb.test.js @@ -116,7 +116,11 @@ test('DynamoDB', async (t) => { } tx.end() const root = tx.trace.root - const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN) + const segments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.DATASTORE_PATTERN + }) segments.forEach((segment) => { const attrs = segment.attributes.get(common.SEGMENT_DESTINATION) @@ -186,7 +190,11 @@ function createCommands({ lib, tableName }) { function finish({ commands, tx, setDatastoreSpy }) { const root = tx.trace.root - const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN) + const segments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.DATASTORE_PATTERN + }) assert.equal( segments.length, @@ -194,7 +202,11 @@ function finish({ commands, tx, setDatastoreSpy }) { `should have ${commands.length} AWS datastore segments` ) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') segments.forEach((segment, i) => { diff --git a/test/versioned/aws-sdk-v3/common.js b/test/versioned/aws-sdk-v3/common.js index 92259c8193..f1aa64eb08 100644 --- a/test/versioned/aws-sdk-v3/common.js +++ b/test/versioned/aws-sdk-v3/common.js @@ -17,7 +17,7 @@ const assert = require('node:assert') const SEGMENT_DESTINATION = TRANS_SEGMENT const helper = require('../../lib/agent_helper') -function checkAWSAttributes(segment, pattern, markedSegments = []) { +function checkAWSAttributes({ trace, segment, pattern, markedSegments = [] }) { const expectedAttrs = { 'aws.operation': String, 'aws.service': String, @@ -30,27 +30,33 @@ function checkAWSAttributes(segment, pattern, markedSegments = []) { const attrs = segment.attributes.get(TRANS_SEGMENT) match(attrs, expectedAttrs) } - segment.children.forEach((child) => { - checkAWSAttributes(child, pattern, markedSegments) + const children = trace.getChildren(segment.id) + children.forEach((child) => { + checkAWSAttributes({ trace, segment: child, pattern, markedSegments }) }) return markedSegments } -function getMatchingSegments(segment, pattern, markedSegments = []) { +function getMatchingSegments({ trace, segment, pattern, markedSegments = [] }) { if (pattern.test(segment.name)) { markedSegments.push(segment) } - segment.children.forEach((child) => { - getMatchingSegments(child, pattern, markedSegments) + const children = trace.getChildren(segment.id) + children.forEach((child) => { + getMatchingSegments({ trace, segment: child, pattern, markedSegments }) }) return markedSegments } function checkExternals({ service, operations, tx, end }) { - const externals = checkAWSAttributes(tx.trace.root, EXTERN_PATTERN) + const externals = checkAWSAttributes({ + trace: tx.trace, + segment: tx.trace.root, + pattern: EXTERN_PATTERN + }) assert.equal( externals.length, operations.length, @@ -71,11 +77,12 @@ function checkExternals({ service, operations, tx, end }) { } function assertChatCompletionMessages({ tx, chatMsgs, expectedId, modelId, prompt, resContent }) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const baseMsg = { appName: 'New Relic for Node.js tests', request_id: 'eda0760a-c3f0-4fc1-9a1e-75559d642866', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': modelId, vendor: 'bedrock', ingest_source: 'Node', @@ -111,18 +118,19 @@ function assertChatCompletionMessages({ tx, chatMsgs, expectedId, modelId, promp } function assertChatCompletionSummary({ tx, modelId, chatSummary, error = false, numMsgs = 2 }) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedChatSummary = { id: /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/, appName: 'New Relic for Node.js tests', request_id: 'eda0760a-c3f0-4fc1-9a1e-75559d642866', 'llm.conversation_id': 'convo-id', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': modelId, vendor: 'bedrock', ingest_source: 'Node', 'request.model': modelId, - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), 'response.number_of_messages': error ? 1 : numMsgs, 'response.choices.finish_reason': error ? undefined : 'endoftext', 'request.temperature': 0.5, diff --git a/test/versioned/aws-sdk-v3/lambda.test.js b/test/versioned/aws-sdk-v3/lambda.test.js index c1f012443f..fc0c369705 100644 --- a/test/versioned/aws-sdk-v3/lambda.test.js +++ b/test/versioned/aws-sdk-v3/lambda.test.js @@ -21,7 +21,7 @@ const { match } = require('../../lib/custom-assertions') function checkEntityLinkingSegments({ operations, tx, end }) { const root = tx.trace.root - const segments = checkAWSAttributes(root, EXTERN_PATTERN) + const segments = checkAWSAttributes({ trace: tx.trace, segment: root, pattern: EXTERN_PATTERN }) const accountId = tx.agent.config.cloud.aws.account_id const testFunctionName = 'funcName' @@ -47,7 +47,7 @@ function checkNonLinkableSegments({ operations, tx, end }) { // When no account ID or ARN is available, make sure not to set cloud resource id or platform const root = tx.trace.root - const segments = checkAWSAttributes(root, EXTERN_PATTERN) + const segments = checkAWSAttributes({ trace: tx.trace, segment: root, pattern: EXTERN_PATTERN }) const accountId = tx.agent.config?.cloud?.aws?.account_id assert(segments.length > 0, 'should have segments') diff --git a/test/versioned/aws-sdk-v3/lib-dynamodb.test.js b/test/versioned/aws-sdk-v3/lib-dynamodb.test.js index c85bf1da3a..dd5507c3b1 100644 --- a/test/versioned/aws-sdk-v3/lib-dynamodb.test.js +++ b/test/versioned/aws-sdk-v3/lib-dynamodb.test.js @@ -136,11 +136,19 @@ test('DynamoDB', async (t) => { function finish(end, tests, tx) { const root = tx.trace.root - const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN) + const segments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.DATASTORE_PATTERN + }) assert.equal(segments.length, tests.length, `should have ${tests.length} aws datastore segments`) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const accountId = tx.agent.config.cloud.aws.account_id diff --git a/test/versioned/aws-sdk-v3/sns.test.js b/test/versioned/aws-sdk-v3/sns.test.js index 3e58a839f8..9b2af8d465 100644 --- a/test/versioned/aws-sdk-v3/sns.test.js +++ b/test/versioned/aws-sdk-v3/sns.test.js @@ -207,11 +207,19 @@ test('SNS', async (t) => { function finish(end, tx, destName, setLibrarySpy) { const root = tx.trace.root - const messages = common.checkAWSAttributes(root, common.SNS_PATTERN) + const messages = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.SNS_PATTERN + }) assert.equal(messages.length, 1, 'should have 1 message broker segment') assert.ok(messages[0].name.endsWith(destName), 'should have appropriate destination') - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: tx.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const attrs = messages[0].attributes.get(common.SEGMENT_DESTINATION) diff --git a/test/versioned/aws-sdk-v3/sqs.test.js b/test/versioned/aws-sdk-v3/sqs.test.js index 8f4b523fab..05471b00ad 100644 --- a/test/versioned/aws-sdk-v3/sqs.test.js +++ b/test/versioned/aws-sdk-v3/sqs.test.js @@ -91,7 +91,11 @@ function finish({ transaction, queueName, setLibrarySpy }) { const expectedSegmentCount = 3 const root = transaction.trace.root - const segments = common.checkAWSAttributes(root, common.SQS_PATTERN) + const segments = common.checkAWSAttributes({ + trace: transaction.trace, + segment: root, + pattern: common.SQS_PATTERN + }) assert.equal( segments.length, @@ -99,7 +103,11 @@ function finish({ transaction, queueName, setLibrarySpy }) { `should have ${expectedSegmentCount} AWS MessageBroker/SQS segments` ) - const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN) + const externalSegments = common.checkAWSAttributes({ + trace: transaction.trace, + segment: root, + pattern: common.EXTERN_PATTERN + }) assert.equal(externalSegments.length, 0, 'should not have any External segments') const [sendMessage, sendMessageBatch, receiveMessage] = segments diff --git a/test/versioned/cassandra-driver/query.test.js b/test/versioned/cassandra-driver/query.test.js index 224c4aeda6..dd1545f6b9 100644 --- a/test/versioned/cassandra-driver/query.test.js +++ b/test/versioned/cassandra-driver/query.test.js @@ -111,11 +111,8 @@ test('executeBatch - callback style', (t, end) => { assert.ok(agent.getTransaction(), 'transaction should still be visible') assert.equal(value.rows[0][COL], colValArr[0], 'cassandra client should still work') - assert.equal( - transaction.trace.root.children.length, - 1, - 'there should be only one child of the root' - ) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 1, 'there should be only one child of the root') verifyTrace(agent, transaction.trace, `${KS}.${FAM}`) transaction.end() checkMetric(agent) @@ -143,12 +140,8 @@ test('executeBatch - promise style', (t, end) => { .then((result) => { assert.ok(agent.getTransaction(), 'transaction should still be visible') assert.equal(result.rows[0][COL], colValArr[0], 'cassandra client should still work') - - assert.equal( - transaction.trace.root.children.length, - 2, - 'there should be two children of the root' - ) + const children = transaction.trace.getChildren(transaction.trace.root.id) + assert.equal(children.length, 2, 'there should be two children of the root') verifyTrace(agent, transaction.trace, `${KS}.${FAM}`) transaction.end() checkMetric(agent) @@ -233,6 +226,7 @@ function verifyTrace(agent, trace, table) { assert.ok(trace.root, 'root element should exist') const setSegment = findSegment( + trace, trace.root, 'Datastore/statement/Cassandra/' + table + '/insert/batch' ) @@ -242,18 +236,22 @@ function verifyTrace(agent, trace, table) { if (setSegment) { verifyTraceSegment(agent, setSegment, 'insert/batch') + const children = trace.getChildren(setSegment.id) assert.ok( - setSegment.children.length >= 2, + children.length >= 2, 'set should have at least a dns lookup and callback/promise child' ) - - const getSegment = findSegment(trace.root, 'Datastore/statement/Cassandra/' + table + '/select') + const getSegment = findSegment( + trace, + trace.root, + 'Datastore/statement/Cassandra/' + table + '/select' + ) assert.ok(getSegment, 'trace segment for select should exist') if (getSegment) { + const getChildren = trace.getChildren(getSegment.id) verifyTraceSegment(agent, getSegment, 'select') - - assert.ok(getSegment.children.length >= 1, 'get should have a callback/promise segment') + assert.ok(getChildren.length >= 1, 'get should have a callback/promise segment') assert.ok(getSegment.timer.hrDuration, 'trace segment should have ended') } } diff --git a/test/versioned/connect/route.test.js b/test/versioned/connect/route.test.js index d6525ce692..977fc14762 100644 --- a/test/versioned/connect/route.test.js +++ b/test/versioned/connect/route.test.js @@ -39,7 +39,7 @@ test('should properly name transaction from route name', async (t) => { plan.equal(tx.verb, 'GET', 'HTTP method is GET') plan.equal(tx.statusCode, 200, 'status code is OK') plan.ok(tx.trace, 'transaction has trace') - const web = tx.trace.root.children[0] + const [web] = tx.trace.getChildren(tx.trace.root.id) plan.ok(web, 'trace has web segment') plan.equal(web.name, tx.name, 'segment name and transaction name match') plan.equal(web.partialName, 'Connect/GET//foo', 'should have partial name for apdex') @@ -73,7 +73,7 @@ test('should default to `/` when no route is specified', async (t) => { plan.equal(tx.verb, 'GET', 'HTTP method is GET') plan.equal(tx.statusCode, 200, 'status code is OK') plan.ok(tx.trace, 'transaction has trace') - const web = tx.trace.root.children[0] + const [web] = tx.trace.getChildren(tx.trace.root.id) plan.ok(web, 'trace has web segment') plan.equal(web.name, tx.name, 'segment name and transaction name match') plan.equal(web.partialName, 'Connect/GET//', 'should have partial name for apdex') diff --git a/test/versioned/disabled-instrumentation/disabled-express.test.js b/test/versioned/disabled-instrumentation/disabled-express.test.js index 83763685c4..fd67eeabda 100644 --- a/test/versioned/disabled-instrumentation/disabled-express.test.js +++ b/test/versioned/disabled-instrumentation/disabled-express.test.js @@ -36,7 +36,7 @@ test('should still record child segments if express instrumentation is disabled' assert.equal(tx.name, 'WebTransaction/NormalizedUri/*', 'should not name transactions') const rootSegment = tx.trace.root const expectedSegments = ['WebTransaction/NormalizedUri/*', ['Datastore/operation/Redis/get']] - assertSegments(rootSegment, expectedSegments) + assertSegments(tx.trace, rootSegment, expectedSegments) resolve() }) }) diff --git a/test/versioned/disabled-instrumentation/disabled-ioredis.test.js b/test/versioned/disabled-instrumentation/disabled-ioredis.test.js index c433d08789..684e217ce6 100644 --- a/test/versioned/disabled-instrumentation/disabled-ioredis.test.js +++ b/test/versioned/disabled-instrumentation/disabled-ioredis.test.js @@ -48,7 +48,7 @@ test('Disabled PG scenarios', async (t) => { await collection.countDocuments() await redisClient.get('bar') tx.end() - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'Datastore/statement/MongoDB/disabled-inst-test/aggregate', 'Datastore/statement/MongoDB/disabled-inst-test/next' ]) @@ -65,7 +65,7 @@ test('Disabled PG scenarios', async (t) => { redisClient.get('bar', (innerErr) => { tx.end() assert.equal(innerErr, null) - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'Datastore/statement/MongoDB/disabled-inst-test/aggregate', 'Datastore/statement/MongoDB/disabled-inst-test/next' ]) diff --git a/test/versioned/elastic/elasticsearch.test.js b/test/versioned/elastic/elasticsearch.test.js index 74bbed80ee..a50d45f203 100644 --- a/test/versioned/elastic/elasticsearch.test.js +++ b/test/versioned/elastic/elasticsearch.test.js @@ -104,8 +104,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.ok(transaction, 'transaction should be visible') await client.indices.create({ index }) const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/ElasticSearch/${index}/index.create`, @@ -120,8 +119,7 @@ test('Elasticsearch instrumentation', async (t) => { await bulkInsert({ client, pkgVersion }) assert.ok(transaction, 'transaction should still be visible after bulk create') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/ElasticSearch/any/bulk.create', @@ -145,10 +143,9 @@ test('Elasticsearch instrumentation', async (t) => { }) assert.ok(transaction, 'transaction should still be visible after bulk create') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - assert.ok(trace?.root?.children?.[1], 'trace, trace root, and second child should exist') // helper interface results in a first child of timers.setTimeout, with the second child related to the operation - const secondChild = trace.root.children[1] + const [firstChild, secondChild] = trace.getChildren(trace.root.id) + assert.ok(firstChild) assert.equal( secondChild.name, 'Datastore/statement/ElasticSearch/any/bulk.create', @@ -169,8 +166,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/ElasticSearch/${DB_INDEX_2}/search`, @@ -205,8 +201,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/ElasticSearch/${DB_INDEX}/search`, @@ -244,8 +239,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/ElasticSearch/any/search', @@ -293,8 +287,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.equal(results?.[1]?.hits?.hits?.length, 8, 'second search should return ten results') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/ElasticSearch/any/msearch.create', @@ -335,8 +328,7 @@ test('Elasticsearch instrumentation', async (t) => { assert.equal(resultsB?.hits?.length, 8, 'second search should return ten results') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'timers.setTimeout', @@ -423,7 +415,7 @@ test('Elasticsearch instrumentation', async (t) => { ...documentProp }) - const createSegment = transaction.trace.root.children[0] + const [createSegment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = createSegment.getAttributes() assert.equal(attributes.host, undefined, 'should not have host attribute') assert.equal(attributes.port_path_or_id, undefined, 'should not have port attribute') @@ -446,7 +438,7 @@ test('Elasticsearch instrumentation', async (t) => { } catch (e) { assert.ok(e, 'should not be able to create an index named _search') } - const firstChild = transaction?.trace?.root?.children[0] + const [firstChild] = transaction.trace.getChildren(transaction.trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/ElasticSearch/_search/index.create', diff --git a/test/versioned/elastic/elasticsearchNoop.test.js b/test/versioned/elastic/elasticsearchNoop.test.js index 1ffd758f6b..c31bfb0686 100644 --- a/test/versioned/elastic/elasticsearchNoop.test.js +++ b/test/versioned/elastic/elasticsearchNoop.test.js @@ -46,7 +46,7 @@ test('Elasticsearch instrumentation', async (t) => { } catch (e) { assert.ok(!e, 'should not error') } - const firstChild = transaction?.trace?.root?.children[0] + const [firstChild] = transaction.trace.getChildren(transaction.trace.root.id) assert.equal( firstChild.name, `External/localhost:9200/${DB_INDEX}`, diff --git a/test/versioned/express-esm/segments.test.mjs b/test/versioned/express-esm/segments.test.mjs index b200e66de3..665e82580e 100644 --- a/test/versioned/express-esm/segments.test.mjs +++ b/test/versioned/express-esm/segments.test.mjs @@ -58,6 +58,7 @@ test('first two segments are built-in Express middleware', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']], assertSegmentsOptions @@ -87,6 +88,7 @@ test('segments for route handler', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']], assertSegmentsOptions @@ -104,6 +106,7 @@ test('route function names are in segment names', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions @@ -121,6 +124,7 @@ test('middleware mounted on a path should produce correct names', async (t) => { const { transaction } = await runTest({ agent, server, endpoint: '/test/1' }) const routeSegment = findSegment( + transaction.trace, transaction.trace.root, NAMES.EXPRESS.MIDDLEWARE + 'handler//test/:id' ) @@ -144,6 +148,7 @@ test('each handler in route has its own segment', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Route Path: /test', @@ -170,6 +175,7 @@ test('segments for routers', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint: '/router1/test' }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router1', @@ -202,6 +208,7 @@ test('two root routers', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /', @@ -238,6 +245,7 @@ test('router mounted as a route handler', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, [ `Expressjs/Route Path: ${segmentPath}`, @@ -268,6 +276,7 @@ test('segments for routers', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint: '/router1/test' }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router1', @@ -300,6 +309,7 @@ test('segments for sub-app', async (t) => { : 'Expressjs/Mounted App: /subapp1' assertSegments( + transaction.trace, rootSegment, [firstSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']]], assertSegmentsOptions @@ -338,6 +348,7 @@ test('segments for sub-app router', async (t) => { : 'Expressjs/Mounted App: /subapp1' assertSegments( + transaction.trace, rootSegment, [ firstSegment, @@ -375,6 +386,7 @@ test('segments for wildcard', async (t) => { : 'Expressjs/Mounted App: /subapp1' assertSegments( + transaction.trace, rootSegment, [firstSegment, ['Expressjs/Route Path: /:app', [NAMES.EXPRESS.MIDDLEWARE + '']]], assertSegmentsOptions @@ -409,6 +421,7 @@ test('router with subapp', async (t) => { : 'Expressjs/Mounted App: /subapp1' assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router1', @@ -432,7 +445,12 @@ test('mounted middleware', async (t) => { }) const { rootSegment, transaction } = await runTest({ agent, server }) - assertSegments(rootSegment, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test'], assertSegmentsOptions) + assertSegments( + transaction.trace, + rootSegment, + [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test'], + assertSegmentsOptions + ) checkMetrics(transaction.metrics, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test']) }) @@ -450,6 +468,7 @@ test('error middleware', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Route Path: /test', @@ -488,6 +507,7 @@ test('error handler in router', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router', @@ -531,6 +551,7 @@ test('error handler in second router', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router1', @@ -574,6 +595,7 @@ test('error handler outside of router', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router', @@ -614,6 +636,7 @@ test('error handler outside of two routers', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint }) assertSegments( + transaction.trace, rootSegment, [ 'Expressjs/Router: /router1', @@ -645,6 +668,7 @@ test('when using a route variable', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint: '/a/b' }) assertSegments( + transaction.trace, rootSegment, ['Expressjs/Route Path: /:foo/:bar', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions @@ -667,6 +691,7 @@ test('when using a string pattern in path', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint: '/abcd' }) assertSegments( + transaction.trace, rootSegment, [`Expressjs/Route Path: ${path}`, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions @@ -684,6 +709,7 @@ test('when using a regular expression in path', async (t) => { const { rootSegment, transaction } = await runTest({ agent, server, endpoint: '/a' }) assertSegments( + transaction.trace, rootSegment, ['Expressjs/Route Path: /a/', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions @@ -694,7 +720,7 @@ test('when using a regular expression in path', async (t) => { async function runTest({ agent, server, endpoint = '/test', errors = 0 }) { const transaction = await makeRequestAndFinishTransaction({ server, agent, endpoint }) - const rootSegment = transaction.trace.root.children[0] + const [rootSegment] = transaction.trace.getChildren(transaction.trace.root.id) assert.equal(agent.errors.traceAggregator.errors.length, errors, `should have ${errors} errors`) return { rootSegment, transaction } diff --git a/test/versioned/express-esm/transaction-naming.test.mjs b/test/versioned/express-esm/transaction-naming.test.mjs index 78f196e1dc..4a998177ff 100644 --- a/test/versioned/express-esm/transaction-naming.test.mjs +++ b/test/versioned/express-esm/transaction-naming.test.mjs @@ -401,7 +401,8 @@ test('Express transaction names are unaffected by errorware', async (t) => { const promise = new Promise((resolve) => { transactionHandler = function (tx) { const expected = 'WebTransaction/Expressjs/GET//test' - plan.equal(tx.trace.root.children[0].name, expected) + const [baseSegment] = tx.trace.getChildren(tx.trace.root.id) + plan.equal(baseSegment.name, expected) resolve() } }) diff --git a/test/versioned/express/async-handlers.test.js b/test/versioned/express/async-handlers.test.js index da6cb2e24b..1a0bf31103 100644 --- a/test/versioned/express/async-handlers.test.js +++ b/test/versioned/express/async-handlers.test.js @@ -36,8 +36,8 @@ test('async handlers', { skip: !isExpress5() }, async (t) => { }) const tx = await runTest(t, '/test') - const [children] = tx.trace.root.children - const [mw, handler] = children.children + const [child] = tx.trace.getChildren(tx.trace.root.id) + const [mw, handler] = tx.trace.getChildren(child.id) assert.ok( Math.ceil(mw.getDurationInMillis()) >= mwTimeout, `should be at least ${mwTimeout} for middleware segment` diff --git a/test/versioned/express/bare-router.test.js b/test/versioned/express/bare-router.test.js index 1c9712f070..73459f38b6 100644 --- a/test/versioned/express/bare-router.test.js +++ b/test/versioned/express/bare-router.test.js @@ -35,7 +35,7 @@ test('Express router introspection', async function (t) { plan.equal(transaction.verb, 'GET', 'HTTP method is GET') plan.ok(transaction.trace, 'transaction has trace') - const web = transaction.trace.root.children[0] + const [web] = transaction.trace.getChildren(transaction.trace.root.id) plan.ok(web, 'trace has web segment') plan.equal(web.name, transaction.name, 'segment name and transaction name match') diff --git a/test/versioned/express/client-disconnect.test.js b/test/versioned/express/client-disconnect.test.js index 6536ba5779..860d099806 100644 --- a/test/versioned/express/client-disconnect.test.js +++ b/test/versioned/express/client-disconnect.test.js @@ -44,6 +44,7 @@ test('Client Premature Disconnection', { timeout: 3000 }, (t, end) => { agent.on('transactionFinished', (transaction) => { assertSegments( + transaction.trace, transaction.trace.root, [ 'WebTransaction/Expressjs/POST//test', diff --git a/test/versioned/express/router-params.test.js b/test/versioned/express/router-params.test.js index a273a9c84b..a4aec2f475 100644 --- a/test/versioned/express/router-params.test.js +++ b/test/versioned/express/router-params.test.js @@ -46,7 +46,7 @@ test('Express router introspection', async function (t) { plan.equal(transaction.verb, 'GET', 'HTTP method is GET') plan.ok(transaction.trace, 'transaction has trace') - const web = transaction.trace.root.children[0] + const [web] = transaction.trace.getChildren(transaction.trace.root.id) plan.ok(web, 'trace has web segment') plan.equal(web.name, transaction.name, 'segment name and transaction name match') plan.equal( diff --git a/test/versioned/express/segments.test.js b/test/versioned/express/segments.test.js index 7eb98b0bd5..3758a56a28 100644 --- a/test/versioned/express/segments.test.js +++ b/test/versioned/express/segments.test.js @@ -36,10 +36,11 @@ test('first two segments are built-in Express middlewares', function (t, end) { res.end() }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { // TODO: check for different HTTP methods assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']], assertSegmentsOptions ) @@ -59,7 +60,7 @@ test('middleware with child segment gets named correctly', function (t, end) { }, 1) }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { checkMetrics(transaction.metrics, [NAMES.EXPRESS.MIDDLEWARE + '//test']) end() @@ -73,9 +74,10 @@ test('segments for route handler', function (t, end) { res.end() }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']], assertSegmentsOptions ) @@ -93,9 +95,10 @@ test('route function names are in segment names', function (t, end) { res.end() }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions ) @@ -113,9 +116,10 @@ test('middleware mounted on a path should produce correct names', function (t, e res.send() }) - runTest(t, '/test/1', function (segments, transaction) { + runTest(t, '/test/1', function (root, transaction) { const segment = findSegment( - transaction.trace.root, + transaction.trace, + root, NAMES.EXPRESS.MIDDLEWARE + 'handler//test/:id' ) assert.ok(segment) @@ -139,9 +143,10 @@ test('each handler in route has its own segment', function (t, end) { } ) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + 'handler1', NAMES.EXPRESS.MIDDLEWARE + 'handler2'] @@ -168,9 +173,10 @@ test('segments for routers', function (t, end) { app.use('/router1', router) - runTest(t, '/router1/test', function (segments, transaction) { + runTest(t, '/router1/test', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router1', ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']] @@ -203,9 +209,10 @@ test('two root routers', function (t, end) { }) app.use('/', router2) - runTest(t, '/test', function (segments, transaction) { + runTest(t, '/test', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /', 'Expressjs/Router: /', @@ -241,9 +248,10 @@ test('router mounted as a route handler', function (t, end) { } app.get(path, router1) - runTest(t, '/test', function (segments, transaction) { + runTest(t, '/test', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ `Expressjs/Route Path: ${segmentPath}`, [ @@ -274,9 +282,10 @@ test('segments for routers', function (t, end) { app.use('/router1', router) - runTest(t, '/router1/test', function (segments, transaction) { + runTest(t, '/router1/test', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router1', ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']] @@ -304,14 +313,15 @@ test('segments for sub-app', function (t, end) { app.use('/subapp1', subapp) - runTest(t, '/subapp1/test', function (segments, transaction) { + runTest(t, '/subapp1/test', function (root, transaction) { // express 5 no longer handles child routers as mounted applications const firstSegment = isExpress5 ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1' : 'Expressjs/Mounted App: /subapp1' assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [firstSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']]], assertSegmentsOptions ) @@ -345,13 +355,14 @@ test('segments for sub-app router', function (t, end) { app.use('/subapp1', subapp) - runTest(t, '/subapp1/test', function (segments, transaction) { + runTest(t, '/subapp1/test', function (root, transaction) { // express 5 no longer handles child routers as mounted applications const firstSegment = isExpress5 ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1' : 'Expressjs/Mounted App: /subapp1' assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ firstSegment, [ @@ -384,13 +395,14 @@ test('segments for wildcard', function (t, end) { app.use('/subapp1', subapp) - runTest(t, '/subapp1/test', function (segments, transaction) { + runTest(t, '/subapp1/test', function (root, transaction) { // express 5 no longer handles child routers as mounted applications const firstSegment = isExpress5 ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1' : 'Expressjs/Mounted App: /subapp1' assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [firstSegment, ['Expressjs/Route Path: /:app', [NAMES.EXPRESS.MIDDLEWARE + '']]], assertSegmentsOptions ) @@ -416,13 +428,14 @@ test('router with subapp', function (t, end) { router.use('/subapp1', subapp) app.use('/router1', router) - runTest(t, '/router1/subapp1/test', function (segments, transaction) { + runTest(t, '/router1/subapp1/test', function (root, transaction) { // express 5 no longer handles child routers as mounted applications const subAppSegment = isExpress5 ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1' : 'Expressjs/Mounted App: /subapp1' assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router1', [subAppSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']]] @@ -447,9 +460,10 @@ test('mounted middleware', function (t, end) { res.end() }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test'], assertSegmentsOptions ) @@ -471,9 +485,10 @@ test('error middleware', function (t, end) { res.end() }) - runTest(t, function (segments, transaction) { + runTest(t, function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + ''], @@ -518,9 +533,10 @@ test('error handler in router', function (t, end) { endpoint, errors: 0 }, - function (segments, transaction) { + function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router', [ @@ -571,9 +587,10 @@ test('error handler in second router', function (t, end) { endpoint, errors: 0 }, - function (segments, transaction) { + function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router1', [ @@ -624,9 +641,10 @@ test('error handler outside of router', function (t, end) { endpoint, errors: 0 }, - function (segments, transaction) { + function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router', ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '']], @@ -674,9 +692,10 @@ test('error handler outside of two routers', function (t, end) { endpoint, errors: 0 }, - function (segments, transaction) { + function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, [ 'Expressjs/Router: /router1', [ @@ -709,9 +728,10 @@ test('when using a route variable', function (t, end) { res.end() }) - runTest(t, '/a/b', function (segments, transaction) { + runTest(t, '/a/b', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: /:foo/:bar', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions ) @@ -734,9 +754,10 @@ test('when using a string pattern in path', function (t, end) { res.end() }) - runTest(t, '/abcd', function (segments, transaction) { + runTest(t, '/abcd', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: ' + path, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions ) @@ -754,9 +775,10 @@ test('when using a regular expression in path', function (t, end) { res.end() }) - runTest(t, '/a', function (segments, transaction) { + runTest(t, '/a', function (root, transaction) { assertSegments( - transaction.trace.root.children[0], + transaction.trace, + root, ['Expressjs/Route Path: /a/', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']], assertSegmentsOptions ) @@ -785,9 +807,15 @@ for (const enabled of codeLevelMetrics) { res.end() }) - runTest(t, '/chained', function (segments, transaction) { - const routeSegment = findSegment(transaction.trace.root, 'Expressjs/Route Path: /chained') - const [mw1Segment, mw2Segment, handlerSegment] = routeSegment.children + runTest(t, '/chained', function (root, transaction) { + const routeSegment = findSegment( + transaction.trace, + transaction.trace.root, + 'Expressjs/Route Path: /chained' + ) + const [mw1Segment, mw2Segment, handlerSegment] = transaction.trace.getChildren( + routeSegment.id + ) const defaultPath = 'test/versioned/express/segments.test.js' assertCLMAttrs({ segments: [ @@ -832,11 +860,11 @@ function runTest(t, options, callback) { } agent.on('transactionFinished', function (tx) { - const baseSegment = tx.trace.root.children[0] + const [baseSegment] = tx.trace.getChildren(tx.trace.root.id) assert.equal(agent.errors.traceAggregator.errors.length, errors, 'should have errors') - callback(baseSegment.children, tx) + callback(baseSegment, tx) }) makeRequest(port, endpoint, function (response) { diff --git a/test/versioned/express/transaction-naming.test.js b/test/versioned/express/transaction-naming.test.js index dade6c9fc2..e9eb3f8058 100644 --- a/test/versioned/express/transaction-naming.test.js +++ b/test/versioned/express/transaction-naming.test.js @@ -382,7 +382,8 @@ test('Express transaction names are unaffected by errorware', async function (t) agent.on('transactionFinished', function (tx) { const expected = 'WebTransaction/Expressjs/GET//test' - plan.equal(tx.trace.root.children[0].name, expected) + const [baseSegment] = tx.trace.getChildren(tx.trace.root.id) + plan.equal(baseSegment.name, expected) }) app.use('/test', function () { diff --git a/test/versioned/fastify/add-hook.test.js b/test/versioned/fastify/add-hook.test.js index 97020e9e5a..f0d1bbf260 100644 --- a/test/versioned/fastify/add-hook.test.js +++ b/test/versioned/fastify/add-hook.test.js @@ -108,7 +108,7 @@ test('non-error hooks', async (t) => { ] ] } - assertSegments(transaction.trace.root, expectedSegments) + assertSegments(transaction.trace, transaction.trace.root, expectedSegments) txPassed = true }) @@ -168,7 +168,7 @@ test('error hook', async function errorHookTest(t) { ] } - assertSegments(transaction.trace.root, expectedSegments) + assertSegments(transaction.trace, transaction.trace.root, expectedSegments) txPassed = true }) diff --git a/test/versioned/fastify/code-level-metrics-hooks.test.js b/test/versioned/fastify/code-level-metrics-hooks.test.js index 31ad174960..cafcf13d00 100644 --- a/test/versioned/fastify/code-level-metrics-hooks.test.js +++ b/test/versioned/fastify/code-level-metrics-hooks.test.js @@ -43,11 +43,12 @@ async function performTest(t) { let txPassed = false agent.on('transactionFinished', (transaction) => { - const baseSegment = transaction.trace.root.children - const [onRequestSegment, handlerSegment] = helper.isSecurityAgentEnabled(agent) - ? baseSegment[0].children[0].children - : baseSegment[0].children - const onSendSegment = handlerSegment.children[0] + const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id) + let [onRequestSegment, handlerSegment] = transaction.trace.getChildren(baseSegment.id) + if (helper.isSecurityAgentEnabled(agent)) { + ;[onRequestSegment, handlerSegment] = transaction.trace.getChildren(onRequestSegment.id) + } + const [onSendSegment] = transaction.trace.getChildren(handlerSegment.id) assertCLMAttrs({ segments: [ { diff --git a/test/versioned/fastify/code-level-metrics-middleware.test.js b/test/versioned/fastify/code-level-metrics-middleware.test.js index 0449d88310..b112ce2c2a 100644 --- a/test/versioned/fastify/code-level-metrics-middleware.test.js +++ b/test/versioned/fastify/code-level-metrics-middleware.test.js @@ -53,9 +53,12 @@ async function setup(t, config) { } } -function assertSegments(testContext, baseSegment, isCLMEnabled) { - const { agent } = testContext.nr - const { children } = helper.isSecurityAgentEnabled(agent) ? baseSegment.children[0] : baseSegment +function assertSegments({ t, trace, baseSegment, isCLMEnabled }) { + const { agent } = t.nr + let children = trace.getChildren(baseSegment.id) + if (helper.isSecurityAgentEnabled(agent)) { + children = trace.getChildren(children[0].id) + } // TODO: once we drop v2 support, this function can be removed and assert inline in test below if (semver.satisfies(pkgVersion, '>=3')) { const [middieSegment, handlerSegment] = children @@ -105,7 +108,13 @@ async function performTest(t) { agent.on('transactionFinished', (transaction) => { calls.test++ - assertSegments(t, transaction.trace.root.children[0], agent.config.code_level_metrics.enabled) + const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id) + assertSegments({ + t, + baseSegment, + trace: transaction.trace, + isCLMEnabled: agent.config.code_level_metrics.enabled + }) }) await fastify.listen({ port: 0 }) diff --git a/test/versioned/fastify/naming-common.js b/test/versioned/fastify/naming-common.js index d7ef4a51ad..efb21e6da7 100644 --- a/test/versioned/fastify/naming-common.js +++ b/test/versioned/fastify/naming-common.js @@ -43,7 +43,7 @@ module.exports = async function runTests(t, getExpectedSegments) { ] } - assertSegments(transaction.trace.root, expectedSegments) + assertSegments(transaction.trace, transaction.trace.root, expectedSegments) }) await fastify.listen({ port: 0 }) diff --git a/test/versioned/grpc/util.cjs b/test/versioned/grpc/util.cjs index 0e8205d0ec..8425326753 100644 --- a/test/versioned/grpc/util.cjs +++ b/test/versioned/grpc/util.cjs @@ -151,8 +151,8 @@ util.assertExternalSegment = function assertExternalSegment( ) { const methodName = util.getRPCName(fnName) const segmentName = `${EXTERNAL.PREFIX}${CLIENT_ADDR}:${port}${methodName}` - assertSegments(tx.trace.root, [segmentName], { exact: false }, { assert }) - const segment = metricsHelpers.findSegment(tx.trace.root, segmentName) + assertSegments(tx.trace, tx.trace.root, [segmentName], { exact: false }, { assert }) + const segment = metricsHelpers.findSegment(tx.trace, tx.trace.root, segmentName) const attributes = segment.getAttributes() assert.equal( attributes.url, diff --git a/test/versioned/hapi/render.test.js b/test/versioned/hapi/render.test.js index 592ac1731c..7500b15c76 100644 --- a/test/versioned/hapi/render.test.js +++ b/test/versioned/hapi/render.test.js @@ -103,13 +103,14 @@ test('using EJS templates', { timeout: 2000 }, (t, end) => { }) function verifyEnded(root, tx) { - for (let i = 0, len = root.children.length; i < len; i++) { - const segment = root.children[i] + const children = tx.trace.getChildren(root.id) + for (let i = 0, len = children.length; i < len; i++) { + const segment = children[i] assert.ok( segment.timer.hasEnd(), util.format('verify %s (%s) has ended', segment.name, tx.id) ) - if (segment.children) { + if (tx.trace.getChildren(segment.id)) { verifyEnded(segment, tx) } } diff --git a/test/versioned/hapi/router.test.js b/test/versioned/hapi/router.test.js index 2e23fcacc9..7b4682b17c 100644 --- a/test/versioned/hapi/router.test.js +++ b/test/versioned/hapi/router.test.js @@ -42,7 +42,7 @@ function verifier(verb = 'GET') { assert.equal(transaction.verb, verb, 'HTTP method is ' + verb) assert.ok(transaction.trace, 'transaction has trace') - const web = transaction.trace.root.children[0] + const [web] = transaction.trace.getChildren(transaction.trace.root.id) assert.ok(web, 'trace has web segment') assert.equal(web.name, transaction.name, 'segment name and transaction name match') @@ -269,8 +269,9 @@ test('using custom handler defaults', (t, end) => { test('404 transaction is named correctly', (t, end) => { const { agent, server } = t.nr agent.on('transactionFinished', function (tx) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) assert.equal( - tx.trace.root.children[0].name, + segment.name, 'WebTransaction/Nodejs/GET/(not found)', '404 segment has standardized name' ) diff --git a/test/versioned/hapi/segments.test.js b/test/versioned/hapi/segments.test.js index f814609a1d..7bc5a6327c 100644 --- a/test/versioned/hapi/segments.test.js +++ b/test/versioned/hapi/segments.test.js @@ -30,8 +30,8 @@ test.afterEach((ctx) => { function runTest(agent, server, callback) { agent.on('transactionFinished', function (tx) { - const baseSegment = tx.trace.root.children[0] - callback(baseSegment.children, tx) + const [ baseSegment ] = tx.trace.getChildren(tx.trace.root.id) + callback(baseSegment, tx) }) server.start().then(function () { @@ -78,9 +78,9 @@ test('route handler is recorded as middleware', (t, end) => { } }) - runTest(agent, server, function (segments, transaction) { + runTest(agent, server, function (baseSegment, transaction) { checkMetrics(transaction.metrics, [NAMES.HAPI.MIDDLEWARE + 'myHandler//test']) - assertSegments(transaction.trace.root.children[0], [NAMES.HAPI.MIDDLEWARE + 'myHandler//test']) + assertSegments(transaction.trace, baseSegment, [NAMES.HAPI.MIDDLEWARE + 'myHandler//test']) end() }) }) @@ -100,9 +100,9 @@ test('custom handler type is recorded as middleware', (t, end) => { handler: { customHandler: { key1: 'val1' } } }) - runTest(agent, server, function (segments, transaction) { + runTest(agent, server, function (baseSegment, transaction) { checkMetrics(transaction.metrics, [NAMES.HAPI.MIDDLEWARE + 'customHandler//test']) - assertSegments(transaction.trace.root.children[0], [ + assertSegments(transaction.trace, baseSegment, [ NAMES.HAPI.MIDDLEWARE + 'customHandler//test' ]) end() @@ -124,12 +124,12 @@ test('extensions are recorded as middleware', (t, end) => { } }) - runTest(agent, server, function (segments, transaction) { + runTest(agent, server, function (baseSegment, transaction) { checkMetrics(transaction.metrics, [ NAMES.HAPI.MIDDLEWARE + '//onRequest', NAMES.HAPI.MIDDLEWARE + 'myHandler//test' ]) - assertSegments(transaction.trace.root.children[0], [ + assertSegments(transaction.trace, baseSegment, [ NAMES.HAPI.MIDDLEWARE + '//onRequest', NAMES.HAPI.MIDDLEWARE + 'myHandler//test' ]) @@ -156,12 +156,12 @@ test('custom route handler and extension recorded as middleware', (t, end) => { handler: { customHandler: { key1: 'val1' } } }) - runTest(agent, server, function (segments, transaction) { + runTest(agent, server, function (baseSegment, transaction) { checkMetrics(transaction.metrics, [ NAMES.HAPI.MIDDLEWARE + '//onRequest', NAMES.HAPI.MIDDLEWARE + 'customHandler//test' ]) - assertSegments(transaction.trace.root.children[0], [ + assertSegments(transaction.trace, baseSegment, [ NAMES.HAPI.MIDDLEWARE + '//onRequest', NAMES.HAPI.MIDDLEWARE + 'customHandler//test' ]) @@ -191,8 +191,8 @@ for (const clmEnabled of [true, false]) { } }) - runTest(agent, server, function (segments) { - const [onRequestSegment, handlerSegment] = segments + runTest(agent, server, function (baseSegment, transaction) { + const [onRequestSegment, handlerSegment] = transaction.trace.getChildren(baseSegment.id) assertClmAttrs({ segments: [ { @@ -232,7 +232,8 @@ for (const clmEnabled of [true, false]) { handler: { customHandler: { key1: 'val1' } } }) - runTest(agent, server, function ([customHandlerSegment]) { + runTest(agent, server, function (baseSegment, transaction) { + const [customHandlerSegment] = transaction.trace.getChildren(baseSegment.id) assertClmAttrs({ segments: [ { @@ -269,7 +270,8 @@ for (const clmEnabled of [true, false]) { } server.register(plugin).then(() => { - runTest(agent, server, function ([pluginHandlerSegment]) { + runTest(agent, server, function (baseSegment, transaction) { + const [pluginHandlerSegment] = transaction.trace.getChildren(baseSegment.id) assertClmAttrs({ segments: [ { diff --git a/test/versioned/ioredis/ioredis.test.js b/test/versioned/ioredis/ioredis.test.js index 60c5870b37..ccdfdd6b39 100644 --- a/test/versioned/ioredis/ioredis.test.js +++ b/test/versioned/ioredis/ioredis.test.js @@ -77,16 +77,18 @@ test('ioredis instrumentation', async (t) => { agent.on('transactionFinished', function (tx) { const root = tx.trace.root - plan.equal(root.children.length, 2, 'root has two children') + const children = tx.trace.getChildren(root.id) + plan.equal(children.length, 2, 'root has two children') + + const [setSegment, getSegment] = children - const setSegment = root.children[0] plan.equal(setSegment.name, 'Datastore/operation/Redis/set') // ioredis operations return promise, any 'then' callbacks will be sibling segments // of the original redis call - const getSegment = root.children[1] plan.equal(getSegment.name, 'Datastore/operation/Redis/get') - plan.equal(getSegment.children.length, 0, 'should not contain any segments') + const getChildren = tx.trace.getChildren(getSegment.id) + plan.equal(getChildren.length, 0, 'should not contain any segments') }) helper.runInTransaction(agent, async (transaction) => { diff --git a/test/versioned/kafkajs/kafka.test.js b/test/versioned/kafkajs/kafka.test.js index 63f1b1572a..c304bb8b7d 100644 --- a/test/versioned/kafkajs/kafka.test.js +++ b/test/versioned/kafkajs/kafka.test.js @@ -61,27 +61,27 @@ test('send records correctly', async (t) => { const expectedName = 'produce-tx' agent.on('transactionFinished', (tx) => { - if (tx.name !== expectedName) { - return - } + if (tx.name === expectedName) { + const name = `MessageBroker/Kafka/Topic/Produce/Named/${topic}` + const segment = tx.agent.tracer.getSegment() + const children = tx.trace.getChildren(segment.id) - const name = `MessageBroker/Kafka/Topic/Produce/Named/${topic}` - const segment = tx.agent.tracer.getSegment() + const foundSegment = children.find((s) => s.name.endsWith(topic)) + plan.equal(foundSegment.name, name) - const foundSegment = segment.children.find((s) => s.name.endsWith(topic)) - plan.equal(foundSegment.name, name) + const metric = tx.metrics.getMetric(name) + plan.equal(metric.callCount, 1) + const sendMetric = agent.metrics.getMetric( + 'Supportability/Features/Instrumentation/kafkajs/send' + ) + plan.equal(sendMetric.callCount, 1) - const metric = tx.metrics.getMetric(name) - plan.equal(metric.callCount, 1) - const sendMetric = agent.metrics.getMetric( - 'Supportability/Features/Instrumentation/kafkajs/send' - ) - plan.equal(sendMetric.callCount, 1) + const produceTrackingMetric = agent.metrics.getMetric( + `MessageBroker/Kafka/Nodes/${broker}/Produce/${topic}` + ) + plan.equal(produceTrackingMetric.callCount, 1) + } - const produceTrackingMetric = agent.metrics.getMetric( - `MessageBroker/Kafka/Nodes/${broker}/Produce/${topic}` - ) - plan.equal(produceTrackingMetric.callCount, 1) }) helper.runInTransaction(agent, async (tx) => { @@ -192,8 +192,9 @@ test('sendBatch records correctly', async (t) => { if (tx.name === expectedName) { const name = `MessageBroker/Kafka/Topic/Produce/Named/${topic}` const segment = tx.agent.tracer.getSegment() + const children = tx.trace.getChildren(segment.id) - const foundSegment = segment.children.find((s) => s.name.endsWith(topic)) + const foundSegment = children.find((s) => s.name.endsWith(topic)) plan.equal(foundSegment.name, name) const metric = tx.metrics.getMetric(name) @@ -306,6 +307,7 @@ test('consume inside of a transaction', async (t) => { txCount++ if (tx.name === expectedName) { assertSegments( + tx.trace, tx.trace.root, [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`], { @@ -362,6 +364,7 @@ test('consume batch inside of a transaction', async (t) => { const txPromise = new Promise((resolve) => { agent.on('transactionFinished', (tx) => { assertSegments( + tx.trace, tx.trace.root, [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`], { exact: false }, diff --git a/test/versioned/kafkajs/utils.js b/test/versioned/kafkajs/utils.js index 7602663ad1..0e964eb433 100644 --- a/test/versioned/kafkajs/utils.js +++ b/test/versioned/kafkajs/utils.js @@ -96,7 +96,7 @@ utils.verifyConsumeTransaction = ({ plan, tx, topic, clientId }) => { ) plan.equal(tx.getFullName(), expectedName) - const consume = metrics.findSegment(tx.trace.root, expectedName) + const consume = metrics.findSegment(tx.trace, tx.trace.root, expectedName) plan.equal(consume, tx.baseSegment) const attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_SCOPE) @@ -113,7 +113,7 @@ utils.verifyConsumeTransaction = ({ plan, tx, topic, clientId }) => { */ utils.verifyDistributedTrace = ({ plan, consumeTxs, produceTx }) => { plan.ok(produceTx.isDistributedTrace, 'should mark producer as distributed') - const produceSegment = produceTx.trace.root.children[3] + const [, , , produceSegment] = produceTx.trace.getChildren(produceTx.trace.root.id) consumeTxs.forEach((consumeTx) => { plan.ok(consumeTx.isDistributedTrace, 'should mark consumer as distributed') plan.equal(consumeTx.incomingCatId, null, 'should not set old CAT properties') diff --git a/test/versioned/koa/code-level-metrics.test.js b/test/versioned/koa/code-level-metrics.test.js index 5975dfbb32..af7228c7fc 100644 --- a/test/versioned/koa/code-level-metrics.test.js +++ b/test/versioned/koa/code-level-metrics.test.js @@ -93,18 +93,21 @@ test('vanilla koa, no router', async (t) => { ctx.body = 'done' }) - agent.on('transactionFinished', (tx) => { - const baseSegment = tx.trace.root.children[0] + agent.on('transactionFinished', (transaction) => { + const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id) + const [one] = transaction.trace.getChildren(baseSegment.id) + const [two] = transaction.trace.getChildren(one.id) + assertClmAttrs( { segments: [ { - segment: baseSegment.children[0], + segment: one, name: 'one', filepath: 'code-level-metrics.test.js' }, { - segment: baseSegment.children[0].children[0], + segment: two, name: 'two', filepath: 'code-level-metrics.test.js' } @@ -155,23 +158,27 @@ test('using koa-router', async (t) => { router.use('/:first', nestedRouter.routes()) app.use(router.routes()) - agent.on('transactionFinished', (tx) => { - const baseSegment = tx.trace.root.children[0] + agent.on('transactionFinished', (transaction) => { + const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id) + const [dispatch] = transaction.trace.getChildren(baseSegment.id) + const [appLevel] = transaction.trace.getChildren(dispatch.id) + const [secondMw] = transaction.trace.getChildren(appLevel.id) + assertClmAttrs( { segments: [ { - segment: baseSegment.children[0], + segment: dispatch, name: 'dispatch', filepath: 'koa-router/lib/router.js' }, { - segment: baseSegment.children[0].children[0], + segment: appLevel, name: 'appLevelMiddleware', filepath: 'code-level-metrics.test.js' }, { - segment: baseSegment.children[0].children[0].children[0], + segment: secondMw, name: 'secondMiddleware', filepath: 'code-level-metrics.test.js' } @@ -222,23 +229,27 @@ test('using @koa/router', async (t) => { router.use('/:first', nestedRouter.routes()) app.use(router.routes()) - agent.on('transactionFinished', (tx) => { - const baseSegment = tx.trace.root.children[0] + agent.on('transactionFinished', (transaction) => { + const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id) + const [dispatch] = transaction.trace.getChildren(baseSegment.id) + const [appLevel] = transaction.trace.getChildren(dispatch.id) + const [secondMw] = transaction.trace.getChildren(appLevel.id) + assertClmAttrs( { segments: [ { - segment: baseSegment.children[0], + segment: dispatch, name: 'dispatch', filepath: '@koa/router/lib/router.js' }, { - segment: baseSegment.children[0].children[0], + segment: appLevel, name: 'appLevelMiddleware', filepath: 'code-level-metrics.test.js' }, { - segment: baseSegment.children[0].children[0].children[0], + segment: secondMw, name: 'secondMiddleware', filepath: 'code-level-metrics.test.js' } diff --git a/test/versioned/koa/koa-route.test.js b/test/versioned/koa/koa-route.test.js index 05f9fe5340..d6debfb8f7 100644 --- a/test/versioned/koa/koa-route.test.js +++ b/test/versioned/koa/koa-route.test.js @@ -35,7 +35,7 @@ test('should name and produce segments for koa-route middleware', (t, end) => { }) app.use(first) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//resource', ['Nodejs/Middleware/Koa/firstMiddleware//resource'] ]) @@ -61,7 +61,7 @@ test('should name the transaction after the last responder', (t, end) => { app.use(first) app.use(second) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Nodejs/Middleware/Koa/firstMiddleware//:first', @@ -91,7 +91,7 @@ test('should name the transaction properly when responding after next', (t, end) app.use(first) app.use(second) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Nodejs/Middleware/Koa/firstMiddleware//:first', @@ -120,7 +120,7 @@ test('should work with early responding', (t, end) => { app.use(first) app.use(second) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', ['Nodejs/Middleware/Koa/firstMiddleware//:first'] ]) @@ -146,7 +146,7 @@ test('should name the transaction after the source of the error that occurred', app.use(first) app.use(second) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Nodejs/Middleware/Koa/firstMiddleware//:first', @@ -179,7 +179,7 @@ test('should work properly when used along with non-route middleware', (t, end) app.use(second) app.use(third) agent.on('transactionFinished', function (tx) { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//resource', [ 'Nodejs/Middleware/Koa/firstMiddleware', diff --git a/test/versioned/koa/koa.test.js b/test/versioned/koa/koa.test.js index efe9684251..9995bd0cde 100644 --- a/test/versioned/koa/koa.test.js +++ b/test/versioned/koa/koa.test.js @@ -54,6 +54,7 @@ function run({ t, expected = 'done', cb, end, plan }) { function checkSegments(plan, tx) { assertSegments( + tx.trace, tx.trace.root, [ // Until koa-router is instrumented and transaction naming is addressed, @@ -292,6 +293,7 @@ test('correctly records actions interspersed among middleware', async (t) => { agent.on('transactionFinished', (tx) => { assertSegments( + tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//', @@ -355,6 +357,7 @@ test('maintains transaction state between middleware', async (t) => { agent.on('transactionFinished', function (txn) { assertSegments( + tx.trace, tx.trace.root, [ txn.name, diff --git a/test/versioned/koa/router-common.js b/test/versioned/koa/router-common.js index 1d97300b47..a9c37114f4 100644 --- a/test/versioned/koa/router-common.js +++ b/test/versioned/koa/router-common.js @@ -98,7 +98,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Koa/Router: /', @@ -125,7 +125,7 @@ module.exports = (pkg) => { }) app.use(router.middleware()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']] ]) @@ -160,7 +160,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//.*rst$', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//.*rst$/']] ]) @@ -185,7 +185,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ `WebTransaction/WebFrameworkUri/Koa/GET//:first/${path}`, ['Koa/Router: /', [`Nodejs/Middleware/Koa/firstMiddleware//:first/${path}`]] ]) @@ -210,7 +210,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Koa/Router: /', @@ -245,7 +245,7 @@ module.exports = (pkg) => { app.silent = true app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Koa/Router: /', @@ -282,7 +282,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -316,7 +316,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Nodejs/Middleware/Koa/errorHandler', @@ -347,7 +347,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Nodejs/Middleware/Koa/errorHandler', @@ -392,7 +392,7 @@ module.exports = (pkg) => { ] app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', ['Koa/Router: /', segmentTree] ]) @@ -417,7 +417,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', ['Koa/Router: /'] ]) @@ -446,7 +446,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', ['Nodejs/Middleware/Koa/baseMiddleware', ['Koa/Router: /']] ]) @@ -486,7 +486,7 @@ module.exports = (pkg) => { // the dispatch function blocking its returned promise on the // resolution of a recursively returned promise. // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44 - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -524,7 +524,7 @@ module.exports = (pkg) => { // the dispatch function blocking its returned promise on the // resolution of a recursively returned promise. // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44 - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -558,7 +558,7 @@ module.exports = (pkg) => { router.use('/:first', router2.routes()) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', ['Koa/Router: /', [getNestedSpanName('secondMiddleware')]] ]) @@ -588,7 +588,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', @@ -622,7 +622,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', @@ -660,7 +660,7 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) @@ -685,7 +685,7 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) @@ -723,7 +723,7 @@ module.exports = (pkg) => { app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/NormalizedUri/*', [ 'Nodejs/Middleware/Koa/errorHandler', @@ -763,7 +763,7 @@ module.exports = (pkg) => { app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', [ 'Nodejs/Middleware/Koa/baseMiddleware', @@ -797,7 +797,7 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) @@ -822,7 +822,7 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) @@ -854,7 +854,7 @@ module.exports = (pkg) => { app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', @@ -888,7 +888,7 @@ module.exports = (pkg) => { app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', @@ -914,7 +914,7 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - assertSegments(tx.trace.root, [ + assertSegments(tx.trace, tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']] ]) diff --git a/test/versioned/langchain/common.js b/test/versioned/langchain/common.js index 55f88028c8..5fbea4a87f 100644 --- a/test/versioned/langchain/common.js +++ b/test/versioned/langchain/common.js @@ -25,10 +25,11 @@ function assertLangChainVectorSearch( { tx, vectorSearch, responseDocumentSize }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedSearch = { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', - span_id: tx.trace.root.children[0].id, + span_id: segment.id, trace_id: tx.traceId, 'request.k': 1, 'request.query': 'This is an embedding test.', @@ -36,7 +37,7 @@ function assertLangChainVectorSearch( vendor: 'langchain', virtual_llm: true, 'response.number_of_documents': responseDocumentSize, - duration: tx.trace.root.children[0].getDurationInMillis() + duration: segment.getDurationInMillis() } assert.equal(vectorSearch[0].type, 'LlmVectorSearch') @@ -47,11 +48,12 @@ function assertLangChainVectorSearchResult( { tx, vectorSearchResult, vectorSearchId }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const baseSearchResult = { id: /[a-f0-9]{36}/, search_id: vectorSearchId, appName: 'New Relic for Node.js tests', - span_id: tx.trace.root.children[0].id, + span_id: segment.id, trace_id: tx.traceId, ingest_source: 'Node', vendor: 'langchain', @@ -78,10 +80,11 @@ function assertLangChainChatCompletionSummary( { tx, chatSummary, withCallback }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedSummary = { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', - span_id: tx.trace.root.children[0].id, + span_id: segment.id, trace_id: tx.traceId, request_id: undefined, ingest_source: 'Node', @@ -91,7 +94,7 @@ function assertLangChainChatCompletionSummary( tags: 'tag1,tag2', virtual_llm: true, 'response.number_of_messages': 1, - duration: tx.trace.root.children[0].getDurationInMillis() + duration: segment.getDurationInMillis() } if (withCallback) { @@ -114,10 +117,11 @@ function assertLangChainChatCompletionMessages( }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const baseMsg = { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', - span_id: tx.trace.root.children[0].id, + span_id: segment.id, trace_id: tx.traceId, ingest_source: 'Node', vendor: 'langchain', diff --git a/test/versioned/langchain/runnables-streaming.test.js b/test/versioned/langchain/runnables-streaming.test.js index c3bcf9ff25..b13e34ccad 100644 --- a/test/versioned/langchain/runnables-streaming.test.js +++ b/test/versioned/langchain/runnables-streaming.test.js @@ -407,7 +407,7 @@ test('streaming enabled', async (t) => { // no-op } - assertSegments(tx.trace.root, ['Llm/chain/Langchain/stream'], { exact: false }) + assertSegments(tx.trace, tx.trace.root, ['Llm/chain/Langchain/stream'], { exact: false }) tx.end() end() diff --git a/test/versioned/langchain/runnables.test.js b/test/versioned/langchain/runnables.test.js index 6c0796f9fc..9df12167e3 100644 --- a/test/versioned/langchain/runnables.test.js +++ b/test/versioned/langchain/runnables.test.js @@ -358,7 +358,7 @@ test('should create span on successful runnables create', (t, end) => { const result = await chain.invoke(input, options) assert.ok(result) - assertSegments(tx.trace.root, ['Llm/chain/Langchain/invoke'], { exact: false }) + assertSegments(tx.trace, tx.trace.root, ['Llm/chain/Langchain/invoke'], { exact: false }) tx.end() end() diff --git a/test/versioned/langchain/tools.test.js b/test/versioned/langchain/tools.test.js index ba62670542..a0522305c5 100644 --- a/test/versioned/langchain/tools.test.js +++ b/test/versioned/langchain/tools.test.js @@ -44,7 +44,7 @@ test('should create span on successful tools create', (t, end) => { helper.runInTransaction(agent, async (tx) => { const result = await tool.call(input) assert.ok(result) - assertSegments(tx.trace.root, ['Llm/tool/Langchain/node-agent-test-tool'], { exact: false }) + assertSegments(tx.trace, tx.trace.root, ['Llm/tool/Langchain/node-agent-test-tool'], { exact: false }) tx.end() end() }) @@ -75,10 +75,11 @@ test('should create LlmTool event for every tool.call', (t, end) => { assert.equal(events.length, 1, 'should create a LlmTool event') const [[{ type }, toolEvent]] = events assert.equal(type, 'LlmTool') + const [segment] = tx.trace.getChildren(tx.trace.root.id) match(toolEvent, { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', - span_id: tx.trace.root.children[0].id, + span_id: segment.id, trace_id: tx.traceId, ingest_source: 'Node', vendor: 'langchain', @@ -89,7 +90,7 @@ test('should create LlmTool event for every tool.call', (t, end) => { output: tool.fakeData[input], name: tool.name, description: tool.description, - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), run_id: undefined }) tx.end() diff --git a/test/versioned/langchain/vectorstore.test.js b/test/versioned/langchain/vectorstore.test.js index 57d3c71799..39401fb115 100644 --- a/test/versioned/langchain/vectorstore.test.js +++ b/test/versioned/langchain/vectorstore.test.js @@ -95,7 +95,7 @@ test('should create span on successful vectorstore create', (t, end) => { helper.runInTransaction(agent, async (tx) => { const result = await vs.similaritySearch('This is an embedding test.', 1) assert.ok(result) - assertSegments(tx.trace.root, ['Llm/vectorstore/Langchain/similaritySearch'], { + assertSegments(tx.trace, tx.trace.root, ['Llm/vectorstore/Langchain/similaritySearch'], { exact: false }) tx.end() diff --git a/test/versioned/memcached/memcached.test.js b/test/versioned/memcached/memcached.test.js index f9762b9a1c..0d8ff7b6ec 100644 --- a/test/versioned/memcached/memcached.test.js +++ b/test/versioned/memcached/memcached.test.js @@ -56,6 +56,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/touch'], { exact: false }, @@ -91,6 +92,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/get', ['Truncated/Callback: ']], { exact: false }, @@ -126,6 +128,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/gets', ['Truncated/Callback: ']], { exact: false }, @@ -161,6 +164,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/get', ['Truncated/Callback: handle']], { exact: false }, @@ -196,6 +200,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/set', ['Truncated/Callback: ']], { exact: false }, @@ -234,6 +239,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/replace', ['Truncated/Callback: ']], { exact: false }, @@ -270,6 +276,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/add', ['Truncated/Callback: ']], { exact: false }, @@ -311,6 +318,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/cas', ['Truncated/Callback: ']], { exact: false }, @@ -349,6 +357,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(agent.getTransaction(), 'transaction should still be visible') transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/append', ['Truncated/Callback: ']], { exact: false }, @@ -386,6 +395,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(agent.getTransaction(), 'transaction should still be visible') transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/prepend', ['Truncated/Callback: ']], { exact: false }, @@ -423,6 +433,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(agent.getTransaction(), 'transaction should still be visible') transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/delete', ['Truncated/Callback: ']], { exact: false }, @@ -459,6 +470,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/incr', ['Truncated/Callback: ']], { exact: false }, @@ -494,6 +506,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/decr'], { exact: false }, @@ -532,6 +545,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { transaction.end() assertSegments( + transaction.trace, transaction.trace.root, ['Datastore/operation/Memcache/version'], { exact: false }, @@ -587,7 +601,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal(segment.getAttributes().key, '"foo"', 'should have the get key as a parameter') }) }) @@ -604,7 +618,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err) transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) plan.ok(!segment.getAttributes().key, 'should not have any attributes') }) }) @@ -620,7 +634,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal( segment.getAttributes().key, '["foo","bar"]', @@ -640,7 +654,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) plan.equal(segment.getAttributes().key, '"foo"', 'should have the set key as a parameter') }) }) @@ -677,7 +691,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = segment.getAttributes() plan.equal( attributes.host, @@ -706,7 +720,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = segment.getAttributes() plan.equal( attributes.host, @@ -758,7 +772,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = segment.getAttributes() plan.equal(attributes.host, undefined, 'should not have host instance parameter') plan.equal( @@ -786,7 +800,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { plan.ok(!err, 'should not throw an error') transaction.end() - const segment = transaction.trace.root.children[0] + const [segment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = segment.getAttributes() plan.equal(attributes.host, undefined, 'should not have host instance parameter') plan.equal( @@ -854,13 +868,13 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { helper.runInTransaction(agent, function (transaction) { memcached.get('foo', function (err) { assert.ok(!err) - const firstSegment = agent.tracer.getSegment().parent + const firstSegment = transaction.trace.getParent(agent.tracer.getSegment().parentId) memcached.get('bar', function (err) { assert.ok(!err) transaction.end() checkParams(firstSegment, 'server1', '1111') - checkParams(agent.tracer.getSegment().parent, 'server2', '2222') + checkParams(transaction.trace.getParent(agent.tracer.getSegment().parentId), 'server2', '2222') end() }) }) @@ -872,8 +886,7 @@ test('memcached instrumentation', { timeout: 5000 }, async function (t) { helper.runInTransaction(agent, function (transaction) { memcached.getMulti(['foo', 'bar'], function (err) { assert.ok(!err) - const firstGet = transaction.trace.root.children[0] - const secondGet = transaction.trace.root.children[1] + const [firstGet, secondGet] = transaction.trace.getChildren(transaction.trace.root.id) if (firstGet.getAttributes().host === 'server1') { checkParams(firstGet, 'server1', '1111') checkParams(secondGet, 'server2', '2222') diff --git a/test/versioned/mongodb-esm/db.test.mjs b/test/versioned/mongodb-esm/db.test.mjs index 805c059969..0e81485f28 100644 --- a/test/versioned/mongodb-esm/db.test.mjs +++ b/test/versioned/mongodb-esm/db.test.mjs @@ -461,11 +461,12 @@ function verifyMongoSegments({ t, tx, expectedSegments }) { let current = tx.trace.root for (let i = 0, l = expectedSegments.length; i < l; i += 1) { + let children = tx.trace.getChildren(current.id) // Filter out net.createConnection segments as they could occur during // execution, and we don't need to verify them. - current.children = current.children.filter((c) => c.name !== 'net.createConnection') - assert.equal(current.children.length, 1, 'should have one child segment') - current = current.children[0] + children = children.filter((c) => c.name !== 'net.createConnection') + assert.equal(children.length, 1, 'should have one child segment') + current = children[0] assert.equal( current.name, expectedSegments[i], diff --git a/test/versioned/mongodb-esm/test-assertions.mjs b/test/versioned/mongodb-esm/test-assertions.mjs index bd6fc20b82..97044bc10a 100644 --- a/test/versioned/mongodb-esm/test-assertions.mjs +++ b/test/versioned/mongodb-esm/test-assertions.mjs @@ -19,6 +19,7 @@ function getValidatorCallback({ t, tx, segments, metrics, end, childrenLength = const segment = agent.tracer.getSegment() let current = tx.trace.root + let children = tx.trace.getChildren(current.id) if (childrenLength === 2) { // This block is for testing `collection.aggregate`. The `aggregate` @@ -29,27 +30,29 @@ function getValidatorCallback({ t, tx, segments, metrics, end, childrenLength = // on the trace root. We also added a strict flag for `aggregate` because, // depending on the version, there is an extra segment for the callback // of our test which we do not need to assert. - assert.equal(current.children.length, childrenLength, 'should have two children') + assert.equal(children.length, childrenLength, 'should have two children') for (const [i, expectedSegment] of segments.entries()) { - const child = current.children[i] + const child = children[i] + const childChildren = tx.trace.getChildren(child.id) assert.equal(child.name, expectedSegment, `child should be named ${expectedSegment}`) if (common.MONGO_SEGMENT_RE.test(child.name) === true) { checkSegmentParams(child, METRIC_HOST_NAME, METRIC_HOST_PORT) assert.equal(child.ignore, false, 'should not ignore segment') } - assert.equal(child.children.length, 0, 'should have no more children') + assert.equal(childChildren.length, 0, 'should have no more children') } } else { for (let i = 0, l = segments.length; i < l; ++i) { - assert.equal(current.children.length, 1, 'should have one child') - current = current.children[0] + assert.equal(children.length, 1, 'should have one child') + current = children[0] + children = tx.trace.getChildren(current.id) assert.equal(current.name, segments[i], 'child should be named ' + segments[i]) if (common.MONGO_SEGMENT_RE.test(current.name) === true) { checkSegmentParams(current, METRIC_HOST_NAME, METRIC_HOST_PORT) assert.equal(current.ignore, false, 'should not ignore segment') } } - assert.equal(current.children.length, 0, 'should have no more children') + assert.equal(children.length, 0, 'should have no more children') } assert.equal(current === segment, true, 'should test to the current segment') diff --git a/test/versioned/mongodb/collection-common.js b/test/versioned/mongodb/collection-common.js index 0556633f10..3c58b2ff23 100644 --- a/test/versioned/mongodb/collection-common.js +++ b/test/versioned/mongodb/collection-common.js @@ -118,6 +118,7 @@ function collectionTest(name, run) { ) const segment = agent.tracer.getSegment() let current = transaction.trace.root + const children = transaction.trace.getChildren(current.id) // this logic is just for the collection.aggregate. // aggregate no longer returns a callback with cursor @@ -130,10 +131,11 @@ function collectionTest(name, run) { // there is an extra segment for the callback of our test which we do not care // to assert if (childrenLength === 2) { - assert.equal(current.children.length, childrenLength, 'should have one child') + assert.equal(children.length, childrenLength, 'should have one child') segments.forEach((expectedSegment, i) => { - const child = current.children[i] + const child = children[i] + const childChildren = transaction.trace.getChildren(child.id) assert.equal( child.name, @@ -146,13 +148,15 @@ function collectionTest(name, run) { } if (strict) { - assert.equal(child.children.length, 0, 'should have no more children') + assert.equal(childChildren.length, 0, 'should have no more children') } }) } else { + let currentChildren for (let i = 0, l = segments.length; i < l; ++i) { - assert.equal(current.children.length, childrenLength, 'should have one child') - current = current.children[0] + assert.equal(children.length, childrenLength, 'should have one child') + current = children[0] + currentChildren = transaction.trace.getChildren(current.id) assert.equal(current.name, segments[i], 'child should be named ' + segments[i]) if (common.MONGO_SEGMENT_RE.test(current.name)) { checkSegmentParams(current) @@ -161,7 +165,7 @@ function collectionTest(name, run) { } if (strict) { - assert.equal(current.children.length, 0, 'should have no more children') + assert.equal(currentChildren.length, 0, 'should have no more children') } } @@ -202,7 +206,7 @@ function collectionTest(name, run) { assert.ok(attributes.database_name, 'should have database name attribute') assert.ok(attributes.product, 'should have product attribute') } - current = current.children[0] + ;[current] = tx.trace.getChildren(current.id) } end() }) @@ -229,7 +233,7 @@ function collectionTest(name, run) { ) assert.ok(attributes.product, 'should have product attribute') } - current = current.children[0] + ;[current] = tx.trace.getChildren(current.id) } end() }) @@ -278,6 +282,7 @@ function collectionTest(name, run) { ) const segment = agent.tracer.getSegment() let current = transaction.trace.root + const children = transaction.trace.getChildren(current.id) // this logic is just for the collection.aggregate. // aggregate no longer returns a callback with cursor @@ -290,10 +295,11 @@ function collectionTest(name, run) { // there is an extra segment for the callback of our test which we do not care // to assert if (childrenLength === 2) { - assert.equal(current.children.length, childrenLength, 'should have one child') + assert.equal(children.length, childrenLength, 'should have one child') segments.forEach((expectedSegment, i) => { - const child = current.children[i] + const child = children[i] + const childChildren = transaction.trace.getChildren(child.id) assert.equal( child.name, @@ -306,13 +312,15 @@ function collectionTest(name, run) { } if (strict) { - assert.equal(child.children.length, 0, 'should have no more children') + assert.equal(childChildren.length, 0, 'should have no more children') } }) } else { + let currentChildren for (let i = 0, l = segments.length; i < l; ++i) { - assert.equal(current.children.length, childrenLength, 'should have one child') - current = current.children[0] + assert.equal(children.length, childrenLength, 'should have one child') + current = children[0] + currentChildren = transaction.trace.getChildren(current.id) assert.equal(current.name, segments[i], 'child should be named ' + segments[i]) if (common.MONGO_SEGMENT_RE.test(current.name)) { checkSegmentParams(current) @@ -321,7 +329,7 @@ function collectionTest(name, run) { } if (strict) { - assert.equal(current.children.length, 0, 'should have no more children') + assert.equal(currentChildren.length, 0, 'should have no more children') } } diff --git a/test/versioned/mongodb/db-common.js b/test/versioned/mongodb/db-common.js index 4e0edac58b..b102a884e1 100644 --- a/test/versioned/mongodb/db-common.js +++ b/test/versioned/mongodb/db-common.js @@ -79,15 +79,16 @@ function verifyMongoSegments(agent, transaction, names, opts) { let child for (let i = 0, l = names.length; i < l; ++i) { + let children = transaction.trace.getChildren(current.id) if (opts.legacy) { // Filter out net.createConnection segments as they could occur during execution, which is fine // but breaks out assertion function - current.children = current.children.filter((c) => c.name !== 'net.createConnection') - assert.equal(current.children.length, 1, 'should have one child segment') - child = current.children[0] - current = current.children[0] + children = children.filter((c) => c.name !== 'net.createConnection') + assert.equal(children.length, 1, 'should have one child segment') + child = children[0] + current = children[0] } else { - child = current.children[i] + child = children[i] } assert.equal(child.name, names[i], 'segment should be named ' + names[i]) diff --git a/test/versioned/mysql/basic-pool.js b/test/versioned/mysql/basic-pool.js index 050524afea..f8fe13ed6d 100644 --- a/test/versioned/mysql/basic-pool.js +++ b/test/versioned/mysql/basic-pool.js @@ -120,9 +120,12 @@ module.exports = function ({ factory, constants, pkgVersion }) { const { agent, pool } = t.nr helper.runInTransaction(agent, function transactionInScope(txn) { pool.query('SELECT 1 + 1 AS solution', function (err) { - const seg = txn.trace.root.children[0].children.filter(function (trace) { + const [firstChild] = txn.trace.getChildren(txn.trace.root.id) + const children = txn.trace.getChildren(firstChild.id) + + const [seg] = children.filter(function (trace) { return /Datastore\/statement\/MySQL/.test(trace.name) - })[0] + }) const attributes = seg.getAttributes() assert.ok(!err, 'should not error') @@ -146,7 +149,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { helper.runInTransaction(agent, function transactionInScope(txn) { agent.config.datastore_tracer.instance_reporting.enabled = false pool.query('SELECT 1 + 1 AS solution', function (err) { - const seg = getDatastoreSegment(agent.tracer.getSegment()) + const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() }) assert.ok(!err, 'should not error making query') assert.ok(seg, 'should have a segment') @@ -167,7 +170,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { helper.runInTransaction(agent, function transactionInScope(txn) { agent.config.datastore_tracer.database_name_reporting.enabled = false pool.query('SELECT 1 + 1 AS solution', function (err) { - const seg = getDatastoreSegment(agent.tracer.getSegment()) + const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() }) const attributes = seg.getAttributes() assert.ok(!err, 'no errors') assert.ok(seg, 'there is a segment') @@ -199,7 +202,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { // In the case where you don't have a server running on // localhost the data will still be correctly associated // with the query. - const seg = getDatastoreSegment(agent.tracer.getSegment()) + const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() }) const attributes = seg.getAttributes() assert.ok(seg, 'there is a segment') assert.equal(attributes.host, agent.config.getHostnameSafe(), 'set host') @@ -220,7 +223,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const defaultPool = mysql.createPool(defaultConfig) helper.runInTransaction(agent, function transactionInScope(txn) { defaultPool.query('SELECT 1 + 1 AS solution', function (err) { - const seg = getDatastoreSegment(agent.tracer.getSegment()) + const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() }) const attributes = seg.getAttributes() assert.ok(!err, 'should not error making query') @@ -267,7 +270,8 @@ module.exports = function ({ factory, constants, pkgVersion }) { helper.runInTransaction(agent, function transactionInScope(txn) { pool.query('SELECT 1 + 1 AS solution123123123123', function (err) { const transaction = agent.getTransaction() - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) + assert.ok(!err, 'no error occurred') assert.ok(transaction, 'transaction should exist') @@ -289,7 +293,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { assert.ok(!err) assert.ok(transaction, 'should not lose transaction') if (transaction) { - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -314,7 +318,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { connection.query('SELECT 1 + 1 AS solution', function (err) { const transaction = agent.getTransaction() - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(!err, 'no error occurred') assert.ok(transaction, 'transaction should exist') @@ -344,7 +348,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { assert.ok(!err) assert.ok(transaction, 'should not lose transaction') if (transaction) { - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -372,7 +376,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { socketPool.query('SELECT 1 + 1 AS solution', function (err) { assert.ok(!err, 'should not error making query') - const seg = getDatastoreSegment(agent.tracer.getSegment()) + const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() }) const attributes = seg.getAttributes() // In the case where you don't have a server running on localhost @@ -455,7 +459,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const transaction = agent.getTransaction() assert.ok(transaction, 'transaction should exist') assert.equal(transaction.id, txn.id, 'transaction must be same') - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -494,7 +498,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const transaction = agent.getTransaction() assert.ok(transaction, 'transaction should exist') assert.equal(transaction.id, txn.id, 'transaction must be same') - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -533,7 +537,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const transaction = agent.getTransaction() assert.ok(transaction, 'transaction should exist') assert.equal(transaction.id, txn.id, 'transaction must be same') - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -571,7 +575,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const transaction = agent.getTransaction() assert.ok(transaction, 'transaction should exist') assert.equal(transaction.id, txn.id, 'transaction must be same') - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -611,7 +615,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { const currentTransaction = agent.getTransaction() assert.ok(currentTransaction, 'transaction should exist') assert.equal(currentTransaction.id, txn.id, 'transaction must be same') - const segment = agent.tracer.getSegment().parent + const segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -638,7 +642,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { assert.ok(transaction, 'transaction should exist') assert.equal(transaction, txn, 'transaction must be same') - let segment = agent.tracer.getSegment().parent + let segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -658,7 +662,7 @@ module.exports = function ({ factory, constants, pkgVersion }) { assert.ok(transaction, 'transaction should exist') assert.equal(transaction, txn, 'transaction must be same') - segment = agent.tracer.getSegment().parent + segment = txn.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(segment, 'segment should exist') assert.ok(segment.timer.start > 0, 'starts at a positive time') assert.ok(segment.timer.start <= Date.now(), 'starts in past') @@ -698,8 +702,8 @@ async function getDomainSocketPath() { } } -function getDatastoreSegment(segment) { - return segment.parent.children.filter(function (s) { +function getDatastoreSegment({ segment, trace }) { + return trace.getChildren(trace.getParent(segment.parentId).id).filter(function (s) { return /^Datastore/.test(s && s.name) })[0] } diff --git a/test/versioned/mysql/basic.js b/test/versioned/mysql/basic.js index 8172194d2f..e53a72c2aa 100644 --- a/test/versioned/mysql/basic.js +++ b/test/versioned/mysql/basic.js @@ -15,6 +15,7 @@ const urltils = require('../../../lib/util/urltils') const params = require('../../lib/params') const setup = require('./setup') const { getClient } = require('./utils') +const { findSegment } = require('../../lib/metrics_helper') module.exports = function ({ lib, factory, poolFactory, constants }) { const { USER, DATABASE, TABLE } = constants @@ -144,7 +145,7 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { await t.test('ensure database name changes with a use statement', function (t, end) { const { agent, pool } = t.nr assert.ok(!agent.getTransaction(), 'no transaction should be in play yet') - helper.runInTransaction(agent, function transactionInScope() { + helper.runInTransaction(agent, function transactionInScope(tx) { assert.ok(agent.getTransaction(), 'we should be in a transaction') getClient(pool, function (err, client) { assert.ok(!err) @@ -155,7 +156,7 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { assert.ok(!err, 'should not fail to set database') client.query('SELECT 1 + 1 AS solution', function (err) { - const seg = agent.tracer.getSegment().parent + const seg = tx.trace.getParent(agent.tracer.getSegment().parentId) const attributes = seg.getAttributes() assert.ok(!err, 'no errors') @@ -246,7 +247,7 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { assert.ok(results && ended, 'result and end events should occur') const traceRoot = transaction.trace.root const traceRootDuration = traceRoot.timer.getDurationInMillis() - const segment = findSegment(traceRoot, 'Datastore/statement/MySQL/unknown/select') + const segment = findSegment(transaction.trace, traceRoot, 'Datastore/statement/MySQL/unknown/select') const queryNodeDuration = segment.timer.getDurationInMillis() assert.ok( @@ -289,20 +290,21 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { const transaction = agent.getTransaction().end() pool.release(client) const traceRoot = transaction.trace.root - const querySegment = traceRoot.children[0] + const [querySegment] = transaction.trace.getChildren(traceRoot.id) + const queryChildren = transaction.trace.getChildren(querySegment.id) assert.equal( - querySegment.children.length, + queryChildren.length, 2, 'the query segment should have two children' ) - const childSegment = querySegment.children[1] + const childSegment = queryChildren[1] assert.equal( childSegment.name, 'Callback: endCallback', 'children should be callbacks' ) - const grandChildSegment = childSegment.children[0] + const [grandChildSegment] = transaction.trace.getChildren(childSegment.id) assert.equal( grandChildSegment.name, 'timers.setTimeout', @@ -373,7 +375,7 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { client.query('use test_db;', function (err) { assert.ok(!err) client.query('SELECT 1 + 1 AS solution', function (err) { - const seg = agent.tracer.getSegment().parent + const seg = txn.trace.getParent(agent.tracer.getSegment().parentId) const attributes = seg.getAttributes() assert.ok(!err) assert.ok(seg, 'should have a segment') @@ -399,12 +401,3 @@ module.exports = function ({ lib, factory, poolFactory, constants }) { }) }) } - -function findSegment(root, segmentName) { - for (let i = 0; i < root.children.length; i++) { - const segment = root.children[i] - if (segment.name === segmentName) { - return segment - } - } -} diff --git a/test/versioned/mysql/pooling.js b/test/versioned/mysql/pooling.js index 06e3ff8cbb..cca9859c9a 100644 --- a/test/versioned/mysql/pooling.js +++ b/test/versioned/mysql/pooling.js @@ -55,9 +55,10 @@ module.exports = function ({ factory, poolFactory, constants }) { const trace = transaction.trace plan.ok(trace, 'trace should exist') plan.ok(trace.root, 'root element should exist.') - plan.equal(trace.root.children.length, 1, 'There should be only one child.') + const children = trace.getChildren(trace.root.id) + plan.equal(children.length, 1, 'There should be only one child.') - const selectSegment = trace.root.children[0] + const selectSegment = children[0] plan.ok(selectSegment, 'trace segment for first SELECT should exist') plan.equal( @@ -66,9 +67,12 @@ module.exports = function ({ factory, poolFactory, constants }) { 'should register as SELECT' ) - plan.equal(selectSegment.children.length, 1, 'should only have a callback segment') - plan.equal(selectSegment.children[0].name, 'Callback: ') - plan.equal(selectSegment.children[0].children.length, 0) + const selectChildren = trace.getChildren(selectSegment.id) + plan.equal(selectChildren.length, 1, 'should only have a callback segment') + const cb = selectChildren[0] + plan.equal(cb.name, 'Callback: ') + const cbChildren = trace.getChildren(cb.id) + plan.equal(cbChildren.length, 0) } }) } diff --git a/test/versioned/mysql2/promises.test.js b/test/versioned/mysql2/promises.test.js index 886a505b29..d1975f24f9 100644 --- a/test/versioned/mysql2/promises.test.js +++ b/test/versioned/mysql2/promises.test.js @@ -93,7 +93,7 @@ test('mysql2 promises', { timeout: 30000 }, async (t) => { activeTx = agent.getTransaction() assert.equal(tx.name, activeTx.name) - const segment = agent.getTransaction().trace.root.children[2] + const [, , segment] = tx.trace.getChildren(tx.trace.root.id) const attributes = segment.getAttributes() assert.equal( attributes.host, diff --git a/test/versioned/nextjs/attributes.test.js b/test/versioned/nextjs/attributes.test.js index 909fb443a7..566d97ddf3 100644 --- a/test/versioned/nextjs/attributes.test.js +++ b/test/versioned/nextjs/attributes.test.js @@ -10,8 +10,7 @@ const assert = require('node:assert') const helpers = require('./helpers') const nextPkg = require('next/package.json') const { - isMiddlewareInstrumentationSupported, - getServerSidePropsSegment + isMiddlewareInstrumentationSupported } = require('../../../lib/instrumentation/nextjs/utils') const middlewareSupported = isMiddlewareInstrumentationSupported(nextPkg.version) const agentHelper = require('../../lib/agent_helper') @@ -199,16 +198,18 @@ test('Next.js', async (t) => { await helpers.makeRequest('/api/person/2?queryParam=queryValue') const [tx] = await txPromise const rootSegment = tx.trace.root + const [handler] = tx.trace.getChildren(rootSegment.id) const segments = [ { - segment: rootSegment.children[0], + segment: handler, name: 'handler', filepath: 'pages/api/person/[id]' } ] if (middlewareSupported) { + const [middleware] = tx.trace.getChildren(handler.id) segments.push({ - segment: rootSegment.children[0].children[0], + segment: middleware, name: 'middleware', filepath: 'middleware' }) @@ -231,20 +232,22 @@ test('Next.js', async (t) => { const [tx] = await txPromise const rootSegment = tx.trace.root const segments = [] + const [first] = tx.trace.getChildren(rootSegment.id) if (middlewareSupported) { + const [middleware, getServerSideProps] = tx.trace.getChildren(first.id) segments.push({ - segment: rootSegment.children[0].children[0], + segment: middleware, name: 'middleware', filepath: 'middleware' }) segments.push({ - segment: rootSegment.children[0].children[1], + segment: getServerSideProps, name: 'getServerSideProps', filepath: 'pages/ssr/people' }) } else { segments.push({ - segment: getServerSidePropsSegment(rootSegment), + segment: helpers.getServerSidePropsSegment(tx.trace), name: 'getServerSideProps', filepath: 'pages/ssr/people' }) @@ -255,8 +258,7 @@ test('Next.js', async (t) => { enabled, skipFull: true }) - } - ) + }) await t.test('should not add CLM attrs to static page segment', async (t) => { agent.config.code_level_metrics = { enabled } @@ -265,21 +267,23 @@ test('Next.js', async (t) => { await helpers.makeRequest('/static/dynamic/testing?queryParam=queryValue') const [tx] = await txPromise const rootSegment = tx.trace.root + const [root] = tx.trace.getChildren(rootSegment.id) // The segment that names the static page will not contain CLM regardless of the // configuration flag assertCLMAttrs({ - segments: [{ segment: rootSegment.children[0] }], + segments: [{ segment: root }], enabled: false, skipFull: true }) if (middlewareSupported) { + const [middleware] = tx.trace.getChildren(root.id) // this will exist when CLM is enabled assertCLMAttrs({ segments: [ { - segment: rootSegment.children[0].children[0], + segment: middleware, name: 'middleware', filepath: 'middleware' } diff --git a/test/versioned/nextjs/helpers.js b/test/versioned/nextjs/helpers.js index 726f9c35f1..48cff34e42 100644 --- a/test/versioned/nextjs/helpers.js +++ b/test/versioned/nextjs/helpers.js @@ -17,6 +17,7 @@ const noServerClose = semver.gte(nextPkg.version, '13.4.15') // just emit SIGTERM after 14.1.0 const closeEvent = semver.gte(nextPkg.version, '14.1.0') ? 'SIGTERM' : 'exit' const { DESTINATIONS } = require('../../../lib/config/attribute-filter') +const { findSegment } = require('../../lib/metrics_helper') /** * Builds a Next.js app @@ -105,22 +106,6 @@ helpers.registerInstrumentation = function (agent) { hooks.forEach(agent.registerInstrumentation) } -helpers.findSegmentByName = function (root, name) { - if (root.name === name) { - return root - } else if (root.children && root.children.length) { - for (let i = 0; i < root.children.length; i++) { - const child = root.children[i] - const found = helpers.findSegmentByName(child, name) - if (found) { - return found - } - } - } - - return null -} - helpers.getTransactionEventAgentAttributes = function getTransactionEventAgentAttributes( transaction ) { @@ -134,7 +119,7 @@ helpers.getTransactionIntrinsicAttributes = function getTransactionIntrinsicAttr } helpers.getSegmentAgentAttributes = function getSegmentAgentAttributes(transaction, name) { - const segment = helpers.findSegmentByName(transaction.trace.root, name) + const segment = findSegment(transaction.trace, transaction.trace.root, name) if (segment) { return segment.attributes.get(DESTINATIONS.SPAN_EVENT) } @@ -166,3 +151,9 @@ helpers.setupTransactionHandler = function setupTransactionHandler({ }) }) } + +helpers.getServerSidePropsSegment = function getServerSidePropsSegment(trace) { + const [first] = trace.getChildren(trace.root.id) + const children = trace.getChildren(first.id) + return children.find((segment) => segment.name.includes('getServerSideProps')) +} diff --git a/test/versioned/nextjs/segments.test.js b/test/versioned/nextjs/segments.test.js index e3e03d320f..f0cda7dcf8 100644 --- a/test/versioned/nextjs/segments.test.js +++ b/test/versioned/nextjs/segments.test.js @@ -67,7 +67,7 @@ test('Next.js', async (t) => { children: getChildSegments(URI) } ] - assertSegments(tx.trace.root, expectedSegments, { exact: false }) + assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false }) }) await t.test('should properly name getServerSideProps segments on dynamic pages', async (t) => { @@ -86,7 +86,7 @@ test('Next.js', async (t) => { children: getChildSegments(EXPECTED_URI) } ] - assertSegments(tx.trace.root, expectedSegments, { exact: false }) + assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false }) }) await t.test( @@ -116,7 +116,7 @@ test('Next.js', async (t) => { ] } - assertSegments(tx.trace.root, expectedSegments, { exact: false }) + assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false }) } ) }) diff --git a/test/versioned/openai/chat-completions.test.js b/test/versioned/openai/chat-completions.test.js index 19f454a5be..6114302139 100644 --- a/test/versioned/openai/chat-completions.test.js +++ b/test/versioned/openai/chat-completions.test.js @@ -66,6 +66,7 @@ test('should create span on successful chat completion create', (t, end) => { assert.equal(results.choices[0].message.content, '1 plus 2 is 3.') assertSegments( + tx.trace, tx.trace.root, [OPENAI.COMPLETION, [`External/${host}:${port}/chat/completions`]], { exact: false } @@ -148,6 +149,7 @@ if (semver.gte(pkgVersion, '4.12.2')) { assert.equal(chunk.choices[0].message.content, res) assertSegments( + tx.trace, tx.trace.root, [OPENAI.COMPLETION, [`External/${host}:${port}/chat/completions`]], { exact: false } @@ -362,6 +364,7 @@ if (semver.gte(pkgVersion, '4.12.2')) { assert.equal(events.length, 0) // we will still record the external segment but not the chat completion assertSegments( + tx.trace, tx.trace.root, ['timers.setTimeout', `External/${host}:${port}/chat/completions`], { exact: false } diff --git a/test/versioned/openai/common.js b/test/versioned/openai/common.js index 63fe83dc43..468e5e7232 100644 --- a/test/versioned/openai/common.js +++ b/test/versioned/openai/common.js @@ -16,11 +16,12 @@ function assertChatCompletionMessages( { tx, chatMsgs, id, model, reqContent, resContent, tokenUsage }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const baseMsg = { appName: 'New Relic for Node.js tests', request_id: '49dbbffbd3c3f4612aa48def69059aad', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': model, vendor: 'openai', ingest_source: 'Node', @@ -65,17 +66,18 @@ function assertChatCompletionSummary( { tx, model, chatSummary, error = false }, { assert = require('node:assert') } = {} ) { + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedChatSummary = { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', request_id: '49dbbffbd3c3f4612aa48def69059aad', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': model, vendor: 'openai', ingest_source: 'Node', 'request.model': model, - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), 'response.organization': 'new-relic-nkmd8b', 'response.headers.llmVersion': '2020-10-01', 'response.headers.ratelimitLimitRequests': '200', diff --git a/test/versioned/openai/embeddings.test.js b/test/versioned/openai/embeddings.test.js index de71aea599..fb7c9042f7 100644 --- a/test/versioned/openai/embeddings.test.js +++ b/test/versioned/openai/embeddings.test.js @@ -62,7 +62,7 @@ test('should create span on successful embedding create', (t, end) => { assert.equal(results.headers, undefined, 'should remove response headers from user result') assert.equal(results.model, 'text-embedding-ada-002-v2') - assertSegments(tx.trace.root, [OPENAI.EMBEDDING, [`External/${host}:${port}/embeddings`]], { + assertSegments(tx.trace, tx.trace.root, [OPENAI.EMBEDDING, [`External/${host}:${port}/embeddings`]], { exact: false }) @@ -97,17 +97,18 @@ test('should create an embedding message', (t, end) => { const events = agent.customEventAggregator.events.toArray() assert.equal(events.length, 1, 'should create a chat completion message and summary event') const [embedding] = events + const [segment] = tx.trace.getChildren(tx.trace.root.id) const expectedEmbedding = { id: /[a-f0-9]{36}/, appName: 'New Relic for Node.js tests', request_id: 'c70828b2293314366a76a2b1dcb20688', trace_id: tx.traceId, - span_id: tx.trace.root.children[0].id, + span_id: segment.id, 'response.model': 'text-embedding-ada-002-v2', vendor: 'openai', ingest_source: 'Node', 'request.model': 'text-embedding-ada-002', - duration: tx.trace.root.children[0].getDurationInMillis(), + duration: segment.getDurationInMillis(), 'response.organization': 'new-relic-nkmd8b', token_count: undefined, 'response.headers.llmVersion': '2020-10-01', diff --git a/test/versioned/opensearch/opensearch.test.js b/test/versioned/opensearch/opensearch.test.js index 1e32278ca9..3bd3fb890b 100644 --- a/test/versioned/opensearch/opensearch.test.js +++ b/test/versioned/opensearch/opensearch.test.js @@ -80,8 +80,7 @@ test('opensearch instrumentation', async (t) => { assert.ok(transaction, 'transaction should be visible') await client.indices.create({ index }) const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/OpenSearch/${index}/index.create`, @@ -96,8 +95,7 @@ test('opensearch instrumentation', async (t) => { await bulkInsert({ client }) assert.ok(transaction, 'transaction should still be visible after bulk create') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/OpenSearch/any/bulk.create', @@ -121,10 +119,9 @@ test('opensearch instrumentation', async (t) => { }) assert.ok(transaction, 'transaction should still be visible after bulk create') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - assert.ok(trace?.root?.children?.[1], 'trace, trace root, and second child should exist') // helper interface results in a first child of timers.setTimeout, with the second child related to the operation - const secondChild = trace.root.children[1] + const [firstChild, secondChild] = trace.getChildren(trace.root.id) + assert.ok(firstChild, 'trace, trace root, and first child should exist') assert.equal( secondChild.name, 'Datastore/statement/OpenSearch/any/bulk.create', @@ -145,8 +142,7 @@ test('opensearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/OpenSearch/${DB_INDEX_2}/search`, @@ -181,8 +177,7 @@ test('opensearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, `Datastore/statement/OpenSearch/${DB_INDEX}/search`, @@ -220,8 +215,7 @@ test('opensearch instrumentation', async (t) => { assert.ok(search, 'search should return a result') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/OpenSearch/any/search', @@ -265,8 +259,7 @@ test('opensearch instrumentation', async (t) => { assert.equal(results?.[1]?.hits?.hits?.length, 8, 'second search should return ten results') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/OpenSearch/any/msearch.create', @@ -307,8 +300,7 @@ test('opensearch instrumentation', async (t) => { assert.equal(resultsB?.hits?.length, 8, 'second search should return ten results') assert.ok(transaction, 'transaction should still be visible after search') const trace = transaction.trace - assert.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist') - const firstChild = trace.root.children[0] + const [firstChild] = trace.getChildren(trace.root.id) assert.equal( firstChild.name, 'timers.setTimeout', @@ -389,7 +381,7 @@ test('opensearch instrumentation', async (t) => { ...documentProp }) - const createSegment = transaction.trace.root.children[0] + const [createSegment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = createSegment.getAttributes() assert.equal(attributes.host, undefined, 'should not have host attribute') assert.equal(attributes.port_path_or_id, undefined, 'should not have port attribute') @@ -412,7 +404,7 @@ test('opensearch instrumentation', async (t) => { } catch (e) { assert.ok(e, 'should not be able to create an index named _search') } - const firstChild = transaction?.trace?.root?.children[0] + const [firstChild] = transaction.trace.getChildren(transaction.trace.root.id) assert.equal( firstChild.name, 'Datastore/statement/OpenSearch/_search/index.create', diff --git a/test/versioned/pg-esm/pg.common.mjs b/test/versioned/pg-esm/pg.common.mjs index 5f5d70612c..cd64de3cd0 100644 --- a/test/versioned/pg-esm/pg.common.mjs +++ b/test/versioned/pg-esm/pg.common.mjs @@ -127,9 +127,14 @@ export default function runTests(name, clientFactory) { expect.ok(trace, 'trace should exist') expect.ok(trace.root, 'root element should exist') - const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert') + const setSegment = findSegment( + trace, + trace.root, + 'Datastore/statement/Postgres/' + TABLE + '/insert' + ) const getSegment = findSegment( + trace, trace.root, 'Datastore/statement/Postgres/' + selectTable + '/select' ) @@ -154,7 +159,11 @@ export default function runTests(name, clientFactory) { const agent = transaction.agent const trace = transaction.trace - const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert') + const setSegment = findSegment( + trace, + trace.root, + 'Datastore/statement/Postgres/' + TABLE + '/insert' + ) const attributes = setSegment.getAttributes() const metricHostName = getMetricHostName(agent, params.postgres_host) @@ -546,6 +555,7 @@ export default function runTests(name, clientFactory) { plan.ifError(error) const segment = findSegment( + transaction.trace, transaction.trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert' ) diff --git a/test/versioned/pg/pg.common.js b/test/versioned/pg/pg.common.js index 6ad8ec6160..2dd54a5002 100644 --- a/test/versioned/pg/pg.common.js +++ b/test/versioned/pg/pg.common.js @@ -128,9 +128,14 @@ module.exports = function runTests(name, clientFactory) { expect.ok(trace, 'trace should exist') expect.ok(trace.root, 'root element should exist') - const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert') + const setSegment = findSegment( + trace, + trace.root, + 'Datastore/statement/Postgres/' + TABLE + '/insert' + ) const getSegment = findSegment( + trace, trace.root, 'Datastore/statement/Postgres/' + selectTable + '/select' ) @@ -155,7 +160,11 @@ module.exports = function runTests(name, clientFactory) { const agent = transaction.agent const trace = transaction.trace - const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert') + const setSegment = findSegment( + trace, + trace.root, + 'Datastore/statement/Postgres/' + TABLE + '/insert' + ) const attributes = setSegment.getAttributes() const metricHostName = getMetricHostName(agent, params.postgres_host) @@ -552,6 +561,7 @@ module.exports = function runTests(name, clientFactory) { client.query(config, [pkVal, colVal], function (error) { plan.ifError(error) const segment = findSegment( + transaction.trace, transaction.trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert' ) diff --git a/test/versioned/prisma/prisma.test.js b/test/versioned/prisma/prisma.test.js index ce9f557799..1e487e2ca3 100644 --- a/test/versioned/prisma/prisma.test.js +++ b/test/versioned/prisma/prisma.test.js @@ -58,7 +58,7 @@ test('Basic run through prisma functionality', { timeout: 30 * 1000 }, async (t) await helper.runInTransaction(agent, async (tx) => { const users = await upsertUsers(prisma) assert.equal(users.length, 2, 'should get two users') - const findManySegment = findSegment(tx.trace.root, findMany) + const findManySegment = findSegment(tx.trace, tx.trace.root, findMany) const attributes = findManySegment.getAttributes() assert.ok(!attributes.host, 'should not have a host set') assert.ok(!attributes.port_path_or_id, 'should not have a port set') @@ -79,7 +79,7 @@ test('Basic run through prisma functionality', { timeout: 30 * 1000 }, async (t) const users = await query assert.equal(users.length, 2, 'should get two users') tx.end() - const rawSegment = findSegment(tx.trace.root, raw) + const rawSegment = findSegment(tx.trace, tx.trace.root, raw) assert.ok(rawSegment, `segment named ${raw} should exist`) }) } @@ -96,7 +96,7 @@ test('Basic run through prisma functionality', { timeout: 30 * 1000 }, async (t) const count = await query assert.equal(count, 2, 'should modify two users') tx.end() - const rawSegment = findSegment(tx.trace.root, rawUpdate) + const rawSegment = findSegment(tx.trace, tx.trace.root, rawUpdate) assert.ok(rawSegment, `segment named ${rawUpdate} should exist`) }) } diff --git a/test/versioned/prisma/utils.js b/test/versioned/prisma/utils.js index 28e89462af..e16735a869 100644 --- a/test/versioned/prisma/utils.js +++ b/test/versioned/prisma/utils.js @@ -59,10 +59,10 @@ function verifyTraces(agent, transaction) { assert.ok(trace, 'trace should exist') assert.ok(trace.root, 'root element should exist') - assertSegments(trace.root, [findMany, update, update, findMany], { exact: true }) - const findManySegment = findSegment(trace.root, findMany) + assertSegments(trace, trace.root, [findMany, update, update, findMany], { exact: true }) + const findManySegment = findSegment(trace, trace.root, findMany) assert.ok(findManySegment.timer.hrDuration, 'findMany segment should have ended') - const updateSegment = findSegment(trace.root, update) + const updateSegment = findSegment(trace, trace.root, update) assert.ok(updateSegment.timer.hrDuration, 'update segment should have ended') for (const segment of [findManySegment, updateSegment]) { const attributes = segment.getAttributes() diff --git a/test/versioned/q/q.test.js b/test/versioned/q/q.test.js index daa7eaa416..b78a190fe3 100644 --- a/test/versioned/q/q.test.js +++ b/test/versioned/q/q.test.js @@ -15,7 +15,8 @@ const helper = require('../../lib/agent_helper') function assertTransaction(agent, tx, expect = assert) { expect.equal(agent.getTransaction(), tx) - expect.equal(agent.getTransaction().trace.root.children.length, 0) + const children = tx.trace.getChildren(tx.trace.root.id) + expect.equal(children.length, 0) } test.beforeEach((ctx) => { diff --git a/test/versioned/redis/redis-v4-legacy-mode.test.js b/test/versioned/redis/redis-v4-legacy-mode.test.js index 110107b7d7..c8cd339762 100644 --- a/test/versioned/redis/redis-v4-legacy-mode.test.js +++ b/test/versioned/redis/redis-v4-legacy-mode.test.js @@ -74,16 +74,17 @@ test('Redis instrumentation', async function (t) { const trace = transaction.trace assert.ok(trace, 'trace should exist') assert.ok(trace.root, 'root element should exist') - assert.equal(trace.root.children.length, 2, 'there should be only two children of the root') + const children = trace.getChildren(trace.root.id) + assert.equal(children.length, 2, 'there should be only two children of the root') - const setSegment = trace.root.children[0] + const [setSegment, getSegment] = children const setAttributes = setSegment.getAttributes() assert.ok(setSegment, 'trace segment for set should exist') assert.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set') assert.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute') - assert.equal(setSegment.children.length, 0, 'set should have no children') + const setSegmentChildren = trace.getChildren(setSegment.id) + assert.equal(setSegmentChildren.length, 0, 'set should have no children') - const getSegment = trace.root.children[1] const getAttributes = getSegment.getAttributes() assert.ok(getSegment, 'trace segment for get should exist') @@ -123,10 +124,10 @@ test('Redis instrumentation', async function (t) { assert.ok(!agent.getTransaction(), 'no transaction should be in play') agent.config.attributes.enabled = true - helper.runInTransaction(agent, async function () { + helper.runInTransaction(agent, async function (tx) { await client.v4.set('saveme', 'foobar') - const segment = agent.tracer.getSegment().children[0] + const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id) assert.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute') end() }) @@ -137,10 +138,10 @@ test('Redis instrumentation', async function (t) { assert.ok(!agent.getTransaction(), 'no transaction should be in play') agent.config.attributes.enabled = false - helper.runInTransaction(agent, async function () { + helper.runInTransaction(agent, async function (tx) { await client.v4.set('saveme', 'foobar') - const segment = agent.tracer.getSegment().children[0] + const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id) assert.ok(!segment.getAttributes().key, 'should not have `key` attribute') end() }) @@ -158,7 +159,7 @@ test('Redis instrumentation', async function (t) { await client.v4.set('testkey', 'arglbargle') const trace = transaction.trace - const setSegment = trace.root.children[0] + const [setSegment] = trace.getChildren(trace.root.id) const attributes = setSegment.getAttributes() assert.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute') assert.equal( @@ -187,7 +188,7 @@ test('Redis instrumentation', async function (t) { const transaction = agent.getTransaction() await client.v4.set('testkey', 'arglbargle') - const setSegment = transaction.trace.root.children[0] + const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = setSegment.getAttributes() assert.equal(attributes.host, undefined, 'should not have host attribute') assert.equal(attributes.port_path_or_id, undefined, 'should not have port attribute') @@ -225,9 +226,9 @@ test('Redis instrumentation', async function (t) { }) function verify() { - const setSegment1 = transaction.trace.root.children[0] - const selectSegment = transaction.trace.root.children[1] - const setSegment2 = transaction.trace.root.children[2] + const [setSegment1, selectSegment, setSegment2] = transaction.trace.getChildren( + transaction.trace.root.id + ) assert.equal( setSegment1.name, diff --git a/test/versioned/redis/redis-v4.test.js b/test/versioned/redis/redis-v4.test.js index 8b04baaa3e..8e70f23bd9 100644 --- a/test/versioned/redis/redis-v4.test.js +++ b/test/versioned/redis/redis-v4.test.js @@ -73,16 +73,16 @@ test('Redis instrumentation', async function (t) { const trace = transaction.trace assert.ok(trace, 'trace should exist') assert.ok(trace.root, 'root element should exist') - assert.equal(trace.root.children.length, 2, 'there should be only two children of the root') + const children = trace.getChildren(trace.root.id) + assert.equal(children.length, 2, 'there should be only two children of the root') - const setSegment = trace.root.children[0] + const [setSegment, getSegment] = children const setAttributes = setSegment.getAttributes() assert.ok(setSegment, 'trace segment for set should exist') assert.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set') assert.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute') - assert.equal(setSegment.children.length, 0, 'set should have no children') - - const getSegment = trace.root.children[1] + const setSegmentChildren = trace.getChildren(setSegment.id) + assert.equal(setSegmentChildren.length, 0, 'set should have no children') const getAttributes = getSegment.getAttributes() assert.ok(getSegment, 'trace segment for get should exist') @@ -147,10 +147,10 @@ test('Redis instrumentation', async function (t) { assert.ok(!agent.getTransaction(), 'no transaction should be in play') agent.config.attributes.enabled = true - helper.runInTransaction(agent, async function () { + helper.runInTransaction(agent, async function (tx) { await client.set('saveme', 'foobar') - const segment = agent.tracer.getSegment().children[0] + const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id) assert.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute') end() }) @@ -161,10 +161,10 @@ test('Redis instrumentation', async function (t) { assert.ok(!agent.getTransaction(), 'no transaction should be in play') agent.config.attributes.enabled = false - helper.runInTransaction(agent, async function () { + helper.runInTransaction(agent, async function (tx) { await client.set('saveme', 'foobar') - const segment = agent.tracer.getSegment().children[0] + const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id) assert.ok(!segment.getAttributes().key, 'should not have `key` attribute') end() }) @@ -182,7 +182,7 @@ test('Redis instrumentation', async function (t) { await client.set('testkey', 'arglbargle') const trace = transaction.trace - const setSegment = trace.root.children[0] + const [setSegment] = trace.getChildren(trace.root.id) const attributes = setSegment.getAttributes() assert.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute') assert.equal( @@ -211,7 +211,7 @@ test('Redis instrumentation', async function (t) { const transaction = agent.getTransaction() await client.set('testkey', 'arglbargle') - const setSegment = transaction.trace.root.children[0] + const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = setSegment.getAttributes() assert.equal(attributes.host, undefined, 'should not have host attribute') assert.equal(attributes.port_path_or_id, undefined, 'should not have port attribute') @@ -249,9 +249,9 @@ test('Redis instrumentation', async function (t) { }) function verify() { - const setSegment1 = transaction.trace.root.children[0] - const selectSegment = transaction.trace.root.children[1] - const setSegment2 = transaction.trace.root.children[2] + const [setSegment1, selectSegment, setSegment2] = transaction.trace.getChildren( + transaction.trace.root.id + ) assert.equal( setSegment1.name, diff --git a/test/versioned/redis/redis.test.js b/test/versioned/redis/redis.test.js index 002a596623..e348c6fdd2 100644 --- a/test/versioned/redis/redis.test.js +++ b/test/versioned/redis/redis.test.js @@ -75,16 +75,17 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { const trace = transaction.trace plan.ok(trace, 'trace should exist') plan.ok(trace.root, 'root element should exist') - plan.equal(trace.root.children.length, 1, 'there should be only one child of the root') - - const setSegment = trace.root.children[0] + const children = trace.getChildren(trace.root.id) + plan.equal(children.length, 1, 'there should be only one child of the root') + const [setSegment] = children const setAttributes = setSegment.getAttributes() plan.ok(setSegment, 'trace segment for set should exist') plan.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set') plan.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute') - plan.equal(setSegment.children.length, 1, 'set should have an only child') - - const getSegment = setSegment.children[0].children[0] + const setChildren = trace.getChildren(setSegment.id) + plan.equal(setChildren.length, 1, 'set should have an only child') + const [getSegment] = trace.getChildren(setChildren[0].id) + const getChildren = trace.getChildren(getSegment.id) const getAttributes = getSegment.getAttributes() plan.ok(getSegment, 'trace segment for get should exist') @@ -92,7 +93,7 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { plan.equal(getAttributes.key, '"testkey"', 'should have the get key as a attribute') - plan.ok(getSegment.children.length >= 1, 'get should have a callback segment') + plan.ok(getChildren.length >= 1, 'get should have a callback segment') plan.ok(getSegment.timer.hrDuration, 'trace segment should have ended') }) @@ -142,13 +143,14 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { }) agent.on('transactionFinished', function (tx) { - const redSeg = tx.trace.root.children[0] + const [redSeg] = tx.trace.getChildren(tx.trace.root.id) plan.equal( redSeg.name, 'Datastore/operation/Redis/set', 'should have untruncated redis segment' ) - plan.equal(redSeg.children.length, 0, 'should have no children for redis segment') + const redChildren = tx.trace.getChildren(redSeg.id) + plan.equal(redChildren.length, 0, 'should have no children for redis segment') }) await plan.completed }) @@ -214,11 +216,11 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { const { agent, client } = t.nr agent.config.attributes.enabled = true - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { client.set('saveme', 'foobar', function (error) { // Regardless of error, key should still be captured. assert.ok(!error) - const segment = agent.tracer.getSegment().parent + const segment = tx.trace.getParent(agent.tracer.getSegment().parentId) assert.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute') end() }) @@ -229,11 +231,11 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { const { agent, client } = t.nr agent.config.attributes.enabled = false - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { client.set('saveme', 'foobar', function (error) { // Regardless of error, key should still be captured. assert.ok(!error) - const segment = agent.tracer.getSegment().parent + const segment = tx.trace.getParent(agent.tracer.getSegment().parentId) assert.ok(!segment.getAttributes().key, 'should not have `key` attribute') end() }) @@ -252,7 +254,7 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { client.set('testkey', 'arglbargle', function (error) { plan.ok(!error) const trace = transaction.trace - const setSegment = trace.root.children[0] + const [setSegment] = trace.getChildren(trace.root.id) const attributes = setSegment.getAttributes() plan.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute') plan.equal( @@ -282,7 +284,7 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { const transaction = agent.getTransaction() client.set('testkey', 'arglbargle', function (error) { plan.ok(!error) - const setSegment = transaction.trace.root.children[0] + const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id) const attributes = setSegment.getAttributes() plan.equal(attributes.host, undefined, 'should not have host attribute') plan.equal(attributes.port_path_or_id, undefined, 'should not have port attribute') @@ -327,9 +329,13 @@ test('Redis instrumentation', { timeout: 20000 }, async function (t) { await plan.completed function verify() { - const setSegment1 = transaction.trace.root.children[0] - const selectSegment = setSegment1.children[0].children[0] - const setSegment2 = selectSegment.children[0].children[0] + const [setSegment1] = transaction.trace.getChildren(transaction.trace.root.id) + const [selectSegment] = transaction.trace.getChildren( + transaction.trace.getChildren(setSegment1.id)[0].id + ) + const [setSegment2] = transaction.trace.getChildren( + transaction.trace.getChildren(selectSegment.id)[0].id + ) plan.equal(setSegment1.name, 'Datastore/operation/Redis/set', 'should register the first set') plan.equal( diff --git a/test/versioned/restify/router.test.js b/test/versioned/restify/router.test.js index f4daa1703a..4b4c40df6b 100644 --- a/test/versioned/restify/router.test.js +++ b/test/versioned/restify/router.test.js @@ -55,7 +55,7 @@ test('Restify router', async function (t) { plan.equal(transaction.verb, 'GET', 'HTTP method is GET') plan.ok(transaction.trace, 'transaction has trace') - const web = transaction.trace.root.children[0] + const [web] = transaction.trace.getChildren(transaction.trace.root.id) plan.ok(web, 'trace has web segment') plan.equal(web.name, transaction.name, 'segment name and transaction name match') plan.equal(web.partialName, 'Restify/GET//test/:id', 'should have partial name for apdex') diff --git a/test/versioned/superagent/async-await.test.js b/test/versioned/superagent/async-await.test.js index 55f54e7a72..edd706361c 100644 --- a/test/versioned/superagent/async-await.test.js +++ b/test/versioned/superagent/async-await.test.js @@ -41,11 +41,12 @@ test('should maintain transaction context with promises', (t, end) => { const { request } = t.nr await request.get(address) - const mainSegment = tx.trace.root.children[0] + const [mainSegment] = tx.trace.getChildren(tx.trace.root.id) assert.ok(mainSegment) match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request') + const mainChildren = tx.trace.getChildren(mainSegment.id) assert.equal( - mainSegment.children.filter((c) => c.name === 'Callback: ').length, + mainChildren.filter((c) => c.name === 'Callback: ').length, 1, 'CB created by superagent is present' ) diff --git a/test/versioned/superagent/superagent.test.js b/test/versioned/superagent/superagent.test.js index 481135cd9a..970bf56544 100644 --- a/test/versioned/superagent/superagent.test.js +++ b/test/versioned/superagent/superagent.test.js @@ -40,11 +40,12 @@ test('should maintain transaction context with callbacks', (t, end) => { request.get(address, function testCallback() { assert.ok(tx) - const mainSegment = tx.trace.root.children[0] + const [mainSegment] = tx.trace.getChildren(tx.trace.root.id) assert.ok(mainSegment) match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request') + const mainChildren = tx.trace.getChildren(mainSegment.id) assert.equal( - mainSegment.children.filter((c) => c.name === 'Callback: testCallback').length, + mainChildren.filter((c) => c.name === 'Callback: testCallback').length, 1, 'has segment matching callback' ) @@ -68,11 +69,12 @@ test('should maintain transaction context with promises', (t, end) => { request.get(address).then(function testThen() { assert.ok(tx) - const mainSegment = tx.trace.root.children[0] + const [mainSegment] = tx.trace.getChildren(tx.trace.root.id) assert.ok(mainSegment) match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request') + const mainChildren = tx.trace.getChildren(mainSegment.id) assert.equal( - mainSegment.children.filter((c) => c.name === 'Callback: ').length, + mainChildren.filter((c) => c.name === 'Callback: ').length, 1, 'has segment matching callback' ) diff --git a/test/versioned/undici/requests.test.js b/test/versioned/undici/requests.test.js index b5ed2e8f54..23922aa133 100644 --- a/test/versioned/undici/requests.test.js +++ b/test/versioned/undici/requests.test.js @@ -82,7 +82,7 @@ test('Undici request tests', async (t) => { }) assert.equal(statusCode, 200) - assertSegments(tx.trace.root, [`External/${HOST}/post`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`], { exact: false }) tx.end() }) }) @@ -115,7 +115,7 @@ test('Undici request tests', async (t) => { await client.request({ path: '/', method: 'GET' }) - assertSegments(transaction.trace.root, [`External/localhost:${port}/`], { + assertSegments(transaction.trace, transaction.trace.root, [`External/localhost:${port}/`], { exact: false }) @@ -130,7 +130,7 @@ test('Undici request tests', async (t) => { method: 'GET' }) assert.equal(statusCode, 200) - const segment = metrics.findSegment(tx.trace.root, `External/${HOST}/get`) + const segment = metrics.findSegment(tx.trace, tx.trace.root, `External/${HOST}/get`) const attrs = segment.getAttributes() assert.equal(attrs.url, `${REQUEST_URL}/get`) assert.equal(attrs.procedure, 'GET') @@ -194,7 +194,7 @@ test('Undici request tests', async (t) => { const [{ statusCode }, { statusCode: statusCode2 }] = await Promise.all([req1, req2]) assert.equal(statusCode, 200) assert.equal(statusCode2, 200) - assertSegments(tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], { + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], { exact: false }) tx.end() @@ -210,7 +210,7 @@ test('Undici request tests', async (t) => { }) } catch (err) { assert.ok(err) - assertSegments(tx.trace.root, ['External/invalidurl/foo'], { exact: false }) + assertSegments(tx.trace, tx.trace.root, ['External/invalidurl/foo'], { exact: false }) assert.equal(tx.exceptions.length, 1) tx.end() } @@ -230,7 +230,7 @@ test('Undici request tests', async (t) => { }, 100) await req } catch { - assertSegments(tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false }) assert.equal(tx.exceptions.length, 1) const expectedErrMsg = semver.gte(pkgVersion, '6.3.0') ? 'This operation was aborted' @@ -259,11 +259,11 @@ test('Undici request tests', async (t) => { try { await req } catch { - assertSegments(transaction.trace.root, [`External/localhost:${port}/`], { + assertSegments(transaction.trace, transaction.trace.root, [`External/localhost:${port}/`], { exact: false }) - const segments = transaction.trace.root.children + const segments = transaction.trace.getChildren(transaction.trace.root.id) const segment = segments[segments.length - 1] assert.ok(segment.timer.start, 'should have started') @@ -281,7 +281,7 @@ test('Undici request tests', async (t) => { method: 'GET' }) assert.equal(statusCode, 400) - assertSegments(tx.trace.root, [`External/${HOST}/status/400`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/status/400`], { exact: false }) tx.end() }) }) @@ -290,7 +290,7 @@ test('Undici request tests', async (t) => { await helper.runInTransaction(agent, async (tx) => { const res = await undici.fetch(REQUEST_URL) assert.equal(res.status, 200) - assertSegments(tx.trace.root, [`External/${HOST}/`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/`], { exact: false }) tx.end() }) }) @@ -312,7 +312,7 @@ test('Undici request tests', async (t) => { }) } ) - assertSegments(tx.trace.root, [`External/${HOST}/get`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/get`], { exact: false }) tx.end() }) }) @@ -347,7 +347,7 @@ test('Undici request tests', async (t) => { }), (err) => { assert.ok(!err) - assertSegments(tx.trace.root, [`External/${HOST}/get`], { exact: false }) + assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/get`], { exact: false }) tx.end() end() } diff --git a/test/versioned/when/segments.test.js b/test/versioned/when/segments.test.js index 0739c8ac93..e176af1c56 100644 --- a/test/versioned/when/segments.test.js +++ b/test/versioned/when/segments.test.js @@ -61,9 +61,11 @@ test('segments enabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', (tx) => { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) assertSegments( + tx.trace, tx.trace.root, [ 'doSomeWork', @@ -98,8 +100,11 @@ test('segments enabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) + assertSegments( + tx.trace, tx.trace.root, [ 'doWork1', @@ -141,9 +146,11 @@ test('segments enabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) assertSegments( + tx.trace, tx.trace.root, [ 'doWork1', @@ -172,9 +179,11 @@ test('segments enabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) assertSegments( + tx.trace, tx.trace.root, ['doWork1', ['Promise startSomeWork', ['Promise#catch catchHandler']]], {}, @@ -200,9 +209,11 @@ test('segments enabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) assertSegments( + tx.trace, tx.trace.root, [ 'doWork1', @@ -253,9 +264,11 @@ test('segments enabled', async (t) => { const { Promise } = when agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 2) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 2) assertSegments( + tx.trace, tx.trace.root, ['Promise startSomeWork', ['Promise#then myThen'], 'doSomeWork'], { exact: true }, @@ -308,9 +321,10 @@ test('segments disabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doSomeWork', ['someChildSegment']], {}, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doSomeWork', ['someChildSegment']], {}, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) { @@ -337,9 +351,10 @@ test('segments disabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doWork1'], {}, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doWork1'], {}, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) { @@ -362,9 +377,10 @@ test('segments disabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doWork1'], {}, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doWork1'], {}, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) { @@ -385,9 +401,10 @@ test('segments disabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doWork1'], {}, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doWork1'], {}, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) { @@ -408,9 +425,10 @@ test('segments disabled', async (t) => { const { agent, tracer, when } = t.nr agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doWork1', ['doWork2']], {}, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doWork1', ['doWork2']], {}, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) { @@ -438,9 +456,10 @@ test('segments disabled', async (t) => { const { Promise } = when agent.once('transactionFinished', function (tx) { - plan.equal(tx.trace.root.children.length, 1) + const children = tx.trace.getChildren(tx.trace.root.id) + plan.equal(children.length, 1) - assertSegments(tx.trace.root, ['doSomeWork'], { exact: true }, { assert: plan }) + assertSegments(tx.trace, tx.trace.root, ['doSomeWork'], { exact: true }, { assert: plan }) }) helper.runInTransaction(agent, function transactionWrapper(transaction) {