Skip to content

Commit

Permalink
feat(dbtraces): adding auto-tracing for redis module
Browse files Browse the repository at this point in the history
  • Loading branch information
mrickard authored Aug 21, 2019
2 parents 7ef1ec3 + d573f82 commit d9ab69a
Show file tree
Hide file tree
Showing 10 changed files with 580 additions and 125 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,15 @@ const iopipe = iopipeLib({
]
});
```
#### `autoRedis` Automatically trace Redis commands using redis (node_redis)

Set the environment variable `IOPIPE_TRACE_REDIS` to `true`, and IOpipe will trace Redis commands automatically: which command, which key is being read or written, and information about the connection: hostname, port, and connection name (if defined in your connection options). Commands batched with multi/exec are traced individually, so you can measure individual performance within batch operations.

If you're using redis@2.5.3 or earlier, turn on auto-tracing with the `IOPIPE_TRACE_REDIS_CB` environment variable set to true.

#### `autoIoRedis` Automatically trace Redis commands using ioredis

Setting the environment variable `IOPIPE_TRACE_IOREDIS` to `true` for your function will enable automatic traces on Redis commands: the name of the command, name of the host, port, and connection (if defined in your connection options), and the key being written or read. Commands batched with multi/exec are traced individually, so you can measure individual performance within batch operations.
Set the environment variable `IOPIPE_TRACE_IOREDIS` to `true`, and your function will enable automatic traces on Redis commands: the name of the command, name of the host, port, and connection (if defined in your connection options), and the key being written or read. Commands batched with multi/exec are traced individually, so you can measure individual performance within batch operations.

#### `autoMeasure` (bool: optional = true)

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"prepare": "npm run build",
"release": "iopipe-scripts release",
"test": "iopipe-scripts test",
"testRedis": "yarn run jest src/plugins/ioredis.test.js --forceExit --verbose",
"validate": "iopipe-scripts validate"
},
"repository": {
Expand All @@ -36,21 +35,22 @@
},
"homepage": "https://github.com/iopipe/iopipe-plugin-trace#readme",
"devDependencies": {
"@iopipe/core": "^1.x",
"@iopipe/core": "^1",
"@iopipe/scripts": "^1.4.1",
"aws-lambda-mock-context": "^3.0.0",
"delay": "^2.0.0",
"got": "^8.3.1",
"ioredis": "^4",
"lodash": "^4.17.4",
"redis": "^2",
"superagent": "^3.8.3"
},
"dependencies": {
"flat": "^4.0.0",
"ioredis": "^4.9.0",
"isarray": "^2.0.4",
"lodash.pickby": "^4.6.0",
"performance-node": "^0.2.0",
"semver": "^6.0.0",
"performance-node": "^0",
"semver": "^6.3.0",
"shimmer": "^1.2.1",
"uuid": "^3.2.1"
},
Expand Down
30 changes: 8 additions & 22 deletions src/addToReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ export function addToReport(pluginInstance, timelineArg) {
];
}

export function addHttpTracesToReport(plugin) {
const { autoHttpData: { timeline = {} } } = plugin;
export function addTraceData(plugin, type) {
const namespace = `${type.config}Data`;

const { [namespace]: { timeline = {} } } = plugin;
const { report: { report = {} } = {} } = plugin.invocationInstance;
Object.keys(plugin.autoHttpData.data).forEach(id => {
const obj = unflatten(plugin.autoHttpData.data[id] || {});

Object.keys(plugin[namespace].data).forEach(id => {
const obj = unflatten(plugin[namespace].data[id] || {});
if (obj.request) {
obj.request.headers = headersObjToArray(obj.request.headers);
}
Expand All @@ -34,23 +37,6 @@ export function addHttpTracesToReport(plugin) {
obj.timestamp = startMark.timestamp || 0;
obj.startTime = startMark.startTime || 0;
obj.duration = measureMark.duration || 0;
report.httpTraceEntries.push(obj);
});
}

export function addIoRedisTracesToReport(plugin) {
const { autoIoRedisData: { timeline = {} } } = plugin;
const { report: { report = {} } = {} } = plugin.invocationInstance;
Object.keys(plugin.autoIoRedisData.data).forEach(id => {
const obj = unflatten(plugin.autoIoRedisData.data[id] || {});
// use start mark for startTime in case the call did not finish / no callback
// and we do not have a measurement
const [startMark = {}] = timeline.getEntriesByName(`start:${id}`) || [];
const [measureMark = {}] = timeline.getEntriesByName(`measure:${id}`) || [];
obj.timestamp = startMark.timestamp || 0;
obj.startTime = startMark.startTime || 0;
obj.duration = measureMark.duration || 0;

report.dbTraceEntries.push(obj);
report[type.entries].push(obj);
});
}
128 changes: 75 additions & 53 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import Perf from 'performance-node';

import pkg from '../package.json'; // eslint-disable-line import/extensions
import {
addToReport,
addHttpTracesToReport,
addIoRedisTracesToReport
} from './addToReport';
import { wrap as httpWrap, unwrap as httpUnwrap } from './plugins/https';
import {
wrap as ioRedisWrap,
unwrap as ioRedisUnwrap
} from './plugins/ioredis';
import { addToReport, addTraceData } from './addToReport';

const plugins = {
https: {
config: 'autoHttp',
enabled: true,
entries: 'httpTraceEntries'
},
ioredis: {
config: 'autoIoRedis',
flag: 'IOPIPE_TRACE_IOREDIS',
entries: 'dbTraceEntries'
},
redis: {
config: 'autoRedis',
flag: 'IOPIPE_TRACE_REDIS',
entries: 'dbTraceEntries'
}
};

function getBooleanFromEnv(key = '') {
const isFalsey =
Expand All @@ -27,7 +36,8 @@ function getConfig(config = {}) {
const {
autoMeasure = true,
autoHttp = { enabled: true },
autoIoRedis = { enabled: getBooleanFromEnv('IOPIPE_TRACE_IOREDIS') }
autoIoRedis = { enabled: getBooleanFromEnv('IOPIPE_TRACE_IOREDIS') },
autoRedis = { enabled: getBooleanFromEnv('IOPIPE_TRACE_REDIS') }
} = config;
return {
autoHttp: {
Expand All @@ -43,6 +53,12 @@ function getConfig(config = {}) {
? autoIoRedis.enabled
: getBooleanFromEnv('IOPIPE_TRACE_IOREDIS')
},
autoRedis: {
enabled:
typeof autoRedis.enabled === 'boolean'
? autoRedis.enabled
: getBooleanFromEnv('IOPIPE_TRACE_REDIS')
},
autoMeasure
};
}
Expand All @@ -58,6 +74,7 @@ function addTimelineMeasures(pluginInstance, timelineArg) {
.map(entry => entry.name);
// loop through each mark and make sure there is a start and end
// if so, measure
// console.log('NAMES', names);
names.forEach(name => {
if (name.match(/^(start):.+/)) {
const baseName = name.replace(/(start|end):(.+)/, '$2');
Expand All @@ -74,18 +91,12 @@ function addTimelineMeasures(pluginInstance, timelineArg) {
return true;
}

function recordAutoHttpData(plugin) {
addTimelineMeasures(plugin, plugin.autoHttpData.timeline);
addHttpTracesToReport(plugin);
plugin.autoHttpData.timeline.clear();
plugin.autoHttpData.data = {};
}

function recordAutoIoRedisData(plugin) {
addTimelineMeasures(plugin, plugin.autoIoRedisData.timeline);
addIoRedisTracesToReport(plugin);
plugin.autoIoRedisData.timeline.clear();
plugin.autoIoRedisData.data = {};
function recordData(plugin, type) {
const namespace = `${plugins[type].config}Data`;
addTimelineMeasures(plugin, plugin[namespace].timeline);
addTraceData(plugin, plugins[type]);
plugin[namespace].timeline.clear();
plugin[namespace].data = {};
}

class TracePlugin {
Expand All @@ -99,24 +110,28 @@ class TracePlugin {
'post:invoke': this.postInvoke.bind(this),
'pre:report': this.preReport.bind(this)
};
if (this.config.autoHttp.enabled) {
this.autoHttpData = {
timeline: new Perf({ timestamp: true }),
// object to store data about traces that will make it into the report later
data: {},
config: this.config.autoHttp
};
httpWrap(this.autoHttpData);
}
if (this.config.autoIoRedis.enabled) {
this.autoIoRedisData = {
timeline: new Perf({ timestamp: true }),
// object to store data about traces that will make it into the report later
data: {},
config: this.config.autoIoRedis
};
ioRedisWrap(this.autoIoRedisData);
}

const context = this;
const pluginKeys = Object.keys(plugins);
pluginKeys.forEach(k => {
const conf = plugins[k].config;
const namespace = `${conf}Data`;

if (context.config[conf].enabled) {
// getting plugin; allows this to be loaded only if enabled.
const module = require(`./plugins/${k}`);
plugins[k].wrap = module.wrap;
plugins[k].unwrap = module.unwrap;

context[namespace] = {
timeline: new Perf({ timestamp: true }),
// object to store data about traces that will make it into the report later
data: {},
config: context.config[conf]
};
plugins[k].wrap(context[namespace]);
}
});

return this;
}
Expand All @@ -133,12 +148,17 @@ class TracePlugin {
this.invocationInstance.report.report.dbTraceEntries = [];
}
postInvoke() {
if (this.config.autoHttp.enabled) {
httpUnwrap();
}
if (this.config.autoIoRedis.enabled) {
ioRedisUnwrap();
}
const context = this;
const pluginKeys = Object.keys(plugins);
pluginKeys.forEach(k => {
const conf = plugins[k].config;
if (context.config[conf].enabled) {
if (context.config[conf].enabled) {
plugins[k].unwrap();
}
}
});

if (
typeof this.invocationInstance.context.iopipe.label === 'function' &&
this.timeline.getEntries().length > 0
Expand All @@ -150,12 +170,14 @@ class TracePlugin {
if (this.config.autoMeasure) {
addTimelineMeasures(this);
}
if (this.config.autoHttp.enabled) {
recordAutoHttpData(this);
}
if (this.config.autoIoRedis.enabled) {
recordAutoIoRedisData(this);
}
const context = this;
const pluginKeys = Object.keys(plugins);
pluginKeys.forEach(k => {
const conf = plugins[k].config;
if (this.config[conf].enabled) {
recordData(context, k);
}
});
addToReport(this);
}
start(name) {
Expand Down
2 changes: 1 addition & 1 deletion src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ test('autoHttp works with got(url) and options', async () => {
const result = await context.Promise;
expect(result).toBe(200);
const traces = getTracesFromInspectableInv(inspectableInv);
// we excluded traces for http calls that have ?exlude=true in url, so only 1 trace total should be present
// we excluded traces for http calls that have ?exclude=true in url, so only 1 trace total should be present
expect(traces).toHaveLength(1);
expect(traces).toMatchSnapshot();
} catch (err) {
Expand Down
3 changes: 1 addition & 2 deletions src/plugins/ioredis.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ const debug = debuglog('@iopipe/trace');
/*eslint-disable func-name-matching */
/*eslint-disable prefer-rest-params */

const createId = () => `redis-${uuid()}`;
const createId = () => `ioredis-${uuid()}`;

const filterRequest = (command, context) => {
const { name, args } = command;
if (!context) {
return null;
}
const { hostname, port, connectionName, db } = context.options;

return {
command: name,
key: args[0] ? args[0] : null,
Expand Down
Loading

0 comments on commit d9ab69a

Please sign in to comment.