Skip to content

Commit

Permalink
feat: added supportability metrics to indicate how agent was loaded a…
Browse files Browse the repository at this point in the history
…nd if --enable-source-maps was passed to Node.js runtime (#1657)

 * `Supportability/Features/CJS/Preload` - recorded if `-r newrelic` was used to load agent
 * `Supportability/Features/CJS/Require` - recorded if `require('newrelic')` was used to load agent
 * `Supportability/Features/EnableSourceMaps` - recorded if `node --enable-source-maps` was present to start application
  • Loading branch information
bizob2828 committed Jun 5, 2023
1 parent c85c006 commit 6f6f7e6
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 2 deletions.
64 changes: 62 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require('./lib/util/unwrapped-core')
const featureFlags = require('./lib/feature_flags').prerelease
const psemver = require('./lib/util/process-version')
let logger = require('./lib/logger') // Gets re-loaded after initialization.
const NAMES = require('./lib/metrics/names')

const pkgJSON = require('./package.json')
logger.info(
Expand Down Expand Up @@ -173,10 +174,30 @@ function createAgent(config) {
}

function addStartupSupportabilities(agent) {
// TODO: As new versions come out, make sure to update Angler metrics.
recordLoaderMetric(agent)
recordNodeVersionMetric(agent)
recordFeatureFlagMetrics(agent)
recordSourceMapMetric(agent)
}

/**
* Records the major version of the Node.js runtime
* TODO: As new versions come out, make sure to update Angler metrics.
*
* @param {Agent} agent active NR agent
*/
function recordNodeVersionMetric(agent) {
const nodeMajor = /^v?(\d+)/.exec(process.version)
agent.recordSupportability('Nodejs/Version/' + ((nodeMajor && nodeMajor[1]) || 'unknown'))
const version = (nodeMajor && nodeMajor[1]) || 'unknown'
agent.recordSupportability(`Nodejs/Version/${version}`)
}

/**
* Records all the feature flags configured and if they are enabled/disabled
*
* @param {Agent} agent active NR agent
*/
function recordFeatureFlagMetrics(agent) {
const configFlags = Object.keys(agent.config.feature_flag)
for (let i = 0; i < configFlags.length; ++i) {
const flag = configFlags[i]
Expand All @@ -189,3 +210,42 @@ function addStartupSupportabilities(agent) {
}
}
}

/**
* Used to determine how the agent is getting loaded:
* 1. -r newrelic
* 2. --loader newrelic/esm-loader.mjs
* 3. require('newrelic')
*
* Then a supportability metric is loaded to decide.
* Note: We already take care of scenario #2 in newrelic/esm-loader.mjs
*
* @param {Agent} agent active NR agent
*/
function recordLoaderMetric(agent) {
const isESM = agent.metrics.getMetric(NAMES.FEATURES.ESM.LOADER)
let isDashR = false

process.execArgv.forEach((arg, index) => {
if (arg === '-r' && process.execArgv[index + 1] === 'newrelic') {
agent.metrics.getOrCreateMetric(NAMES.FEATURES.CJS.PRELOAD).incrementCallCount()
isDashR = true
}
})

if (!isESM && !isDashR) {
agent.metrics.getOrCreateMetric(NAMES.FEATURES.CJS.REQUIRE).incrementCallCount()
}
}

/**
* Checks to see if `--enable-source-maps` is being used and logs a supportability metric.
*
* @param {Agent} agent active NR agent
*/
function recordSourceMapMetric(agent) {
const isSourceMapsEnabled = process.execArgv.includes('--enable-source-maps')
if (isSourceMapsEnabled) {
agent.metrics.getOrCreateMetric(NAMES.FEATURES.SOURCE_MAPS).incrementCallCount()
}
}
5 changes: 5 additions & 0 deletions lib/metrics/names.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ const FEATURES = {
UNSUPPORTED_LOADER: `${SUPPORTABILITY.FEATURES}/ESM/UnsupportedLoader`,
CUSTOM_INSTRUMENTATION: `${SUPPORTABILITY.FEATURES}/ESM/CustomInstrumentation`
},
CJS: {
PRELOAD: `${SUPPORTABILITY.FEATURES}/CJS/Preload`,
REQUIRE: `${SUPPORTABILITY.FEATURES}/CJS/Require`
},
SOURCE_MAPS: `${SUPPORTABILITY.FEATURES}/EnableSourceMaps`,
CERTIFICATES: SUPPORTABILITY.FEATURES + '/Certificates',
INSTRUMENTATION: {
ON_RESOLVED: SUPPORTABILITY.FEATURES + '/Instrumentation/OnResolved',
Expand Down
127 changes: 127 additions & 0 deletions test/unit/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const proxyquire = require('proxyquire')
const sinon = require('sinon')

tap.test('loader metrics', (t) => {
t.autoend()
let metricsMock
let MockAgent
let shimmerMock
let ApiMock
let sandbox

t.beforeEach(() => {
sandbox = sinon.createSandbox()
metricsMock = require('./mocks/metrics')(sandbox)
MockAgent = require('./mocks/agent')(sandbox, metricsMock)
shimmerMock = require('./mocks/shimmer')(sandbox)

ApiMock = function (agent) {
this.agent = agent
}
})

t.afterEach(() => {
process.execArgv = []
sandbox.restore()
delete require.cache.__NR_cache
})

t.test('should load preload metric when agent is loaded via -r', (t) => {
process.execArgv = ['-r', 'newrelic']
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 1)
t.equal(metricCall.args[0][0], 'Supportability/Features/CJS/Preload')
t.end()
})

t.test('should not load preload metric if -r is present but is not newrelic', (t) => {
process.execArgv = ['-r', 'some-cool-lib']
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 1)
t.equal(metricCall.args[0][0], 'Supportability/Features/CJS/Require')
t.end()
})

t.test(
'should detect preload metric if newrelic is one of the -r calls but not the first',
(t) => {
process.execArgv = ['-r', 'some-cool-lib', '--inspect', '-r', 'newrelic']
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 1)
t.equal(metricCall.args[0][0], 'Supportability/Features/CJS/Preload')
t.end()
}
)

t.test('should not load preload nor require metric is esm loader loads agent', (t) => {
metricsMock.getMetric.withArgs('Supportability/Features/ESM/Loader').returns(true)
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 0)
t.end()
})

t.test('should load require metric when agent is required', (t) => {
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 1)
t.equal(metricCall.args[0][0], 'Supportability/Features/CJS/Require')
t.end()
})

