Skip to content

Commit

Permalink
chore: moved recorders to lib/metrics/recorders (newrelic#2666)
Browse files Browse the repository at this point in the history
Co-authored-by: Bob Evans <[email protected]>
  • Loading branch information
svetlanabrennan and bizob2828 authored Oct 31, 2024
1 parent bd4f7ff commit d8dfe84
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 158 deletions.
54 changes: 0 additions & 54 deletions lib/db/parsed-statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

'use strict'

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

function ParsedStatement(type, operation, collection, raw) {
this.type = type
this.operation = operation
Expand All @@ -21,55 +18,4 @@ 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)
}

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

module.exports = ParsedStatement
62 changes: 62 additions & 0 deletions lib/metrics/recorders/database-operation.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('../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
}

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
68 changes: 68 additions & 0 deletions lib/metrics/recorders/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2024 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const { DB, ALL } = require('../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 recordQueryMetrics(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)
}

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

module.exports = recordQueryMetrics
29 changes: 29 additions & 0 deletions lib/metrics/recorders/middleware.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
72 changes: 4 additions & 68 deletions lib/shim/datastore-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const Shim = require('./shim')
const urltils = require('../util/urltils')
const util = require('util')
const specs = require('./specs')
const recordOperationMetrics = require('../../lib/metrics/recorders/database-operation')
const { DatastoreParameters } = specs.params
const recordQueryMetrics = require('../../lib/metrics/recorders/database')

/**
* An enumeration of well-known datastores so that new instrumentations can use
Expand Down Expand Up @@ -284,7 +286,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 @@ -567,7 +569,7 @@ function _recordQuery(suffix, nodule, properties, querySpec) {
const name = (parsed.collection || 'other') + '/' + parsed.operation + suffix
shim.logger.trace('Found and parsed query %s -> %s', parsed.type, name)
segDesc.name = shim._metrics.STATEMENT + name
segDesc.recorder = _recordQueryMetrics.bind(null, parsed)
segDesc.recorder = recordQueryMetrics.bind(parsed)
}

return segDesc
Expand Down Expand Up @@ -598,72 +600,6 @@ function _getSpec({ spec, shim, fn, fnName, args }) {
return dsSpec
}

/**
* Records all query metrics when a segment is active
*
* @private
* @param {ParsedStatement} parsed instance of ParsedStatement
* @param {TraceSegment} segment active segment
* @param {string} scope scope of metrics if it exists
*/
function _recordQueryMetrics(parsed, segment, scope) {
if (segment) {
parsed.recordMetrics(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')

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

0 comments on commit d8dfe84

Please sign in to comment.