Skip to content

Commit 91e1f30

Browse files
committed
Merged APM prototype
1 parent 59d3f1b commit 91e1f30

File tree

19 files changed

+1160
-229
lines changed

19 files changed

+1160
-229
lines changed

docs/content/tutorials/tracing.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,176 @@ MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
5050
In this code we change the behavior of the findOne method by wrapping it in our own method that records the start and end times of the findOne operation and prints the number of milliseconds it took to the console. The cool thing is that this is global. Once we changed `Collection.prototype` we automatically get our new wrapped method for all methods that create a new `Collection` instance allowing us to instruments all calls using `Collection.findOne` across our application.
5151

5252
There is not much more to it so go ahead and think of some crazy ways to use this and if you do something very clever let me know :).
53+
54+
# APM Integration Interface API
55+
56+
The `2.0.29` driver introduces an Application Performance Monitoring integration interface to allow for more streamlined interfacing with the driver. The API exposes all the integration points the driver exposes for instrumentation and this should allow minimizing the breakage going forward when the driver adds or removed methods.
57+
58+
Interfacing is straight forward.
59+
60+
```js
61+
var mongodb = require('mongodb');
62+
mongodb.instrument(function(err, instrumentations) {
63+
64+
});
65+
```
66+
67+
Where `instrumentations` is an array of prototypes that can be instrumented and their metadata. In the case of the gridstore it might look like the following.
68+
69+
```js
70+
{
71+
name: 'GridStore',
72+
obj: GridStore,
73+
stream: true,
74+
instrumentations: [
75+
{ methods: [
76+
'open', 'getc', 'puts', 'write', 'writeFile', 'close', 'unlink', 'readlines',
77+
'rewind', 'read', 'tell', 'seek'
78+
], options: { callback: true, promise:false } },
79+
{ methods: [
80+
'collection'
81+
], options: { callback: true, promise:false, returns: connect.Collection } },
82+
{ methods: [
83+
'exist', 'list', 'read', 'readlines', 'unlink'
84+
], options: { callback: false, promise:false, static:true } },
85+
{ methods: [
86+
'eof', 'destroy', 'chunkCollection'
87+
], options: { callback: false, promise:false } }
88+
]
89+
}
90+
```
91+
92+
Let's break down the object to see what it contains and how we can use it to wrap the code. The first part of the object is the.
93+
94+
| `Parameter` | `Type` | `Description` |
95+
| :------------- | :--------- | :-----------------------------------------------------------|
96+
| name | string | The name of the Prototype |
97+
| obj | object | The prototype object |
98+
| stream | boolean | Is the Prototype a stream |
99+
| instrumentations | array | Array of instrumentation integrations |
100+
101+
Each of the entries in the `instrumentations` array contains a list of methods on the prototype and the associated metadata for those methods. Let's break down the examples from the GridStore object above.
102+
103+
```js
104+
{
105+
methods: ['open', 'getc', 'puts', 'write',
106+
'writeFile', 'close', 'unlink', 'readlines',
107+
'rewind', 'read', 'tell', 'seek'],
108+
options: {
109+
callback: true,
110+
promise:false
111+
}
112+
}
113+
```
114+
115+
Looking at the instrumentation description above we see that the `methods` array is a list methods names on the GridStore prototype. The `options` object contains metadata about the methods describing their structure. In this case it tells us that these methods take a `callback` and do not return a `promise`. Let's look at some other examples of metadata.
116+
117+
```js
118+
{
119+
methods: ['collection'],
120+
options: {
121+
callback: true,
122+
promise:false,
123+
returns: [Collection]
124+
}
125+
}
126+
```
127+
128+
This options object contains one difference from the previous one, namely the `returns` field. In short this method provides both a callback and a return value. An example of this in the driver would be the `Db.prototype.collection` method that can either take a callback or just return a collection. Let's look at the next instrumentation.
129+
130+
```js
131+
{
132+
methods: ['exist', 'list', 'read', 'readlines', 'unlink'],
133+
options: {
134+
callback: false,
135+
promise:false,
136+
static:true
137+
}
138+
}
139+
```
140+
141+
The options contain the `static` field that tells us that all the methods in this instrumentation are on the `GridStore` directly. An example method might be `GridStore.exist`.
142+
143+
```js
144+
{
145+
methods: ['eof', 'destroy', 'chunkCollection'],
146+
options: {
147+
callback: false,
148+
promise:false
149+
}
150+
}
151+
```
152+
153+
The final example tells us the method don't take a callback, return a promise or any other value. Let's describe the available options.
154+
155+
| `Parameter` | `Type` | `Description` |
156+
| :------------- | :--------- | :-----------------------------------------------------------|
157+
| callback | boolean | Method accepts a callback |
158+
| promise | boolean | The method returns a promise |
159+
| returns | array | The method return an array of possible return values |
160+
| static | boolean | Method is static on the prototype |
161+
| cursor | boolean | Method returns a cursor object |
162+
163+
Let's look at a simple example that wraps the callback only instrumentations.
164+
165+
```js
166+
require('../..').instrument(function(err, instrumentations) {
167+
instrumentations.forEach(function(obj) {
168+
var object = obj.obj;
169+
170+
// Iterate over all the methods that are just callback with no return
171+
obj.instrumentations.forEach(function(instr) {
172+
var options = instr.options;
173+
174+
if(options.callback
175+
&& !options.promise
176+
&& !options.returns && !options.static) {
177+
178+
// Method name
179+
instr.methods.forEach(function(method) {
180+
var applyMethod = function(_method) {
181+
var func = object.prototype[_method];
182+
object.prototype[_method] = function() {
183+
if(!methodsCalled[_method]) methodsCalled[_method] = 0;
184+
methodsCalled[_method] = methodsCalled[_method] + 1;
185+
var args = Array.prototype.slice.call(arguments, 0);
186+
func.apply(this, args);
187+
}
188+
}
189+
190+
applyMethod(method);
191+
});
192+
}
193+
});
194+
});
195+
});
196+
```
197+
198+
## Available Integration Points
199+
200+
Currently the available integration points are.
201+
202+
| `Prototype` | `Description` |
203+
| :------------- | :-----------------------------------------------------------|
204+
| Db | Db methods |
205+
| Collection | Collection methods |
206+
| GridStore | GridStore methods |
207+
| OrderedBulkOperation | Ordered bulk operation methods |
208+
| UnorderedBulkOperation | Unordered bulk operation methods |
209+
| CommandCursor | Command cursor queries |
210+
| AggregationCursor | Aggregation cursor queries |
211+
| Cursor | Query cursor queries |
212+
| Server | Low level server operations, return objects always contain the connection they where executed against |
213+
| ReplSet | Low level server operations, return objects always contain the connection they where executed against |
214+
| Mongos | Low level server operations, return objects always contain the connection they where executed against |
215+
216+
217+
218+
219+
220+
221+
222+
223+
224+
225+