t.test('should load enable source map metric when --enable-source-maps is present', (t) => {
process.execArgv = ['--enable-source-maps']
const agent = proxyquire('../../index', {
'./lib/agent': MockAgent,
'./lib/shimmer': shimmerMock,
'./api': ApiMock
})

const metricCall = agent.agent.metrics.getOrCreateMetric

t.equal(metricCall.args.length, 2)
t.equal(metricCall.args[1][0], 'Supportability/Features/EnableSourceMaps')
t.end()
})
})
18 changes: 18 additions & 0 deletions test/unit/mocks/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const sinon = require('sinon')
module.exports = (sandbox = sinon, metricsMock) => {
function MockAgent(config) {
this.config = config
this.config.app_name = 'Unit Test App'
this.metrics = metricsMock
}
MockAgent.prototype.start = sandbox.stub()
MockAgent.prototype.recordSupportability = sandbox.stub()
MockAgent.prototype.once = sandbox.stub()
return MockAgent
}
13 changes: 13 additions & 0 deletions test/unit/mocks/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const sinon = require('sinon')
module.exports = (sandbox = sinon) => {
return {
getMetric: sandbox.stub(),
getOrCreateMetric: sandbox.stub().returns({ incrementCallCount: sandbox.stub() })
}
}
14 changes: 14 additions & 0 deletions test/unit/mocks/shimmer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'
const sinon = require('sinon')
module.exports = (sandbox = sinon) => {
return {
patchModule: sandbox.stub(),
bootstrapInstrumentation: sandbox.stub(),
registeredInstrumentations: {}
}
}

0 comments on commit 6f6f7e6

Please sign in to comment.