Skip to content

Commit 7a7db8d

Browse files
dszakallasgergelyke
authored andcommitted
feat(agent): add basic node_redis instrumentation
1 parent 8f7748e commit 7a7db8d

4 files changed

Lines changed: 258 additions & 12 deletions

File tree

lib/consts.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module.exports = {
55
PROTOCOLS: {
66
POSTGRES: 'pg',
77
HTTP: 'http',
8-
MONGODB: 'mongodb'
8+
MONGODB: 'mongodb',
9+
REDIS: 'redis'
910
},
1011
EDGE_STATUS: {
1112
OK: 0,

lib/instrumentations/redis.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,56 @@
11
var Shimmer = require('../utils/shimmer')
2+
var consts = require('../consts')
23

3-
module.exports = function (redis, agent) {
4+
module.exports = function wrap (redis, agent) {
45
Shimmer.wrap(redis.RedisClient.prototype, 'redis.RedisClient.prototype', 'send_command', function (original) {
5-
return function (fulfill, reject, progress, promise, receiver, domain) {
6+
return function () {
7+
var spanId = agent.generateSpanId()
8+
var clientSendTime = agent.getMicrotime()
9+
var requestId = agent.getTransactionId()
10+
11+
var host = this.address
12+
613
var args = Array.prototype.slice.apply(arguments)
7-
var last = args.length - 1
8-
var callback = args[last]
9-
var tail = callback
10-
11-
if (typeof callback === 'function') {
12-
args[last] = agent.bind(callback)
13-
} else if (Array.isArray(tail) && typeof tail[tail.length - 1] === 'function') {
14-
last = tail.length - 1
15-
tail[last] = agent.bind(tail[last])
14+
var command = args[0]
15+
var last = args[args.length - 1]
16+
17+
var wrappedCallback = function (original) {
18+
return function (err) {
19+
agent.clientReceive({
20+
protocol: consts.PROTOCOLS.REDIS,
21+
id: requestId,
22+
spanId: spanId,
23+
host: host,
24+
time: clientSendTime,
25+
url: 'unknown', // TODO(c/KRU7H3D1): add support for url parameter with redis key
26+
method: command,
27+
mustCollect: err ? consts.MUST_COLLECT.ERROR : undefined,
28+
responseTime: agent.getMicrotime() - clientSendTime,
29+
status: err ? consts.EDGE_STATUS.NOT_OK : consts.EDGE_STATUS.OK,
30+
statusCode: err ? err.code : 200
31+
})
32+
return original.apply(this, arguments)
33+
}
1634
}
1735

36+
if (last && typeof last === 'function') {
37+
args[args.length - 1] = wrappedCallback(last)
38+
} else if (Array.isArray(last) && typeof last[last.length - 1] === 'function') {
39+
last[last.length - 1] = wrappedCallback(last[last.length - 1])
40+
} else {
41+
args.push(wrappedCallback(function () { }))
42+
}
43+
44+
agent.clientSend({
45+
id: requestId,
46+
spanId: spanId,
47+
host: host,
48+
time: clientSendTime,
49+
method: command,
50+
type: agent.CLIENT_SEND,
51+
url: 'unknown'
52+
})
53+
1854
return original.apply(this, args)
1955
}
2056
})
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict'
2+
3+
var expect = require('chai').expect
4+
var wrap = require('./redis')
5+
var Shimmer = require('../utils/shimmer')
6+
var REDIS_URL
7+
8+
function fakeAgent (sandbox) {
9+
return {
10+
generateSpanId: function () { return 'fakeSpanId' },
11+
getMicrotime: function () { return 42 },
12+
getTransactionId: function () { return 'fakeTransactionId' },
13+
clientSend: sandbox.spy(),
14+
clientReceive: sandbox.spy(),
15+
CLIENT_SEND: 'fakeSend'
16+
}
17+
}
18+
19+
describe('redis module wrapper', function () {
20+
beforeEach(function () {
21+
Shimmer.unwrapAll()
22+
})
23+
24+
it('should instrument operation test #1', function (done) {
25+
var agent = fakeAgent(this.sandbox)
26+
var redis = wrap(require('redis'), agent)
27+
var client = redis.createClient(REDIS_URL)
28+
29+
client.sadd('x', 6, function () {
30+
expect(agent.clientReceive).to.have.been.called
31+
done()
32+
})
33+
34+
expect(agent.clientSend).to.have.been.called
35+
expect(agent.clientSend).to.have.been.calledWith({
36+
id: 'fakeTransactionId',
37+
spanId: 'fakeSpanId',
38+
host: '127.0.0.1:6379',
39+
time: 42,
40+
method: 'sadd',
41+
type: 'fakeSend',
42+
url: 'unknown'
43+
})
44+
})
45+
46+
it('should instrument operation test #2', function () {
47+
var agent = fakeAgent(this.sandbox)
48+
var redis = wrap(require('redis'), agent)
49+
var client = redis.createClient(REDIS_URL)
50+
51+
for (var i = 0; i < 9; ++i) {
52+
client.sadd('x', i)
53+
}
54+
55+
expect(agent.clientSend).to.have.callCount(i)
56+
})
57+
58+
it('should work with multi', function (done) {
59+
var agent = fakeAgent(this.sandbox)
60+
var redis = wrap(require('redis'), agent)
61+
62+
var client = redis.createClient(REDIS_URL)
63+
64+
client.multi()
65+
.sadd('x', 6)
66+
.srem('x', 7)
67+
.exec(function () {
68+
expect(agent.clientReceive).to.have.been.called
69+
done()
70+
})
71+
72+
expect(agent.clientSend).to.have.callCount(4)
73+
})
74+
})

lib/instrumentations/redis.spec.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict'
2+
3+
var expect = require('chai').expect
4+
var wrapper = require('./redis')
5+
var Shimmer = require('../utils/shimmer')
6+
var consts = require('../consts')
7+
8+
describe('The redis wrapper module', function () {
9+
it('should wrap redis.RedisClient.prototype.send_command', function () {
10+
var shimmerWrapStub = this.sandbox.stub(Shimmer, 'wrap')
11+
12+
var fakeRedis = {
13+
RedisClient: function () { }
14+
}
15+
16+
// wrapped as a side effect
17+
wrapper(fakeRedis, null)
18+
19+
expect(shimmerWrapStub).to.have.been.calledWith(
20+
fakeRedis.RedisClient.prototype,
21+
'redis.RedisClient.prototype',
22+
'send_command'
23+
)
24+
})
25+
26+
it('should call clientSend on the Agent when command is sent', function () {
27+
var shimmerWrapStub = this.sandbox.stub(Shimmer, 'wrap')
28+
29+
var fakeRedis = {
30+
RedisClient: function () {
31+
this.address = 'fakeRedisAddress'
32+
}
33+
}
34+
35+
var fakeAgent = {
36+
generateSpanId: function () { return 'fakeSpanId' },
37+
getMicrotime: function () { return 42 },
38+
getTransactionId: function () { return 'fakeTransactionId' },
39+
clientSend: this.sandbox.spy(),
40+
clientReceive: this.sandbox.spy(),
41+
CLIENT_SEND: 'fakeSend'
42+
}
43+
44+
// wrapped as a side effect
45+
wrapper(fakeRedis, fakeAgent)
46+
var wrapOp = shimmerWrapStub.args[0][3]
47+
48+
var fakeRedisClientSend = this.sandbox.spy(function (command, args, callback) {
49+
callback() // simulate the response
50+
})
51+
52+
wrapOp(fakeRedisClientSend).apply(new fakeRedis.RedisClient(), ['hset', ['abc', 'def']])
53+
54+
expect(fakeAgent.clientSend).to.have.been.calledOnce
55+
expect(fakeAgent.clientSend).to.have.been.calledWith({
56+
id: 'fakeTransactionId',
57+
spanId: 'fakeSpanId',
58+
host: 'fakeRedisAddress',
59+
time: 42,
60+
type: fakeAgent.CLIENT_SEND,
61+
url: 'unknown',
62+
method: 'hset'
63+
})
64+
65+
expect(fakeAgent.clientReceive).to.have.been.calledOnce
66+
expect(fakeAgent.clientReceive).to.have.been.calledWith({
67+
mustCollect: undefined,
68+
responseTime: 0,
69+
status: consts.EDGE_STATUS.OK,
70+
statusCode: 200,
71+
id: 'fakeTransactionId',
72+
spanId: 'fakeSpanId',
73+
host: 'fakeRedisAddress',
74+
time: 42,
75+
url: 'unknown',
76+
method: 'hset',
77+
protocol: consts.PROTOCOLS.REDIS
78+
})
79+
})
80+
81+
it('should instrument errors too', function () {
82+
var shimmerWrapStub = this.sandbox.stub(Shimmer, 'wrap')
83+
84+
var fakeRedis = {
85+
RedisClient: function () {
86+
this.address = 'fakeRedisAddress'
87+
}
88+
}
89+
90+
var fakeAgent = {
91+
generateSpanId: function () { return 'fakeSpanId' },
92+
getMicrotime: function () { return 42 },
93+
getTransactionId: function () { return 'fakeTransactionId' },
94+
clientSend: this.sandbox.spy(),
95+
clientReceive: this.sandbox.spy(),
96+
CLIENT_SEND: 'fakeSend'
97+
}
98+
99+
// wrapped as a side effect
100+
wrapper(fakeRedis, fakeAgent)
101+
var wrapOp = shimmerWrapStub.args[0][3]
102+
103+
var fakeRedisClientSend = this.sandbox.spy(function (command, args, callback) {
104+
callback(new Error('error')) // simulate the response
105+
})
106+
107+
wrapOp(fakeRedisClientSend).apply(new fakeRedis.RedisClient(), ['hset', ['abc', 'def']])
108+
109+
expect(fakeAgent.clientSend).to.have.been.calledOnce
110+
expect(fakeAgent.clientSend).to.have.been.calledWith({
111+
id: 'fakeTransactionId',
112+
spanId: 'fakeSpanId',
113+
host: 'fakeRedisAddress',
114+
time: 42,
115+
type: fakeAgent.CLIENT_SEND,
116+
url: 'unknown',
117+
method: 'hset'
118+
})
119+
120+
expect(fakeAgent.clientReceive).to.have.been.calledOnce
121+
expect(fakeAgent.clientReceive).to.have.been.calledWith({
122+
mustCollect: consts.MUST_COLLECT.ERROR,
123+
responseTime: 0,
124+
status: consts.EDGE_STATUS.NOT_OK,
125+
statusCode: undefined,
126+
id: 'fakeTransactionId',
127+
spanId: 'fakeSpanId',
128+
host: 'fakeRedisAddress',
129+
time: 42,
130+
url: 'unknown',
131+
method: 'hset',
132+
protocol: consts.PROTOCOLS.REDIS
133+
})
134+
})
135+
})

0 commit comments

Comments
 (0)