PageRenderTime 73ms CodeModel.GetById 15ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 1ms

/node_modules/mongoose/node_modules/mongodb/lib/apm.js

https://bitbucket.org/coleman333/smartsite
JavaScript | 639 lines | 409 code | 94 blank | 136 comment | 100 complexity | 56563a42619095b7e5f3e4c38701aa6a MD5 | raw file
  1'use strict';
  2
  3var EventEmitter = require('events').EventEmitter,
  4  inherits = require('util').inherits;
  5
  6// Get prototypes
  7var AggregationCursor = require('./aggregation_cursor'),
  8  CommandCursor = require('./command_cursor'),
  9  OrderedBulkOperation = require('./bulk/ordered').OrderedBulkOperation,
 10  UnorderedBulkOperation = require('./bulk/unordered').UnorderedBulkOperation,
 11  GridStore = require('./gridfs/grid_store'),
 12  Cursor = require('./cursor'),
 13  Collection = require('./collection'),
 14  Db = require('./db');
 15
 16var basicOperationIdGenerator = {
 17  operationId: 1,
 18
 19  next: function() {
 20    return this.operationId++;
 21  }
 22};
 23
 24var basicTimestampGenerator = {
 25  current: function() {
 26    return new Date().getTime();
 27  },
 28
 29  duration: function(start, end) {
 30    return end - start;
 31  }
 32};
 33
 34var senstiveCommands = [
 35  'authenticate',
 36  'saslStart',
 37  'saslContinue',
 38  'getnonce',
 39  'createUser',
 40  'updateUser',
 41  'copydbgetnonce',
 42  'copydbsaslstart',
 43  'copydb'
 44];
 45
 46var Instrumentation = function(core, options, callback) {
 47  options = options || {};
 48
 49  // Optional id generators
 50  var operationIdGenerator = options.operationIdGenerator || basicOperationIdGenerator;
 51  // Optional timestamp generator
 52  var timestampGenerator = options.timestampGenerator || basicTimestampGenerator;
 53  // Extend with event emitter functionality
 54  EventEmitter.call(this);
 55
 56  // Contains all the instrumentation overloads
 57  this.overloads = [];
 58
 59  // ---------------------------------------------------------
 60  //
 61  // Instrument prototype
 62  //
 63  // ---------------------------------------------------------
 64
 65  var instrumentPrototype = function(callback) {
 66    var instrumentations = [];
 67
 68    // Classes to support
 69    var classes = [
 70      GridStore,
 71      OrderedBulkOperation,
 72      UnorderedBulkOperation,
 73      CommandCursor,
 74      AggregationCursor,
 75      Cursor,
 76      Collection,
 77      Db
 78    ];
 79
 80    // Add instrumentations to the available list
 81    for (var i = 0; i < classes.length; i++) {
 82      if (classes[i].define) {
 83        instrumentations.push(classes[i].define.generate());
 84      }
 85    }
 86
 87    // Return the list of instrumentation points
 88    callback(null, instrumentations);
 89  };
 90
 91  // Did the user want to instrument the prototype
 92  if (typeof callback === 'function') {
 93    instrumentPrototype(callback);
 94  }
 95
 96  // ---------------------------------------------------------
 97  //
 98  // Server
 99  //
100  // ---------------------------------------------------------
101
102  // Reference
103  var self = this;
104  // Names of methods we need to wrap
105  var methods = ['command', 'insert', 'update', 'remove'];
106  // Prototype
107  var proto = core.Server.prototype;
108  // Core server method we are going to wrap
109  methods.forEach(function(x) {
110    var func = proto[x];
111
112    // Add to overloaded methods
113    self.overloads.push({ proto: proto, name: x, func: func });
114
115    // The actual prototype
116    proto[x] = function() {
117      var requestId = core.Query.nextRequestId();
118      // Get the aruments
119      var args = Array.prototype.slice.call(arguments, 0);
120      var ns = args[0];
121      var commandObj = args[1];
122      var options = args[2] || {};
123      var keys = Object.keys(commandObj);
124      var commandName = keys[0];
125      var db = ns.split('.')[0];
126
127      // Get the collection
128      var col = ns.split('.');
129      col.shift();
130      col = col.join('.');
131
132      // Do we have a legacy insert/update/remove command
133      if (x === 'insert') {
134        //} && !this.lastIsMaster().maxWireVersion) {
135        commandName = 'insert';
136
137        // Re-write the command
138        commandObj = {
139          insert: col,
140          documents: commandObj
141        };
142
143        if (options.writeConcern && Object.keys(options.writeConcern).length > 0) {
144          commandObj.writeConcern = options.writeConcern;
145        }
146
147        commandObj.ordered = options.ordered !== undefined ? options.ordered : true;
148      } else if (x === 'update') {
149        // && !this.lastIsMaster().maxWireVersion) {
150        commandName = 'update';
151
152        // Re-write the command
153        commandObj = {
154          update: col,
155          updates: commandObj
156        };
157
158        if (options.writeConcern && Object.keys(options.writeConcern).length > 0) {
159          commandObj.writeConcern = options.writeConcern;
160        }
161
162        commandObj.ordered = options.ordered !== undefined ? options.ordered : true;
163      } else if (x === 'remove') {
164        //&& !this.lastIsMaster().maxWireVersion) {
165        commandName = 'delete';
166
167        // Re-write the command
168        commandObj = {
169          delete: col,
170          deletes: commandObj
171        };
172
173        if (options.writeConcern && Object.keys(options.writeConcern).length > 0) {
174          commandObj.writeConcern = options.writeConcern;
175        }
176
177        commandObj.ordered = options.ordered !== undefined ? options.ordered : true;
178      }
179
180      // Get the callback
181      var callback = args.pop();
182      // Set current callback operation id from the current context or create
183      // a new one
184      var ourOpId = callback.operationId || operationIdGenerator.next();
185
186      // Get a connection reference for this server instance
187      var connection = this.s.pool.get();
188
189      // Emit the start event for the command
190      var command = {
191        // Returns the command.
192        command: commandObj,
193        // Returns the database name.
194        databaseName: db,
195        // Returns the command name.
196        commandName: commandName,
197        // Returns the driver generated request id.
198        requestId: requestId,
199        // Returns the driver generated operation id.
200        // This is used to link events together such as bulk write operations. OPTIONAL.
201        operationId: ourOpId,
202        // Returns the connection id for the command. For languages that do not have this,
203        // this MUST return the driver equivalent which MUST include the server address and port.
204        // The name of this field is flexible to match the object that is returned from the driver.
205        connectionId: connection
206      };
207
208      // Filter out any sensitive commands
209      if (senstiveCommands.indexOf(commandName.toLowerCase()) !== -1) {
210        command.commandObj = {};
211        command.commandObj[commandName] = true;
212      }
213
214      // Emit the started event
215      self.emit('started', command);
216
217      // Start time
218      var startTime = timestampGenerator.current();
219
220      // Push our handler callback
221      args.push(function(err, r) {
222        var endTime = timestampGenerator.current();
223        var command = {
224          duration: timestampGenerator.duration(startTime, endTime),
225          commandName: commandName,
226          requestId: requestId,
227          operationId: ourOpId,
228          connectionId: connection
229        };
230
231        // If we have an error
232        if (err || (r && r.result && r.result.ok === 0)) {
233          command.failure = err || r.result.writeErrors || r.result;
234
235          // Filter out any sensitive commands
236          if (senstiveCommands.indexOf(commandName.toLowerCase()) !== -1) {
237            command.failure = {};
238          }
239
240          self.emit('failed', command);
241        } else if (commandObj && commandObj.writeConcern && commandObj.writeConcern.w === 0) {
242          // If we have write concern 0
243          command.reply = { ok: 1 };
244          self.emit('succeeded', command);
245        } else {
246          command.reply = r && r.result ? r.result : r;
247
248          // Filter out any sensitive commands
249          if (senstiveCommands.indexOf(commandName.toLowerCase()) !== -1) {
250            command.reply = {};
251          }
252
253          self.emit('succeeded', command);
254        }
255
256        // Return to caller
257        callback(err, r);
258      });
259
260      // Apply the call
261      func.apply(this, args);
262    };
263  });
264
265  // ---------------------------------------------------------
266  //
267  // Bulk Operations
268  //
269  // ---------------------------------------------------------
270
271  // Inject ourselves into the Bulk methods
272  methods = ['execute'];
273  var prototypes = [
274    require('./bulk/ordered').Bulk.prototype,
275    require('./bulk/unordered').Bulk.prototype
276  ];
277
278  prototypes.forEach(function(proto) {
279    // Core server method we are going to wrap
280    methods.forEach(function(x) {
281      var func = proto[x];
282
283      // Add to overloaded methods
284      self.overloads.push({ proto: proto, name: x, func: func });
285
286      // The actual prototype
287      proto[x] = function() {
288        // Get the aruments
289        var args = Array.prototype.slice.call(arguments, 0);
290        // Set an operation Id on the bulk object
291        this.operationId = operationIdGenerator.next();
292
293        // Get the callback
294        var callback = args.pop();
295        // If we have a callback use this
296        if (typeof callback === 'function') {
297          args.push(function(err, r) {
298            // Return to caller
299            callback(err, r);
300          });
301
302          // Apply the call
303          func.apply(this, args);
304        } else {
305          return func.apply(this, args);
306        }
307      };
308    });
309  });
310
311  // ---------------------------------------------------------
312  //
313  // Cursor
314  //
315  // ---------------------------------------------------------
316
317  // Inject ourselves into the Cursor methods
318  methods = ['_find', '_getmore', '_killcursor'];
319  prototypes = [
320    require('./cursor').prototype,
321    require('./command_cursor').prototype,
322    require('./aggregation_cursor').prototype
323  ];
324
325  // Command name translation
326  var commandTranslation = {
327    _find: 'find',
328    _getmore: 'getMore',
329    _killcursor: 'killCursors',
330    _explain: 'explain'
331  };
332
333  prototypes.forEach(function(proto) {
334    // Core server method we are going to wrap
335    methods.forEach(function(x) {
336      var func = proto[x];
337
338      // Add to overloaded methods
339      self.overloads.push({ proto: proto, name: x, func: func });
340
341      // The actual prototype
342      proto[x] = function() {
343        var cursor = this;
344        var requestId = core.Query.nextRequestId();
345        var ourOpId = operationIdGenerator.next();
346        var parts = this.ns.split('.');
347        var db = parts[0];
348
349        // Get the collection
350        parts.shift();
351        var collection = parts.join('.');
352
353        // Set the command
354        var command = this.query;
355        var cmd = this.s.cmd;
356
357        // If we have a find method, set the operationId on the cursor
358        if (x === '_find') {
359          cursor.operationId = ourOpId;
360        }
361
362        // Do we have a find command rewrite it
363        if (x === '_getmore') {
364          command = {
365            getMore: this.cursorState.cursorId,
366            collection: collection,
367            batchSize: cmd.batchSize
368          };
369
370          if (cmd.maxTimeMS) command.maxTimeMS = cmd.maxTimeMS;
371        } else if (x === '_killcursor') {
372          command = {
373            killCursors: collection,
374            cursors: [this.cursorState.cursorId]
375          };
376        } else if (cmd.find) {
377          command = {
378            find: collection,
379            filter: cmd.query
380          };
381
382          if (cmd.sort) command.sort = cmd.sort;
383          if (cmd.fields) command.projection = cmd.fields;
384          if (cmd.limit && cmd.limit < 0) {
385            command.limit = Math.abs(cmd.limit);
386            command.singleBatch = true;
387          } else if (cmd.limit) {
388            command.limit = Math.abs(cmd.limit);
389          }
390
391          // Options
392          if (cmd.skip) command.skip = cmd.skip;
393          if (cmd.hint) command.hint = cmd.hint;
394          if (cmd.batchSize) command.batchSize = cmd.batchSize;
395          if (typeof cmd.returnKey === 'boolean') command.returnKey = cmd.returnKey;
396          if (cmd.comment) command.comment = cmd.comment;
397          if (cmd.min) command.min = cmd.min;
398          if (cmd.max) command.max = cmd.max;
399          if (cmd.maxScan) command.maxScan = cmd.maxScan;
400          if (cmd.maxTimeMS) command.maxTimeMS = cmd.maxTimeMS;
401
402          // Flags
403          if (typeof cmd.awaitData === 'boolean') command.awaitData = cmd.awaitData;
404          if (typeof cmd.snapshot === 'boolean') command.snapshot = cmd.snapshot;
405          if (typeof cmd.tailable === 'boolean') command.tailable = cmd.tailable;
406          if (typeof cmd.oplogReplay === 'boolean') command.oplogReplay = cmd.oplogReplay;
407          if (typeof cmd.noCursorTimeout === 'boolean')
408            command.noCursorTimeout = cmd.noCursorTimeout;
409          if (typeof cmd.partial === 'boolean') command.partial = cmd.partial;
410          if (typeof cmd.showDiskLoc === 'boolean') command.showRecordId = cmd.showDiskLoc;
411
412          // Read Concern
413          if (cmd.readConcern) command.readConcern = cmd.readConcern;
414
415          // Override method
416          if (cmd.explain) command.explain = cmd.explain;
417          if (cmd.exhaust) command.exhaust = cmd.exhaust;
418
419          // If we have a explain flag
420          if (cmd.explain) {
421            // Create fake explain command
422            command = {
423              explain: command,
424              verbosity: 'allPlansExecution'
425            };
426
427            // Set readConcern on the command if available
428            if (cmd.readConcern) command.readConcern = cmd.readConcern;
429
430            // Set up the _explain name for the command
431            x = '_explain';
432          }
433        } else {
434          command = cmd;
435        }
436
437        // Set up the connection
438        var connectionId = null;
439
440        // Set local connection
441        if (this.connection) connectionId = this.connection;
442        if (!connectionId && this.topology && this.topology.getConnection)
443          connectionId = this.topology.getConnection();
444
445        // Get the command Name
446        var commandName = x === '_find' ? Object.keys(command)[0] : commandTranslation[x];
447
448        // Emit the start event for the command
449        command = {
450          // Returns the command.
451          command: command,
452          // Returns the database name.
453          databaseName: db,
454          // Returns the command name.
455          commandName: commandName,
456          // Returns the driver generated request id.
457          requestId: requestId,
458          // Returns the driver generated operation id.
459          // This is used to link events together such as bulk write operations. OPTIONAL.
460          operationId: this.operationId,
461          // Returns the connection id for the command. For languages that do not have this,
462          // this MUST return the driver equivalent which MUST include the server address and port.
463          // The name of this field is flexible to match the object that is returned from the driver.
464          connectionId: connectionId
465        };
466
467        // Get the aruments
468        var args = Array.prototype.slice.call(arguments, 0);
469
470        // Get the callback
471        var callback = args.pop();
472
473        // We do not have a callback but a Promise
474        if (typeof callback === 'function' || command.commandName === 'killCursors') {
475          var startTime = timestampGenerator.current();
476          // Emit the started event
477          self.emit('started', command);
478
479          // Emit succeeded event with killcursor if we have a legacy protocol
480          if (
481            command.commandName === 'killCursors' &&
482            this.topology.lastIsMaster() &&
483            this.topology.lastIsMaster().maxWireVersion < 4
484          ) {
485            // Emit the succeeded command
486            command = {
487              duration: timestampGenerator.duration(startTime, timestampGenerator.current()),
488              commandName: commandName,
489              requestId: requestId,
490              operationId: cursor.operationId,
491              connectionId: cursor.topology.getConnection(),
492              reply: [{ ok: 1 }]
493            };
494
495            // Apply callback to the list of args
496            args.push(callback);
497            // Apply the call
498            func.apply(this, args);
499            // Emit the command
500            return self.emit('succeeded', command);
501          }
502
503          // Add our callback handler
504          args.push(function(err, r) {
505            if (err) {
506              // Command
507              var command = {
508                duration: timestampGenerator.duration(startTime, timestampGenerator.current()),
509                commandName: commandName,
510                requestId: requestId,
511                operationId: ourOpId,
512                connectionId: cursor.topology.getConnection(),
513                failure: err
514              };
515
516              // Emit the command
517              self.emit('failed', command);
518            } else {
519              if (r && r.documents) {
520                r = r.documents[0];
521              }
522
523              if (commandName.toLowerCase() === 'getmore' && (r == null || r.cursor == null)) {
524                r = {
525                  cursor: {
526                    id: cursor.cursorState.cursorId,
527                    ns: cursor.ns,
528                    nextBatch: cursor.cursorState.documents
529                  },
530                  ok: 1
531                };
532              } else if (
533                (commandName.toLowerCase() === 'find' ||
534                  commandName.toLowerCase() === 'aggregate' ||
535                  commandName.toLowerCase() === 'listcollections') &&
536                (r == null || r.cursor == null)
537              ) {
538                r = {
539                  cursor: {
540                    id: cursor.cursorState.cursorId,
541                    ns: cursor.ns,
542                    firstBatch: cursor.cursorState.documents
543                  },
544                  ok: 1
545                };
546              } else if (commandName.toLowerCase() === 'killcursors' && r == null) {
547                r = {
548                  cursorsUnknown: [cursor.cursorState.lastCursorId],
549                  ok: 1
550                };
551              }
552
553              // cursor id is zero, we can issue success command
554              command = {
555                duration: timestampGenerator.duration(startTime, timestampGenerator.current()),
556                commandName: commandName,
557                requestId: requestId,
558                operationId: cursor.operationId,
559                connectionId: cursor.topology.getConnection(),
560                reply: r && r.result ? r.result : r
561              };
562
563              // Emit the command
564              self.emit('succeeded', command);
565            }
566
567            // Return
568            if (!callback) return;
569
570            // Return to caller
571            callback(err, r);
572          });
573
574          // Apply the call
575          func.apply(this, args);
576        } else {
577          // Assume promise, push back the missing value
578          args.push(callback);
579          // Get the promise
580          var promise = func.apply(this, args);
581          // Return a new promise
582          return new cursor.s.promiseLibrary(function(resolve, reject) {
583            var startTime = timestampGenerator.current();
584            // Emit the started event
585            self.emit('started', command);
586            // Execute the function
587            promise
588              .then(function() {
589                // cursor id is zero, we can issue success command
590                var command = {
591                  duration: timestampGenerator.duration(startTime, timestampGenerator.current()),
592                  commandName: commandName,
593                  requestId: requestId,
594                  operationId: cursor.operationId,
595                  connectionId: cursor.topology.getConnection(),
596                  reply: cursor.cursorState.documents
597                };
598
599                // Emit the command
600                self.emit('succeeded', command);
601              })
602              .catch(function(err) {
603                // Command
604                var command = {
605                  duration: timestampGenerator.duration(startTime, timestampGenerator.current()),
606                  commandName: commandName,
607                  requestId: requestId,
608                  operationId: ourOpId,
609                  connectionId: cursor.topology.getConnection(),
610                  failure: err
611                };
612
613                // Emit the command
614                self.emit('failed', command);
615                // reject the promise
616                reject(err);
617              });
618          });
619        }
620      };
621    });
622  });
623};
624
625inherits(Instrumentation, EventEmitter);
626
627Instrumentation.prototype.uninstrument = function() {
628  for (var i = 0; i < this.overloads.length; i++) {
629    var obj = this.overloads[i];
630    obj.proto[obj.name] = obj.func;
631  }
632
633  // Remove all listeners
634  this.removeAllListeners('started');
635  this.removeAllListeners('succeeded');
636  this.removeAllListeners('failed');
637};
638
639module.exports = Instrumentation;