1 sh = function() {
  2     return "try sh.help();";
  3 };
  4 
  5 sh._checkMongos = function() {
  6     var x = db.runCommand("ismaster");
  7     if (x.msg != "isdbgrid")
  8         throw Error("not connected to a mongos");
  9 };
 10 
 11 sh._checkFullName = function(fullName) {
 12     assert(fullName, "need a full name");
 13     assert(fullName.indexOf(".") > 0, "name needs to be fully qualified <db>.<collection>'");
 14 };
 15 
 16 sh._adminCommand = function(cmd, skipCheck) {
 17     if (!skipCheck)
 18         sh._checkMongos();
 19     return db.getSisterDB("admin").runCommand(cmd);
 20 };
 21 
 22 sh._getConfigDB = function() {
 23     sh._checkMongos();
 24     return db.getSiblingDB("config");
 25 };
 26 
 27 sh._dataFormat = function(bytes) {
 28     if (bytes < 1024)
 29         return Math.floor(bytes) + "B";
 30     if (bytes < 1024 * 1024)
 31         return Math.floor(bytes / 1024) + "KiB";
 32     if (bytes < 1024 * 1024 * 1024)
 33         return Math.floor((Math.floor(bytes / 1024) / 1024) * 100) / 100 + "MiB";
 34     return Math.floor((Math.floor(bytes / (1024 * 1024)) / 1024) * 100) / 100 + "GiB";
 35 };
 36 
 37 sh._collRE = function(coll) {
 38     return RegExp("^" + RegExp.escape(coll + "") + "-.*");
 39 };
 40 
 41 sh._pchunk = function(chunk) {
 42     return "[" + tojson(chunk.min) + " -> " + tojson(chunk.max) + "]";
 43 };
 44 
 45 /**
 46  * Internal method to write the balancer state to the config.settings collection. Should not be used
 47  * directly, instead go through the start/stopBalancer calls and the balancerStart/Stop commands.
 48  */
 49 sh._writeBalancerStateDeprecated = function(onOrNot) {
 50     return assert.writeOK(
 51         sh._getConfigDB().settings.update({_id: 'balancer'},
 52                                           {$set: {stopped: onOrNot ? false : true}},
 53                                           {upsert: true, writeConcern: {w: 'majority'}}));
 54 };
 55 
 56 sh.help = function() {
 57     print("\tsh.addShard( host )                       server:port OR setname/server:port");
 58     print("\tsh.addShardToZone(shard,zone)             adds the shard to the zone");
 59     print("\tsh.updateZoneKeyRange(fullName,min,max,zone)      " +
 60           "assigns the specified range of the given collection to a zone");
 61     print("\tsh.disableBalancing(coll)                 disable balancing on one collection");
 62     print("\tsh.enableBalancing(coll)                  re-enable balancing on one collection");
 63     print("\tsh.enableSharding(dbname)                 enables sharding on the database dbname");
 64     print("\tsh.getBalancerState()                     returns whether the balancer is enabled");
 65     print(
 66         "\tsh.isBalancerRunning()                    return true if the balancer has work in progress on any mongos");
 67     print(
 68         "\tsh.moveChunk(fullName,find,to)            move the chunk where 'find' is to 'to' (name of shard)");
 69     print("\tsh.removeShardFromZone(shard,zone)      removes the shard from zone");
 70     print(
 71         "\tsh.removeRangeFromZone(fullName,min,max)   removes the range of the given collection from any zone");
 72     print("\tsh.shardCollection(fullName,key,unique)   shards the collection");
 73     print(
 74         "\tsh.splitAt(fullName,middle)               splits the chunk that middle is in at middle");
 75     print(
 76         "\tsh.splitFind(fullName,find)               splits the chunk that find is in at the median");
 77     print(
 78         "\tsh.startBalancer()                        starts the balancer so chunks are balanced automatically");
 79     print("\tsh.status()                               prints a general overview of the cluster");
 80     print(
 81         "\tsh.stopBalancer()                         stops the balancer so chunks are not balanced automatically");
 82     print("\tsh.disableAutoSplit()                   disable autoSplit on one collection");
 83     print("\tsh.enableAutoSplit()                    re-eable autoSplit on one colleciton");
 84     print("\tsh.getShouldAutoSplit()                 returns whether autosplit is enabled");
 85 };
 86 
 87 sh.status = function(verbose, configDB) {
 88     // TODO: move the actual command here
 89     printShardingStatus(configDB, verbose);
 90 };
 91 
 92 sh.addShard = function(url) {
 93     return sh._adminCommand({addShard: url}, true);
 94 };
 95 
 96 sh.enableSharding = function(dbname) {
 97     assert(dbname, "need a valid dbname");
 98     return sh._adminCommand({enableSharding: dbname});
 99 };
