Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: moved recorders to lib/metrics/recorders #2666

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 3 additions & 47 deletions lib/db/parsed-statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

'use strict'

const { DB, ALL } = require('../metrics/names')
const { DESTINATIONS } = require('../config/attribute-filter')
const _recordMetrics = require('../../lib/metrics/recorders/record-metrics')

function ParsedStatement(type, operation, collection, raw) {
this.type = type
Expand All @@ -22,53 +21,10 @@ function ParsedStatement(type, operation, collection, raw) {
}

ParsedStatement.prototype.recordMetrics = function recordMetrics(segment, scope) {
const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction
const type = transaction.isWeb() ? DB.WEB : DB.OTHER
const thisTypeSlash = this.type + '/'
const operation = DB.OPERATION + '/' + thisTypeSlash + this.operation

// Note, an operation metric should _always_ be created even if the action was
// a statement. This is part of the spec.

// Rollups
transaction.measure(operation, null, duration, exclusive)
transaction.measure(DB.PREFIX + type, null, duration, exclusive)
transaction.measure(DB.PREFIX + thisTypeSlash + type, null, duration, exclusive)
transaction.measure(DB.PREFIX + thisTypeSlash + ALL, null, duration, exclusive)
transaction.measure(DB.ALL, null, duration, exclusive)

// If we can parse the SQL statement, create a 'statement' metric, and use it
// as the scoped metric for transaction breakdowns. Otherwise, skip the
// 'statement' metric and use the 'operation' metric as the scoped metric for
// transaction breakdowns.
let collection
if (this.collection) {
collection = DB.STATEMENT + '/' + thisTypeSlash + this.collection + '/' + this.operation
transaction.measure(collection, null, duration, exclusive)
if (scope) {
transaction.measure(collection, scope, duration, exclusive)
}
} else if (scope) {
transaction.measure(operation, scope, duration, exclusive)
}

// This recorder is side-effectful Because we are depending on the recorder
// setting the transaction name, recorders must always be run before generating
// the final transaction trace
segment.name = collection || operation

// Datastore instance metrics.
const attributes = segment.attributes.get(DESTINATIONS.TRANS_SEGMENT)
if (attributes.host && attributes.port_path_or_id) {
const instanceName =
DB.INSTANCE + '/' + thisTypeSlash + attributes.host + '/' + attributes.port_path_or_id
transaction.measure(instanceName, null, duration, exclusive)
}
_recordMetrics.bind(this)(segment, scope)

if (this.raw) {
transaction.agent.queries.add(segment, this.type.toLowerCase(), this.raw, this.trace)
segment.transaction.agent.queries.add(segment, this.type.toLowerCase(), this.raw, this.trace)
}
}

Expand Down
29 changes: 29 additions & 0 deletions lib/metrics/recorders/middleware-metric-recorder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

/**
* Creates a recorder for middleware metrics.
*
* @private
* @param {object} _shim instance of shim
* @param {string} metricName name of metric
* @returns {Function} recorder for middleware
*/
function makeMiddlewareRecorder(_shim, metricName) {
return function middlewareMetricRecorder(segment, scope) {
const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction

if (scope) {
transaction.measure(metricName, scope, duration, exclusive)
}
transaction.measure(metricName, null, duration, exclusive)
}
}

module.exports = makeMiddlewareRecorder
64 changes: 64 additions & 0 deletions lib/metrics/recorders/record-metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const { DB, ALL } = require('../../metrics/names')
const { DESTINATIONS } = require('../../config/attribute-filter')

/**
* @this ParsedStatement
* @param {TraceSegment} segment - The segment being recorded.
* @param {string} [scope] - The scope of the segment.
*/

function _recordMetrics(segment, scope) {
const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction
const type = transaction.isWeb() ? DB.WEB : DB.OTHER
const thisTypeSlash = this.type + '/'
const operation = DB.OPERATION + '/' + thisTypeSlash + this.operation

// Note, an operation metric should _always_ be created even if the action was
// a statement. This is part of the spec.

// Rollups
transaction.measure(operation, null, duration, exclusive)
transaction.measure(DB.PREFIX + type, null, duration, exclusive)
transaction.measure(DB.PREFIX + thisTypeSlash + type, null, duration, exclusive)
transaction.measure(DB.PREFIX + thisTypeSlash + ALL, null, duration, exclusive)
transaction.measure(DB.ALL, null, duration, exclusive)

// If we can parse the SQL statement, create a 'statement' metric, and use it
// as the scoped metric for transaction breakdowns. Otherwise, skip the
// 'statement' metric and use the 'operation' metric as the scoped metric for
// transaction breakdowns.
let collection
if (this.collection) {
collection = DB.STATEMENT + '/' + thisTypeSlash + this.collection + '/' + this.operation
transaction.measure(collection, null, duration, exclusive)
if (scope) {
transaction.measure(collection, scope, duration, exclusive)
}
} else if (scope) {
transaction.measure(operation, scope, duration, exclusive)
}

// This recorder is side-effectful Because we are depending on the recorder
// setting the transaction name, recorders must always be run before generating
// the final transaction trace
segment.name = collection || operation

// Datastore instance metrics.
const attributes = segment.attributes.get(DESTINATIONS.TRANS_SEGMENT)
if (attributes.host && attributes.port_path_or_id) {
const instanceName =
DB.INSTANCE + '/' + thisTypeSlash + attributes.host + '/' + attributes.port_path_or_id
transaction.measure(instanceName, null, duration, exclusive)
}
}

module.exports = _recordMetrics
62 changes: 62 additions & 0 deletions lib/metrics/recorders/record-operation-metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const metrics = require('../../metrics/names')

/**
* Records all the metrics required for database operations.
*
* - `recordOperationMetrics(segment [, scope])`
*
* @private
* @this DatastoreShim
* @implements {MetricFunction}
* @param {TraceSegment} segment - The segment being recorded.
* @param {string} [scope] - The scope of the segment.
* @see DatastoreShim#recordOperation
* @see MetricFunction
*/
function recordOperationMetrics(segment, scope) {
if (!segment) {
return
}

Check warning on line 26 in lib/metrics/recorders/record-operation-metrics.js

View check run for this annotation

Codecov / codecov/patch

lib/metrics/recorders/record-operation-metrics.js#L25-L26

Added lines #L25 - L26 were not covered by tests

const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction
const type = transaction.isWeb() ? 'allWeb' : 'allOther'
const operation = segment.name

if (scope) {
transaction.measure(operation, scope, duration, exclusive)
}

transaction.measure(operation, null, duration, exclusive)
transaction.measure(metrics.DB.PREFIX + type, null, duration, exclusive)
transaction.measure(metrics.DB.ALL, null, duration, exclusive)
transaction.measure(this._metrics.ALL, null, duration, exclusive)
transaction.measure(
metrics.DB.PREFIX + this._metrics.PREFIX + '/' + type,
null,
duration,
exclusive
)

const attributes = segment.getAttributes()
if (attributes.host && attributes.port_path_or_id) {
const instanceName = [
metrics.DB.INSTANCE,
this._metrics.PREFIX,
attributes.host,
attributes.port_path_or_id
].join('/')

transaction.measure(instanceName, null, duration, exclusive)
}
}

module.exports = recordOperationMetrics
55 changes: 2 additions & 53 deletions lib/shim/datastore-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Shim = require('./shim')
const urltils = require('../util/urltils')
const util = require('util')
const specs = require('./specs')
const recordOperationMetrics = require('../../lib/metrics/recorders/record-operation-metrics')
const { DatastoreParameters } = specs.params

/**
Expand Down Expand Up @@ -284,7 +285,7 @@ function recordOperation(nodule, properties, opSpec) {
if (!segDesc?.name?.startsWith(shim._metrics.OPERATION)) {
segDesc.name = shim._metrics.OPERATION + segDesc.name
}
segDesc.recorder = _recordOperationMetrics.bind(shim)
segDesc.recorder = recordOperationMetrics.bind(shim)
}

return segDesc
Expand Down Expand Up @@ -612,58 +613,6 @@ function _recordQueryMetrics(parsed, segment, scope) {
}
}

/**
* Records all the metrics required for database operations.
*
* - `_recordOperationMetrics(segment [, scope])`
*
* @private
* @this DatastoreShim
* @implements {MetricFunction}
* @param {TraceSegment} segment - The segment being recorded.
* @param {string} [scope] - The scope of the segment.
* @see DatastoreShim#recordOperation
* @see MetricFunction
*/
function _recordOperationMetrics(segment, scope) {
if (!segment) {
return
}

const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction
const type = transaction.isWeb() ? 'allWeb' : 'allOther'
const operation = segment.name

if (scope) {
transaction.measure(operation, scope, duration, exclusive)
}

transaction.measure(operation, null, duration, exclusive)
transaction.measure(metrics.DB.PREFIX + type, null, duration, exclusive)
transaction.measure(metrics.DB.ALL, null, duration, exclusive)
transaction.measure(this._metrics.ALL, null, duration, exclusive)
transaction.measure(
metrics.DB.PREFIX + this._metrics.PREFIX + '/' + type,
null,
duration,
exclusive
)

const attributes = segment.getAttributes()
if (attributes.host && attributes.port_path_or_id) {
const instanceName = [
metrics.DB.INSTANCE,
this._metrics.PREFIX,
attributes.host,
attributes.port_path_or_id
].join('/')

transaction.measure(instanceName, null, duration, exclusive)
}
}

/**
* Extracts the query string from the arguments according to the given spec.
*
Expand Down
24 changes: 2 additions & 22 deletions lib/shim/webframework-shim/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
} = require('./common')
const { assignCLMSymbol } = require('../../util/code-level-metrics')
const { RecorderSpec } = require('../specs')
const makeMiddlewareRecorder = require('../../metrics/recorders/middleware-metric-recorder')

const MIDDLEWARE_TYPE_DETAILS = {
APPLICATION: { name: 'Mounted App: ', path: true, record: false },
Expand Down Expand Up @@ -88,7 +89,7 @@ function constructRecorder({ txInfo, typeDetails, shim, metricName }) {
let recorder = null
if (typeDetails.record) {
const stackPath = txInfo.transaction.nameState.getPath() || ''
recorder = _makeMiddlewareRecorder(shim, metricName + '/' + stackPath)
recorder = makeMiddlewareRecorder(shim, metricName + '/' + stackPath)
}
return recorder
}
Expand Down Expand Up @@ -325,27 +326,6 @@ module.exports._recordMiddleware = function _recordMiddleware(shim, middleware,
)
}

/**
* Creates a recorder for middleware metrics.
*
* @private
* @param {object} _shim instance of shim
* @param {string} metricName name of metric
* @returns {Function} recorder for middleware
*/
function _makeMiddlewareRecorder(_shim, metricName) {
return function middlewareMetricRecorder(segment, scope) {
const duration = segment.getDurationInMillis()
const exclusive = segment.getExclusiveDurationInMillis()
const transaction = segment.transaction

if (scope) {
transaction.measure(metricName, scope, duration, exclusive)
}
transaction.measure(metricName, null, duration, exclusive)
}
}

/**
* Wrap the `next` middleware function and push on our name state if we find it. We only want to
* push the name state if there is a next so that we can safely remove it
Expand Down
Loading