Skip to content

Commit

Permalink
feat: Removed children from segments. (#2689)
Browse files Browse the repository at this point in the history
  • Loading branch information
bizob2828 committed Dec 13, 2024
1 parent f9fcdf6 commit 2f9f6a8
Show file tree
Hide file tree
Showing 128 changed files with 1,472 additions and 1,555 deletions.
13 changes: 0 additions & 13 deletions lib/instrumentation/nextjs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
)
}
2 changes: 1 addition & 1 deletion lib/metrics/recorders/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/database-operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/recorders/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ const { DESTINATIONS } = require('../../config/attribute-filter')
*/

function recordQueryMetrics(segment, scope, transaction) {
debugger
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
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/generic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/http_external.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/message-transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/metrics/recorders/other.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions lib/shim/shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -1325,8 +1325,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.
*/
Expand Down
6 changes: 4 additions & 2 deletions lib/transaction/trace/exclusive-time-calculator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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) {
Expand Down
175 changes: 125 additions & 50 deletions lib/transaction/trace/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand All @@ -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 = {
Expand All @@ -106,54 +100,35 @@ 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
}

const infiniteTracingConfigured = Boolean(config.infinite_tracing.trace_observer.host)
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.
*
Expand Down Expand Up @@ -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)
}

/**
Expand All @@ -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()) {
Expand Down Expand Up @@ -366,6 +339,104 @@ Trace.prototype._getRequestUri = function _getRequestUri() {
return requestUri
}

/**
* Gets all children of a segment.
*
* @param {number} id of segment
* @returns {Array.<TraceSegment>} 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.<TraceSegment>} 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.
*
Expand All @@ -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
Loading

0 comments on commit 2f9f6a8

Please sign in to comment.