100 
101 sh.shardCollection = function(fullName, key, unique) {
102     sh._checkFullName(fullName);
103     assert(key, "need a key");
104     assert(typeof(key) == "object", "key needs to be an object");
105 
106     var cmd = {shardCollection: fullName, key: key};
107     if (unique)
108         cmd.unique = true;
109 
110     return sh._adminCommand(cmd);
111 };
112 
113 sh.splitFind = function(fullName, find) {
114     sh._checkFullName(fullName);
115     return sh._adminCommand({split: fullName, find: find});
116 };
117 
118 sh.splitAt = function(fullName, middle) {
119     sh._checkFullName(fullName);
120     return sh._adminCommand({split: fullName, middle: middle});
121 };
122 
123 sh.moveChunk = function(fullName, find, to) {
124     sh._checkFullName(fullName);
125     return sh._adminCommand({moveChunk: fullName, find: find, to: to});
126 };
127 
128 sh.setBalancerState = function(isOn) {
129     if (isOn) {
130         return sh.startBalancer();
131     } else {
132         return sh.stopBalancer();
133     }
134 };
135 
136 sh.getBalancerState = function(configDB) {
137     if (configDB === undefined)
138         configDB = sh._getConfigDB();
139     var x = configDB.settings.findOne({_id: "balancer"});
140     if (x == null)
141         return true;
142     return !x.stopped;
143 };
144 
145 sh.isBalancerRunning = function(configDB) {
146     if (configDB === undefined)
147         configDB = sh._getConfigDB();
148     var x = configDB.locks.findOne({_id: "balancer"});
149     if (x == null) {
150         print("config.locks collection empty or missing. be sure you are connected to a mongos");
151         return false;
152     }
153     return x.state > 0;
154 };
155 
156 sh.getBalancerHost = function(configDB) {
157     if (configDB === undefined)
158         configDB = sh._getConfigDB();
159     var x = configDB.locks.findOne({_id: "balancer"});
160     if (x == null) {
161         print(
162             "config.locks collection does not contain balancer lock. be sure you are connected to a mongos");
163         return "";
164     }
165     return x.process.match(/[^:]+:[^:]+/)[0];
166 };
167 
168 sh.stopBalancer = function(timeoutMs, interval) {
169     timeoutMs = timeoutMs || 60000;
170 
171     var result = db.adminCommand({balancerStop: 1, maxTimeMS: timeoutMs});
172     if (result.code === ErrorCodes.CommandNotFound) {
173         // For backwards compatibility, use the legacy balancer stop method
174         result = sh._writeBalancerStateDeprecated(false);
175         sh.waitForBalancer(false, timeoutMs, interval);
176         return result;
177     }
178 
179     return assert.commandWorked(result);
180 };
181 
182 sh.startBalancer = function(timeoutMs, interval) {
183     timeoutMs = timeoutMs || 60000;
184 
185     var result = db.adminCommand({balancerStart: 1, maxTimeMS: timeoutMs});
186     if (result.code === ErrorCodes.CommandNotFound) {
187         // For backwards compatibility, use the legacy balancer start method
188         result = sh._writeBalancerStateDeprecated(true);
189         sh.waitForBalancer(true, timeoutMs, interval);
190         return result;
191     }
192 
193     return assert.commandWorked(result);
194 };
195 
196 sh.enableAutoSplit = function(configDB) {
197     if (configDB === undefined)
198         configDB = sh._getConfigDB();
199     return assert.writeOK(
200         configDB.settings.update({_id: 'autosplit'},
201                                  {$set: {enabled: true}},
202                                  {upsert: true, writeConcern: {w: 'majority', wtimeout: 30000}}));
203 };
204 
205 sh.disableAutoSplit = function(configDB) {
206     if (configDB === undefined)
207         configDB = sh._getConfigDB();
208     return assert.writeOK(
209         configDB.settings.update({_id: 'autosplit'},
210                                  {$set: {enabled: false}},
211                                  {upsert: true, writeConcern: {w: 'majority', wtimeout: 30000}}));
212 };
213 
214 sh.getShouldAutoSplit = function(configDB) {
215     if (configDB === undefined)
216         configDB = sh._getConfigDB();
217     var autosplit = configDB.settings.findOne({_id: 'autosplit'});
218     if (autosplit == null) {
219         print(
220             "No autosplit document found in config.settings collection. Be sure you are connected to a mongos");
221         return true;
222     }
223     return autosplit.enabled;
224 };
225 
226 sh._waitForDLock = function(lockId, onOrNot, timeout, interval) {
227     // Wait for balancer to be on or off
228     // Can also wait for particular balancer state
229     var state = onOrNot;
230     var configDB = sh._getConfigDB();
231 
232     var beginTS = undefined;
233     if (state == undefined) {
234         var currLock = configDB.locks.findOne({_id: lockId});
235         if (currLock != null)
236             beginTS = currLock.ts;
237     }
238 
239     var lockStateOk = function() {
240         var lock = configDB.locks.findOne({_id: lockId});
241 
242         if (state == false)
243             return !lock || lock.state == 0;
244         if (state == true)
245             return lock && lock.state == 2;
246         if (state == undefined)
247             return (beginTS == undefined && lock) ||
248                 (beginTS != undefined && (!lock || lock.ts + "" != beginTS + ""));
249         else
250             return lock && lock.state == state;
251     };
252 
253     assert.soon(
254         lockStateOk,
255         "Waited too long for lock " + lockId + " to " +
256             (state == true ? "lock" : (state == false ? "unlock" : "change to state " + state)),
257         timeout,
258         interval);
259 };
260 
261 sh.waitForPingChange = function(activePings, timeout, interval) {
262     var isPingChanged = function(activePing) {
263         var newPing = sh._getConfigDB().mongos.findOne({_id: activePing._id});
264         return !newPing || newPing.ping + "" != activePing.ping + "";
265     };
266 
267     // First wait for all active pings to change, so we're sure a settings reload
268     // happened
269 
270     // Timeout all pings on the same clock
271     var start = new Date();
272 
273     var remainingPings = [];
274     for (var i = 0; i < activePings.length; i++) {
275         var activePing = activePings[i];
276         print("Waiting for active host " + activePing._id +
277               " to recognize new settings... (ping : " + activePing.ping + ")");
278 
279         // Do a manual timeout here, avoid scary assert.soon errors
280         var timeout = timeout || 30000;
281         var interval = interval || 200;
282         while (isPingChanged(activePing) != true) {
283             if ((new Date()).getTime() - start.getTime() > timeout) {
284                 print("Waited for active ping to change for host " + activePing._id +
285                       ", a migration may be in progress or the host may be down.");
286                 remainingPings.push(activePing);
287                 break;
288             }
289             sleep(interval);
290         }
291     }
292 
293     return remainingPings;
294 };
295 
296 sh.waitForBalancer = function(onOrNot, timeout, interval) {
297     // If we're waiting for the balancer to turn on or switch state or go to a particular state
298     if (onOrNot) {
299         // Just wait for the balancer lock to change, can't ensure we'll ever see it actually locked
300         sh._waitForDLock("balancer", undefined, timeout, interval);
301     } else {
302         // Otherwise we need to wait until we're sure balancing stops
303         var activePings = [];
304         sh._getConfigDB().mongos.find().forEach(function(ping) {
305             if (!ping.waiting)
306                 activePings.push(ping);
307         });
308 
309         print("Waiting for active hosts...");
310         activePings = sh.waitForPingChange(activePings, 60 * 1000);
311 
312         // After 1min, we assume that all hosts with unchanged pings are either offline (this is
313         // enough time for a full errored balance round, if a network issue, which would reload
314         // settings) or balancing, which we wait for next. Legacy hosts we always have to wait for.
315         print("Waiting for the balancer lock...");
316 
317         // Wait for the balancer lock to become inactive. We can guess this is stale after 15 mins,
318         // but need to double-check manually.
319         try {
320             sh._waitForDLock("balancer", false, 15 * 60 * 1000);
321         } catch (e) {
322             print(
323                 "Balancer still may be active, you must manually verify this is not the case using the config.changelog collection.");
324             throw Error(e);
325         }
326 
327         print("Waiting again for active hosts after balancer is off...");
328 
329         // Wait a short time afterwards, to catch the host which was balancing earlier
330         activePings = sh.waitForPingChange(activePings, 5 * 1000);
331 
332         // Warn about all the stale host pings remaining
333         activePings.forEach(function(activePing) {
334             print("Warning : host " + activePing._id + " seems to have been offline since " +
335                   activePing.ping);
336         });
337     }
338 };
339 
340 sh.disableBalancing = function(coll) {
341     if (coll === undefined) {
342         throw Error("Must specify collection");
343     }
344     var dbase = db;
345     if (coll instanceof DBCollection) {
346         dbase = coll.getDB();
347     } else {
348         sh._checkMongos();
349     }
350 
351     return assert.writeOK(dbase.getSisterDB("config").collections.update(
352         {_id: coll + ""},
353         {$set: {"noBalance": true}},
354         {writeConcern: {w: 'majority', wtimeout: 60000}}));
355 };
356 
357 sh.enableBalancing = function(coll) {
358     if (coll === undefined) {
359         throw Error("Must specify collection");
360     }
361     var dbase = db;
362     if (coll instanceof DBCollection) {
363         dbase = coll.getDB();
364     } else {
365         sh._checkMongos();
366     }
367 
368     return assert.writeOK(dbase.getSisterDB("config").collections.update(
369         {_id: coll + ""},
370         {$set: {"noBalance": false}},
371         {writeConcern: {w: 'majority', wtimeout: 60000}}));
372 };
373 
374 /*
375  * Can call _lastMigration( coll ), _lastMigration( db ), _lastMigration( st ), _lastMigration(
376  * mongos )
377  */
378 sh._lastMigration = function(ns) {
379 
380     var coll = null;
381     var dbase = null;
382     var config = null;
383 
384     if (!ns) {
385         config = db.getSisterDB("config");
386     } else if (ns instanceof DBCollection) {
387         coll = ns;
388         config = coll.getDB().getSisterDB("config");
389     } else if (ns instanceof DB) {
390         dbase = ns;
391         config = dbase.getSisterDB("config");
392     } else if (ns instanceof ShardingTest) {
393         config = ns.s.getDB("config");
394     } else if (ns instanceof Mongo) {
395         config = ns.getDB("config");
396     } else {
397         // String namespace
398         ns = ns + "";
399         if (ns.indexOf(".") > 0) {
400             config = db.getSisterDB("config");
401             coll = db.getMongo().getCollection(ns);
402         } else {
403             config = db.getSisterDB("config");
404             dbase = db.getSisterDB(ns);
405         }
406     }
407 
408     var searchDoc = {what: /^moveChunk/};
409     if (coll)
410         searchDoc.ns = coll + "";
411     if (dbase)
412         searchDoc.ns = new RegExp("^" + dbase + "\\.");
413 
414     var cursor = config.changelog.find(searchDoc).sort({time: -1}).limit(1);
415     if (cursor.hasNext())
416         return cursor.next();
417     else
418         return null;
419 };
420 
421 sh.addShardTag = function(shard, tag) {
422     var result = sh.addShardToZone(shard, tag);
423     if (result.code != ErrorCodes.CommandNotFound) {
424         return result;
425     }
426 
427     var config = sh._getConfigDB();
428     if (config.shards.findOne({_id: shard}) == null) {
429         throw Error("can't find a shard with name: " + shard);
430     }
431     return assert.writeOK(config.shards.update(
432         {_id: shard}, {$addToSet: {tags: tag}}, {writeConcern: {w: 'majority', wtimeout: 60000}}));
433 };
434 
435 sh.removeShardTag = function(shard, tag) {
436     var result = sh.removeShardFromZone(shard, tag);
437     if (result.code != ErrorCodes.CommandNotFound) {
438         return result;
439     }
440 
441     var config = sh._getConfigDB();
442     if (config.shards.findOne({_id: shard}) == null) {
443         throw Error("can't find a shard with name: " + shard);
444     }
445     return assert.writeOK(config.shards.update(
446         {_id: shard}, {$pull: {tags: tag}}, {writeConcern: {w: 'majority', wtimeout: 60000}}));
447 };
448 
449 sh.addTagRange = function(ns, min, max, tag) {
450     var result = sh.updateZoneKeyRange(ns, min, max, tag);
451     if (result.code != ErrorCodes.CommandNotFound) {
452         return result;
453     }
454 
455     if (bsonWoCompare(min, max) == 0) {
456         throw new Error("min and max cannot be the same");
457     }
458 
459     var config = sh._getConfigDB();
460     return assert.writeOK(
461         config.tags.update({_id: {ns: ns, min: min}},
462                            {_id: {ns: ns, min: min}, ns: ns, min: min, max: max, tag: tag},
463                            {upsert: true, writeConcern: {w: 'majority', wtimeout: 60000}}));
464 };
465 
466 sh.removeTagRange = function(ns, min, max, tag) {
467     var result = sh.removeRangeFromZone(ns, min, max);
468     if (result.code != ErrorCodes.CommandNotFound) {
469         return result;
470     }
471 
472     var config = sh._getConfigDB();
473     // warn if the namespace does not exist, even dropped
474     if (config.collections.findOne({_id: ns}) == null) {
475         print("Warning: can't find the namespace: " + ns + " - collection likely never sharded");
476     }
477     // warn if the tag being removed is still in use
478     if (config.shards.findOne({tags: tag})) {
479         print("Warning: tag still in use by at least one shard");
480     }
481     // max and tag criteria not really needed, but including them avoids potentially unexpected
482     // behavior.
483     return assert.writeOK(config.tags.remove({_id: {ns: ns, min: min}, max: max, tag: tag},
484                                              {writeConcern: {w: 'majority', wtimeout: 60000}}));
485 };
486 
487 sh.addShardToZone = function(shardName, zoneName) {
488     return sh._getConfigDB().adminCommand({addShardToZone: shardName, zone: zoneName});
489 };
490 
491 sh.removeShardFromZone = function(shardName, zoneName) {
492     return sh._getConfigDB().adminCommand({removeShardFromZone: shardName, zone: zoneName});
493 };
494 
495 sh.updateZoneKeyRange = function(ns, min, max, zoneName) {
496     return sh._getConfigDB().adminCommand(
497         {updateZoneKeyRange: ns, min: min, max: max, zone: zoneName});
498 };
499 
500 sh.removeRangeFromZone = function(ns, min, max) {
501     return sh._getConfigDB().adminCommand({updateZoneKeyRange: ns, min: min, max: max, zone: null});
502 };
503 
504 sh.getBalancerLockDetails = function(configDB) {
505     if (configDB === undefined)
506         configDB = db.getSiblingDB('config');
507     var lock = configDB.locks.findOne({_id: 'balancer'});
508     if (lock == null) {
509         return null;
510     }
511     if (lock.state == 0) {
512         return null;
513     }
514     return lock;
515 };
516 
517 sh.getBalancerWindow = function(configDB) {
518     if (configDB === undefined)
519         configDB = db.getSiblingDB('config');
520     var settings = configDB.settings.findOne({_id: 'balancer'});
521     if (settings == null) {
522         return null;
523     }
524     if (settings.hasOwnProperty("activeWindow")) {
525         return settings.activeWindow;
526     }
527     return null;
528 };
529 
530 sh.getActiveMigrations = function(configDB) {
531     if (configDB === undefined)
532         configDB = db.getSiblingDB('config');
533     var activeLocks = configDB.locks.find({_id: {$ne: "balancer"}, state: {$eq: 2}});
534     var result = [];
535     if (activeLocks != null) {
536         activeLocks.forEach(function(lock) {
537             result.push({_id: lock._id, when: lock.when});
538         });
539     }
540     return result;
541 };
542 
543 sh.getRecentFailedRounds = function(configDB) {
544     if (configDB === undefined)
545         configDB = db.getSiblingDB('config');
546     var balErrs = configDB.actionlog.find({what: "balancer.round"}).sort({time: -1}).limit(5);
547     var result = {count: 0, lastErr: "", lastTime: " "};
548     if (balErrs != null) {
549         balErrs.forEach(function(r) {
550             if (r.details.errorOccured) {
551                 result.count += 1;
552                 result.lastErr = r.details.errmsg;
553                 result.lastTime = r.time;
554             }
555         });
556     }
557     return result;
558 };
559 
560 /**
561  * Returns a summary of chunk migrations that was completed either successfully or not
562  * since yesterday. The format is an array of 2 arrays, where the first array contains
563  * the successful cases, and the second array contains the failure cases.
564  */
565 sh.getRecentMigrations = function(configDB) {
566     if (configDB === undefined)
567         configDB = sh._getConfigDB();
568     var yesterday = new Date(new Date() - 24 * 60 * 60 * 1000);
569 
570     // Successful migrations.
571     var result = configDB.changelog
572                      .aggregate([
573                          {
574                            $match: {
575                                time: {$gt: yesterday},
576                                what: "moveChunk.from",
577                                'details.errmsg': {$exists: false},
578                                'details.note': 'success'
579                            }
580                          },
581                          {$group: {_id: {msg: "$details.errmsg"}, count: {$sum: 1}}},
582                          {$project: {_id: {$ifNull: ["$_id.msg", "Success"]}, count: "$count"}}
583                      ])
584                      .toArray();
585 
586     // Failed migrations.
587     result = result.concat(
588         configDB.changelog
589             .aggregate([
590                 {
591                   $match: {
592                       time: {$gt: yesterday},
593                       what: "moveChunk.from",
594                       $or: [
595                           {'details.errmsg': {$exists: true}},
596                           {'details.note': {$ne: 'success'}}
597                       ]
598                   }
599                 },
600                 {
601                   $group: {
602                       _id: {msg: "$details.errmsg", from: "$details.from", to: "$details.to"},
603                       count: {$sum: 1}
604                   }
605                 },
606                 {
607                   $project: {
608                       _id: {$ifNull: ['$_id.msg', 'aborted']},
609                       from: "$_id.from",
610                       to: "$_id.to",
611                       count: "$count"
612                   }
613                 }
614             ])
615             .toArray());
616 
617     return result;
618 };
619 
620 function printShardingStatus(configDB, verbose) {
621     // configDB is a DB object that contains the sharding metadata of interest.
622     // Defaults to the db named "config" on the current connection.
623     if (configDB === undefined)
624         configDB = db.getSisterDB('config');
625 
626     var version = configDB.getCollection("version").findOne();
627     if (version == null) {
628         print(
629             "printShardingStatus: this db does not have sharding enabled. be sure you are connecting to a mongos from the shell and not to a mongod.");
630         return;
631     }
632 
633     var raw = "";
634     var output = function(s) {
635         raw += s + "\n";
636     };
637     output("--- Sharding Status --- ");
638     output("  sharding version: " + tojson(configDB.getCollection("version").findOne()));
639 
640     output("  shards:");
641     configDB.shards.find().sort({_id: 1}).forEach(function(z) {
642         output("\t" + tojsononeline(z));
643     });
644 
645     // (most recently) active mongoses
646     var mongosActiveThresholdMs = 60000;
647     var mostRecentMongos = configDB.mongos.find().sort({ping: -1}).limit(1);
648     var mostRecentMongosTime = null;
649     var mongosAdjective = "most recently active";
650     if (mostRecentMongos.hasNext()) {
651         mostRecentMongosTime = mostRecentMongos.next().ping;
652         // Mongoses older than the threshold are the most recent, but cannot be
653         // considered "active" mongoses. (This is more likely to be an old(er)
654         // configdb dump, or all the mongoses have been stopped.)
655         if (mostRecentMongosTime.getTime() >= Date.now() - mongosActiveThresholdMs) {
656             mongosAdjective = "active";
657         }
658     }
659 
660     output("  " + mongosAdjective + " mongoses:");
661     if (mostRecentMongosTime === null) {
662         output("\tnone");
663     } else {
664         var recentMongosQuery = {
665             ping: {
666                 $gt: (function() {
667                     var d = mostRecentMongosTime;
668                     d.setTime(d.getTime() - mongosActiveThresholdMs);
669                     return d;
670                 })()
671             }
672         };
673 
674         if (verbose) {
675             configDB.mongos.find(recentMongosQuery).sort({ping: -1}).forEach(function(z) {
676                 output("\t" + tojsononeline(z));
677             });
678         } else {
679             configDB.mongos
680                 .aggregate([
681                     {$match: recentMongosQuery},
682                     {$group: {_id: "$mongoVersion", num: {$sum: 1}}},
683                     {$sort: {num: -1}}
684                 ])
685                 .forEach(function(z) {
686                     output("\t" + tojson(z._id) + " : " + z.num);
687                 });
688         }
689     }
690 
691     output(" autosplit:");
692 
693     // Is autosplit currently enabled
694     output("\tCurrently enabled: " + (sh.getShouldAutoSplit(configDB) ? "yes" : "no"));
695 
696     output("  balancer:");
697 
698     // Is the balancer currently enabled
699     output("\tCurrently enabled:  " + (sh.getBalancerState(configDB) ? "yes" : "no"));
700 
701     // Is the balancer currently active
702     output("\tCurrently running:  " + (sh.isBalancerRunning(configDB) ? "yes" : "no"));
703 
704     // Output details of the current balancer round
705     var balLock = sh.getBalancerLockDetails(configDB);
706     if (balLock) {
707         output("\t\tBalancer lock taken at " + balLock.when + " by " + balLock.who);
708     }
709 
710     // Output the balancer window
711     var balSettings = sh.getBalancerWindow(configDB);
712     if (balSettings) {
713         output("\t\tBalancer active window is set between " + balSettings.start + " and " +
714                balSettings.stop + " server local time");
715     }
716 
717     // Output the list of active migrations
718     var activeMigrations = sh.getActiveMigrations(configDB);
719     if (activeMigrations.length > 0) {
720         output("\tCollections with active migrations: ");
721         activeMigrations.forEach(function(migration) {
722             output("\t\t" + migration._id + " started at " + migration.when);
723         });
724     }
725 
726     // Actionlog and version checking only works on 2.7 and greater
727     var versionHasActionlog = false;
728     var metaDataVersion = configDB.getCollection("version").findOne().currentVersion;
729     if (metaDataVersion > 5) {
730         versionHasActionlog = true;
731     }
732     if (metaDataVersion == 5) {
733         var verArray = db.serverBuildInfo().versionArray;
734         if (verArray[0] == 2 && verArray[1] > 6) {
735             versionHasActionlog = true;
736         }
737     }
738 
739     if (versionHasActionlog) {
740         // Review config.actionlog for errors
741         var actionReport = sh.getRecentFailedRounds(configDB);
742         // Always print the number of failed rounds
743         output("\tFailed balancer rounds in last 5 attempts:  " + actionReport.count);
744 
745         // Only print the errors if there are any
746         if (actionReport.count > 0) {
747             output("\tLast reported error:  " + actionReport.lastErr);
748             output("\tTime of Reported error:  " + actionReport.lastTime);
749         }
750 
751         output("\tMigration Results for the last 24 hours: ");
752         var migrations = sh.getRecentMigrations(configDB);
753         if (migrations.length > 0) {
754             migrations.forEach(function(x) {
755                 if (x._id === "Success") {
756                     output("\t\t" + x.count + " : " + x._id);
757                 } else {
758                     output("\t\t" + x.count + " : Failed with error '" + x._id + "', from " +
759                            x.from + " to " + x.to);
760                 }
761             });
762         } else {
763             output("\t\tNo recent migrations");
764         }
765     }
766 
767     output("  databases:");
768     configDB.databases.find().sort({name: 1}).forEach(function(db) {
769         var truthy = function(value) {
770             return !!value;
771         };
772         var nonBooleanNote = function(name, value) {
773             // If the given value is not a boolean, return a string of the
774             // form " (<name>: <value>)", where <value> is converted to JSON.
775             var t = typeof(value);
776             var s = "";
777             if (t != "boolean" && t != "undefined") {
778                 s = " (" + name + ": " + tojson(value) + ")";
779             }
780             return s;
781         };
782 
783         output("\t" + tojsononeline(db, "", true));
784 
785         if (db.partitioned) {
786             configDB.collections.find({_id: new RegExp("^" + RegExp.escape(db._id) + "\\.")})
787                 .sort({_id: 1})
788                 .forEach(function(coll) {
789                     if (!coll.dropped) {
790                         output("\t\t" + coll._id);
791                         output("\t\t\tshard key: " + tojson(coll.key));
792                         output("\t\t\tunique: " + truthy(coll.unique) +
793                                nonBooleanNote("unique", coll.unique));
794                         output("\t\t\tbalancing: " + !truthy(coll.noBalance) +
795                                nonBooleanNote("noBalance", coll.noBalance));
796                         output("\t\t\tchunks:");
797 
798                         res = configDB.chunks
799                                   .aggregate({$match: {ns: coll._id}},
800                                              {$group: {_id: "$shard", cnt: {$sum: 1}}},
801                                              {$project: {_id: 0, shard: "$_id", nChunks: "$cnt"}},
802                                              {$sort: {shard: 1}})
803                                   .toArray();
804                         var totalChunks = 0;
805                         res.forEach(function(z) {
806                             totalChunks += z.nChunks;
807                             output("\t\t\t\t" + z.shard + "\t" + z.nChunks);
808                         });
809 
810                         if (totalChunks < 20 || verbose) {
811                             configDB.chunks.find({"ns": coll._id})
812                                 .sort({min: 1})
813                                 .forEach(function(chunk) {
814                                     output("\t\t\t" + tojson(chunk.min) + " -->> " +
815                                            tojson(chunk.max) + " on : " + chunk.shard + " " +
816                                            tojson(chunk.lastmod) + " " +
817                                            (chunk.jumbo ? "jumbo " : ""));
818                                 });
819                         } else {
820                             output(
821                                 "\t\t\ttoo many chunks to print, use verbose if you want to force print");
822                         }
823 
824                         configDB.tags.find({ns: coll._id}).sort({min: 1}).forEach(function(tag) {
825                             output("\t\t\t tag: " + tag.tag + "  " + tojson(tag.min) + " -->> " +
826                                    tojson(tag.max));
827                         });
828                     }
829                 });
830         }
831     });
832 
833     print(raw);
834 }
835 
836 function printShardingSizes(configDB) {
837     // configDB is a DB object that contains the sharding metadata of interest.
838     // Defaults to the db named "config" on the current connection.
839     if (configDB === undefined)
840         configDB = db.getSisterDB('config');
841 
842     var version = configDB.getCollection("version").findOne();
843     if (version == null) {
844         print("printShardingSizes : not a shard db!");
845         return;
846     }
847 
848     var raw = "";
849     var output = function(s) {
850         raw += s + "\n";
851     };
852     output("--- Sharding Status --- ");
853     output("  sharding version: " + tojson(configDB.getCollection("version").findOne()));
854 
855     output("  shards:");
856     var shards = {};
857     configDB.shards.find().forEach(function(z) {
858         shards[z._id] = new Mongo(z.host);
859         output("      " + tojson(z));
860     });
861 
862     var saveDB = db;
863     output("  databases:");
864     configDB.databases.find().sort({name: 1}).forEach(function(db) {
865         output("\t" + tojson(db, "", true));
866 
867         if (db.partitioned) {
868             configDB.collections.find({_id: new RegExp("^" + RegExp.escape(db._id) + "\.")})
869                 .sort({_id: 1})
870                 .forEach(function(coll) {
871                     output("\t\t" + coll._id + " chunks:");
872                     configDB.chunks.find({"ns": coll._id}).sort({min: 1}).forEach(function(chunk) {
873                         var mydb = shards[chunk.shard].getDB(db._id);
874                         var out = mydb.runCommand({
875                             dataSize: coll._id,
876                             keyPattern: coll.key,
877                             min: chunk.min,
878                             max: chunk.max
879                         });
880                         delete out.millis;
881                         delete out.ok;
882 
883                         output("\t\t\t" + tojson(chunk.min) + " -->> " + tojson(chunk.max) +
884                                " on : " + chunk.shard + " " + tojson(out));
885 
886                     });
887                 });
888         }
889     });
890 
891     print(raw);
892 }
893