Skip to content

Commit f980b33

Browse files
tlhunterNicolas HansseBridgeAR
authored
instrument the iovalkey package (#5555)
* feat: monitor io-valkey * rename redis.command to valkey.command * Apply suggestions from code review Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de> * Update packages/datadog-instrumentations/src/iovalkey.js Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de> * Update packages/datadog-instrumentations/src/helpers/hooks.js * Apply suggestions from code review BridgeAR Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de> * add span type to class / subclass * seems that our agent.use stuff is broken * break apart redis, ioredis, valkey jobs * refactor tests * try using redis server 6.2 * promise to async --------- Co-authored-by: Nicolas Hansse <nicolas.hansse@royalcanin.com> Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 0c27678 commit f980b33

File tree

17 files changed

+448
-26
lines changed

17 files changed

+448
-26
lines changed

.github/workflows/plugins.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,16 +1023,44 @@ jobs:
10231023
runs-on: ubuntu-latest
10241024
services:
10251025
redis:
1026-
image: redis:4.0-alpine
1026+
image: redis:6.2-alpine
10271027
ports:
10281028
- 6379:6379
10291029
env:
1030-
PLUGINS: redis|ioredis # TODO: move ioredis to its own job
1030+
PLUGINS: redis
10311031
SERVICES: redis
10321032
steps:
10331033
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
10341034
- uses: ./.github/actions/plugins/test
10351035

1036+
ioredis:
1037+
runs-on: ubuntu-latest
1038+
services:
1039+
redis:
1040+
image: redis:6.2-alpine
1041+
ports:
1042+
- 6379:6379
1043+
env:
1044+
PLUGINS: ioredis
1045+
SERVICES: ioredis
1046+
steps:
1047+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1048+
- uses: ./.github/actions/plugins/test
1049+
1050+
valkey:
1051+
runs-on: ubuntu-latest
1052+
services:
1053+
valkey:
1054+
image: valkey/valkey:8.1-alpine
1055+
ports:
1056+
- 6379:6379
1057+
env:
1058+
PLUGINS: iovalkey
1059+
SERVICES: valkey
1060+
steps:
1061+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1062+
- uses: ./.github/actions/plugins/test
1063+
10361064
restify:
10371065
runs-on: ubuntu-latest
10381066
env:

docs/API.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ tracer.use('pg', {
5757
<h5 id="ioredis"></h5>
5858
<h5 id="ioredis-tags"></h5>
5959
<h5 id="ioredis-config"></h5>
60+
<h5 id="iovalkey"></h5>
61+
<h5 id="iovalkey-tags"></h5>
62+
<h5 id="iovalkey-config"></h5>
6063
<h5 id="jest"></h5>
6164
<h5 id="kafkajs"></h5>
6265
<h5 id="koa"></h5>
@@ -128,6 +131,7 @@ tracer.use('pg', {
128131
* [http](./interfaces/export_.plugins.http.html)
129132
* [http2](./interfaces/export_.plugins.http2.html)
130133
* [ioredis](./interfaces/export_.plugins.ioredis.html)
134+
* [iovalkey](./interfaces/export_.plugins.iovalkey.html)
131135
* [jest](./interfaces/export_.plugins.jest.html)
132136
* [kafkajs](./interfaces/export_.plugins.kafkajs.html)
133137
* [knex](./interfaces/export_.plugins.knex.html)

docs/add-redirects.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ declare -a plugins=(
3636
"http"
3737
"http2"
3838
"ioredis"
39+
"iovalkey"
3940
"jest"
4041
"kafkajs"
4142
"knex"

docs/test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ tracer.use('http2', {
348348
tracer.use('ioredis');
349349
tracer.use('ioredis', redisOptions);
350350
tracer.use('ioredis', { splitByInstance: true });
351+
tracer.use('iovalkey');
352+
tracer.use('iovalkey', redisOptions);
353+
tracer.use('iovalkey', { splitByInstance: true });
351354
tracer.use('jest');
352355
tracer.use('jest', { service: 'jest-service' });
353356
tracer.use('kafkajs');

index.d.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ interface Plugins {
189189
"http": tracer.plugins.http;
190190
"http2": tracer.plugins.http2;
191191
"ioredis": tracer.plugins.ioredis;
192+
"iovalkey": tracer.plugins.iovalkey;
192193
"jest": tracer.plugins.jest;
193194
"kafkajs": tracer.plugins.kafkajs
194195
"knex": tracer.plugins.knex;
@@ -1666,6 +1667,53 @@ declare namespace tracer {
16661667
splitByInstance?: boolean;
16671668
}
16681669

1670+
/**
1671+
* This plugin automatically instruments the
1672+
* [iovalkey](https://github.com/valkey-io/iovalkey) module.
1673+
*/
1674+
interface iovalkey extends Instrumentation {
1675+
/**
1676+
* List of commands that should be instrumented. Commands must be in
1677+
* lowercase for example 'xread'.
1678+
*
1679+
* @default /^.*$/
1680+
*/
1681+
allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1682+
1683+
/**
1684+
* Deprecated in favor of `allowlist`.
1685+
*
1686+
* @deprecated
1687+
* @hidden
1688+
*/
1689+
whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1690+
1691+
/**
1692+
* List of commands that should not be instrumented. Takes precedence over
1693+
* allowlist if a command matches an entry in both. Commands must be in
1694+
* lowercase for example 'xread'.
1695+
*
1696+
* @default []
1697+
*/
1698+
blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1699+
1700+
/**
1701+
* Deprecated in favor of `blocklist`.
1702+
*
1703+
* @deprecated
1704+
* @hidden
1705+
*/
1706+
blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1707+
1708+
/**
1709+
* Whether to use a different service name for each Redis instance based
1710+
* on the configured connection name of the client.
1711+
*
1712+
* @default false
1713+
*/
1714+
splitByInstance?: boolean;
1715+
}
1716+
16691717
/**
16701718
* This plugin automatically instruments the
16711719
* [jest](https://github.com/jestjs/jest) module.

packages/datadog-instrumentations/src/helpers/hooks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ module.exports = {
6161
http2: () => require('../http2'),
6262
https: () => require('../http'),
6363
ioredis: () => require('../ioredis'),
64+
iovalkey: () => require('../iovalkey'),
6465
'jest-circus': () => require('../jest'),
6566
'jest-config': () => require('../jest'),
6667
'jest-environment-node': () => require('../jest'),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict'
2+
3+
const {
4+
channel,
5+
addHook,
6+
AsyncResource
7+
} = require('./helpers/instrument')
8+
const shimmer = require('../../datadog-shimmer')
9+
10+
const startCh = channel('apm:iovalkey:command:start')
11+
const finishCh = channel('apm:iovalkey:command:finish')
12+
const errorCh = channel('apm:iovalkey:command:error')
13+
14+
addHook({ name: 'iovalkey', versions: ['>=0.0.1'] }, Valkey => {
15+
shimmer.wrap(Valkey.prototype, 'sendCommand', sendCommand => function (command, stream) {
16+
if (!startCh.hasSubscribers) return sendCommand.apply(this, arguments)
17+
18+
if (!command?.promise) return sendCommand.apply(this, arguments)
19+
20+
const options = this.options || {}
21+
const connectionName = options.connectionName
22+
const db = options.db
23+
const connectionOptions = { host: options.host, port: options.port }
24+
25+
const asyncResource = new AsyncResource('bound-anonymous-fn')
26+
return asyncResource.runInAsyncScope(() => {
27+
startCh.publish({ db, command: command.name, args: command.args, connectionOptions, connectionName })
28+
29+
const onResolve = asyncResource.bind(() => finishCh.publish())
30+
const onReject = asyncResource.bind(err => finish(finishCh, errorCh, err))
31+
32+
command.promise.then(onResolve, onReject)
33+
34+
try {
35+
return sendCommand.apply(this, arguments)
36+
} catch (err) {
37+
errorCh.publish(err)
38+
39+
throw err
40+
}
41+
})
42+
})
43+
return Valkey
44+
})
45+
46+
function finish (finishCh, errorCh, error) {
47+
if (error) {
48+
errorCh.publish(error)
49+
}
50+
finishCh.publish()
51+
}

packages/datadog-plugin-ioredis/test/index.spec.js

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,25 @@ describe('Plugin', () => {
2525
})
2626

2727
describe('without configuration', () => {
28-
before(() => agent.load(['ioredis']))
28+
beforeEach(() => agent.load(['ioredis']))
2929

30-
after(() => agent.close({ ritmReset: false }))
30+
afterEach(() => agent.close({ ritmReset: false }))
3131

3232
it('should do automatic instrumentation when using callbacks', done => {
3333
agent.use(() => {}) // wait for initial info command
34-
agent
35-
.use(traces => {
36-
expect(traces[0][0]).to.have.property('name', expectedSchema.outbound.opName)
37-
expect(traces[0][0]).to.have.property('service', expectedSchema.outbound.serviceName)
38-
expect(traces[0][0]).to.have.property('resource', 'get')
39-
expect(traces[0][0]).to.have.property('type', 'redis')
40-
expect(traces[0][0].meta).to.have.property('component', 'ioredis')
41-
expect(traces[0][0].meta).to.have.property('db.name', '0')
42-
expect(traces[0][0].meta).to.have.property('db.type', 'redis')
43-
expect(traces[0][0].meta).to.have.property('span.kind', 'client')
44-
expect(traces[0][0].meta).to.have.property('out.host', 'localhost')
45-
expect(traces[0][0].meta).to.have.property('redis.raw_command', 'GET foo')
46-
expect(traces[0][0].metrics).to.have.property('network.destination.port', 6379)
47-
})
34+
agent.use(traces => {
35+
expect(traces[0][0]).to.have.property('name', expectedSchema.outbound.opName)
36+
expect(traces[0][0]).to.have.property('service', expectedSchema.outbound.serviceName)
37+
expect(traces[0][0]).to.have.property('resource', 'get')
38+
expect(traces[0][0]).to.have.property('type', 'redis')
39+
expect(traces[0][0].meta).to.have.property('component', 'ioredis')
40+
expect(traces[0][0].meta).to.have.property('db.name', '0')
41+
expect(traces[0][0].meta).to.have.property('db.type', 'redis')
42+
expect(traces[0][0].meta).to.have.property('span.kind', 'client')
43+
expect(traces[0][0].meta).to.have.property('out.host', 'localhost')
44+
expect(traces[0][0].meta).to.have.property('redis.raw_command', 'GET foo')
45+
expect(traces[0][0].metrics).to.have.property('network.destination.port', 6379)
46+
})
4847
.then(done)
4948
.catch(done)
5049

@@ -54,11 +53,9 @@ describe('Plugin', () => {
5453
it('should run the callback in the parent context', () => {
5554
const span = {}
5655

57-
return tracer.scope().activate(span, () => {
58-
return redis.get('foo')
59-
.then(() => {
60-
expect(tracer.scope().active()).to.equal(span)
61-
})
56+
return tracer.scope().activate(span, async () => {
57+
await redis.get('foo')
58+
expect(tracer.scope().active()).to.equal(span)
6259
})
6360
})
6461

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
const RedisPlugin = require('../../datadog-plugin-redis/src')
4+
5+
class IOValkeyPlugin extends RedisPlugin {
6+
static get id () {
7+
return 'iovalkey'
8+
}
9+
10+
static get system () { return 'valkey' }
11+
12+
constructor (...args) {
13+
super(...args)
14+
this._spanType = 'valkey'
15+
}
16+
}
17+
18+
module.exports = IOValkeyPlugin

0 commit comments

Comments
 (0)