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