index.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,37 @@ connect.connect = connect;
4040
// Instrumentation instance
4141
var instrumentation = null;
4242

43-
// // Set up the instrumentation method
44-
// connect.instrument = function(options) {
45-
// if(!instrumentation) instrumentation = new Instrumentation(core, options)
46-
// return instrumentation;
47-
// }
43+
// Set up the instrumentation method
44+
connect.instrument = function(options) {
45+
if(!instrumentation) instrumentation = new Instrumentation(core, options)
46+
return instrumentation;
47+
}
48+
// Get prototype
49+
var AggregationCursor = require('./lib/aggregation_cursor'),
50+
CommandCursor = require('./lib/command_cursor'),
51+
OrderedBulkOperation = require('./lib/bulk/ordered').OrderedBulkOperation,
52+
UnorderedBulkOperation = require('./lib/bulk/unordered').UnorderedBulkOperation,
53+
Admin = require('./lib/admin');
54+
55+
// Instrument Hook
56+
connect.instrument = function(callback) {
57+
var instrumentations = []
58+
59+
// Classes to support
60+
var classes = [connect.GridStore, connect.Server, connect.ReplSet, connect.Mongos,
61+
OrderedBulkOperation, UnorderedBulkOperation, CommandCursor, AggregationCursor,
62+
connect.Cursor, connect.Collection, connect.Db];
63+
64+
// Add instrumentations to the available list
65+
for(var i = 0; i < classes.length; i++) {
66+
if(classes[i].define) {
67+
instrumentations.push(classes[i].define.generate());
68+
}
69+
}
70+
71+
// Return the list of instrumentation points
72+
callback(null, instrumentations);
73+
}
4874

4975
// Set our exports to be the connect function
5076
module.exports = connect;

lib/aggregation_cursor.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var inherits = require('util').inherits
1111
, ReadPreference = require('./read_preference')
1212
, MongoError = require('mongodb-core').MongoError
1313
, Readable = require('stream').Readable || require('readable-stream').Readable
14+
, Define = require('./metadata')
1415
, CoreCursor = require('./cursor')
1516
, Query = require('mongodb-core').Query
1617
, CoreReadPreference = require('mongodb-core').ReadPreference;
@@ -144,6 +145,8 @@ for(var name in CoreCursor.prototype) {
144145
AggregationCursor.prototype[name] = CoreCursor.prototype[name];
145146
}
146147

148+
var define = AggregationCursor.define = new Define('AggregationCursor', AggregationCursor, true);
149+
147150
/**
148151
* Set the batch size for the cursor.
149152
* @method
@@ -159,6 +162,8 @@ AggregationCursor.prototype.batchSize = function(value) {
159162
return this;
160163
}
161164

165+
define.classMethod('batchSize', {callback: false, promise:false, returns: [AggregationCursor]});
166+
162167
/**
163168
* Add a geoNear stage to the aggregation pipeline
164169
* @method
@@ -170,6 +175,8 @@ AggregationCursor.prototype.geoNear = function(document) {
170175
return this;
171176
}
172177

178+
define.classMethod('geoNear', {callback: false, promise:false, returns: [AggregationCursor]});
179+
173180
/**
174181
* Add a group stage to the aggregation pipeline
175182
* @method
@@ -181,6 +188,8 @@ AggregationCursor.prototype.group = function(document) {
181188
return this;
182189
}
183190

191+
define.classMethod('group', {callback: false, promise:false, returns: [AggregationCursor]});
192+
184193
/**
185194
* Add a limit stage to the aggregation pipeline
186195
* @method
@@ -192,6 +201,8 @@ AggregationCursor.prototype.limit = function(value) {
192201
return this;
193202
}
194203

204+
define.classMethod('limit', {callback: false, promise:false, returns: [AggregationCursor]});
205+
195206
/**
196207
* Add a match stage to the aggregation pipeline
197208
* @method
@@ -203,6 +214,8 @@ AggregationCursor.prototype.match = function(document) {
203214
return this;
204215
}
205216

217+
define.classMethod('match', {callback: false, promise:false, returns: [AggregationCursor]});
218+
206219
/**
207220
* Add a maxTimeMS stage to the aggregation pipeline
208221
* @method
@@ -216,6 +229,8 @@ AggregationCursor.prototype.maxTimeMS = function(value) {
216229
return this;
217230
}
218231

232+
define.classMethod('maxTimeMS', {callback: false, promise:false, returns: [AggregationCursor]});
233+
219234
/**
220235
* Add a out stage to the aggregation pipeline
221236
* @method
@@ -227,6 +242,8 @@ AggregationCursor.prototype.out = function(destination) {
227242
return this;
228243
}
229244

245+
define.classMethod('out', {callback: false, promise:false, returns: [AggregationCursor]});
246+
230247
/**
231248
* Add a project stage to the aggregation pipeline
232249
* @method
@@ -238,6 +255,8 @@ AggregationCursor.prototype.project = function(document) {
238255
return this;
239256
}
240257

258+
define.classMethod('project', {callback: false, promise:false, returns: [AggregationCursor]});
259+
241260
/**
242261
* Add a redact stage to the aggregation pipeline
243262
* @method
@@ -249,6 +268,8 @@ AggregationCursor.prototype.redact = function(document) {
249268
return this;
250269
}
251270

271+
define.classMethod('redact', {callback: false, promise:false, returns: [AggregationCursor]});
272+
252273
/**
253274
* Add a skip stage to the aggregation pipeline
254275
* @method
@@ -260,6 +281,8 @@ AggregationCursor.prototype.skip = function(value) {
260281
return this;
261282
}
262283

284+
define.classMethod('skip', {callback: false, promise:false, returns: [AggregationCursor]});
285+
263286
/**
264287
* Add a sort stage to the aggregation pipeline
265288
* @method
@@ -271,6 +294,8 @@ AggregationCursor.prototype.sort = function(document) {
271294
return this;
272295
}
273296

297+
define.classMethod('sort', {callback: false, promise:false, returns: [AggregationCursor]});
298+
274299
/**
275300
* Add a unwind stage to the aggregation pipeline
276301
* @method
@@ -282,8 +307,24 @@ AggregationCursor.prototype.unwind = function(field) {
282307
return this;
283308
}
284309

310+
define.classMethod('unwind', {callback: false, promise:false, returns: [AggregationCursor]});
311+
285312
AggregationCursor.prototype.get = AggregationCursor.prototype.toArray;
286313

314+
define.classMethod('get', {callback: true, promise:false});
315+
316+
// Inherited methods
317+
define.classMethod('toArray', {callback: true, promise:false});
318+
define.classMethod('each', {callback: true, promise:false});
319+
define.classMethod('forEach', {callback: true, promise:false});
320+
define.classMethod('next', {callback: true, promise:false});
321+
define.classMethod('explain', {callback: true, promise:false});
322+
define.classMethod('close', {callback: true, promise:false});
323+
define.classMethod('isClosed', {callback: false, promise:false, returns: [Boolean]});
324+
define.classMethod('rewind', {callback: false, promise:false});
325+
define.classMethod('bufferedCount', {callback: false, promise:false, returns: [Number]});
326+
define.classMethod('readBufferedDocuments', {callback: false, promise:false, returns: [Array]});
327+
287328
/**
288329
* Get the next available document from the cursor, returns null if no more documents are available.
289330
* @function AggregationCursor.prototype.next

lib/bulk/ordered.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var common = require('./common')
99
, BulkWriteResult = common.BulkWriteResult
1010
, LegacyOp = common.LegacyOp
1111
, ObjectID = require('mongodb-core').BSON.ObjectID
12+
, Define = require('../metadata')
1213
, Batch = common.Batch
1314
, mergeBatchResults = common.mergeBatchResults;
1415

@@ -288,6 +289,8 @@ function OrderedBulkOperation(topology, collection, options) {
288289
}
289290
}
290291

292+
var define = OrderedBulkOperation.define = new Define('OrderedBulkOperation', OrderedBulkOperation, false);
293+
291294
OrderedBulkOperation.prototype.raw = function(op) {
292295
var key = Object.keys(op)[0];
293296

@@ -498,6 +501,8 @@ OrderedBulkOperation.prototype.execute = function(_writeConcern, callback) {
498501
});
499502
}
500503

504+
define.classMethod('execute', {callback: true, promise:false});
505+
501506
/**
502507
* Returns an unordered batch object
503508
* @ignore
@@ -506,5 +511,6 @@ var initializeOrderedBulkOp = function(topology, collection, options) {
506511
return new OrderedBulkOperation(topology, collection, options);
507512
}
508513

514+
initializeOrderedBulkOp.OrderedBulkOperation = OrderedBulkOperation;
509515
module.exports = initializeOrderedBulkOp;
510516
module.exports.Bulk = OrderedBulkOperation;

0 commit comments

Comments
 (0)