func GetAuthVersion(session *mgo.Session) (int, error) { results := bson.M{} err := session.Run( bson.D{ {"getParameter", 1}, {"authSchemaVersion", 1}, }, &results, ) if err != nil { return 0, err } if util.IsTruthy(results["ok"]) { version, ok := results["authSchemaVersion"].(int) if !ok { // very unlikely this will ever happen return 0, fmt.Errorf( "getParameter command returned non-numeric result: %v", results["authSchemaVersion"]) } return version, nil } else { errMessage, ok := results["errmsg"].(string) if !ok { // errmsg will always be returned on error unless the command API changes return 0, fmt.Errorf( "getParameter command returned non-string errmsg: %v", results["authSchemaVersion"]) } // as a necessary hack, if the error message takes a certain form, // we can infer version 1. This is because early versions of mongodb // had no concept of an "auth schema version", so asking for the // authSchemaVersion value will return a "no option found" or "no such cmd" if errMessage == "no option found to get" || strings.HasPrefix(errMessage, "no such cmd") { return 1, nil } return 0, fmt.Errorf(errMessage) } }
// NewStatLine constructs a StatLine object from two ServerStatus objects. func NewStatLine(oldStat, newStat ServerStatus, key string, all bool, sampleSecs int64) *StatLine { returnVal := &StatLine{ Key: key, Host: newStat.Host, Mapped: -1, Virtual: -1, Resident: -1, NonMapped: -1, Faults: -1, } // set the storage engine appropriately if newStat.StorageEngine != nil && newStat.StorageEngine["name"] != "" { returnVal.StorageEngine = newStat.StorageEngine["name"] } else { returnVal.StorageEngine = "mmapv1" } if newStat.Opcounters != nil && oldStat.Opcounters != nil { returnVal.Insert = diff(newStat.Opcounters.Insert, oldStat.Opcounters.Insert, sampleSecs) returnVal.Query = diff(newStat.Opcounters.Query, oldStat.Opcounters.Query, sampleSecs) returnVal.Update = diff(newStat.Opcounters.Update, oldStat.Opcounters.Update, sampleSecs) returnVal.Delete = diff(newStat.Opcounters.Delete, oldStat.Opcounters.Delete, sampleSecs) returnVal.GetMore = diff(newStat.Opcounters.GetMore, oldStat.Opcounters.GetMore, sampleSecs) returnVal.Command = diff(newStat.Opcounters.Command, oldStat.Opcounters.Command, sampleSecs) } if newStat.OpcountersRepl != nil && oldStat.OpcountersRepl != nil { returnVal.InsertR = diff(newStat.OpcountersRepl.Insert, oldStat.OpcountersRepl.Insert, sampleSecs) returnVal.QueryR = diff(newStat.OpcountersRepl.Query, oldStat.OpcountersRepl.Query, sampleSecs) returnVal.UpdateR = diff(newStat.OpcountersRepl.Update, oldStat.OpcountersRepl.Update, sampleSecs) returnVal.DeleteR = diff(newStat.OpcountersRepl.Delete, oldStat.OpcountersRepl.Delete, sampleSecs) returnVal.GetMoreR = diff(newStat.OpcountersRepl.GetMore, oldStat.OpcountersRepl.GetMore, sampleSecs) returnVal.CommandR = diff(newStat.OpcountersRepl.Command, oldStat.OpcountersRepl.Command, sampleSecs) } returnVal.CacheDirtyPercent = -1 returnVal.CacheUsedPercent = -1 if newStat.WiredTiger != nil && oldStat.WiredTiger != nil { returnVal.Flushes = newStat.WiredTiger.Transaction.TransCheckpoints - oldStat.WiredTiger.Transaction.TransCheckpoints returnVal.CacheDirtyPercent = float64(newStat.WiredTiger.Cache.TrackedDirtyBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured) returnVal.CacheUsedPercent = float64(newStat.WiredTiger.Cache.CurrentCachedBytes) / float64(newStat.WiredTiger.Cache.MaxBytesConfigured) } else if newStat.BackgroundFlushing != nil && oldStat.BackgroundFlushing != nil { returnVal.Flushes = newStat.BackgroundFlushing.Flushes - oldStat.BackgroundFlushing.Flushes } returnVal.Time = newStat.SampleTime returnVal.IsMongos = (newStat.ShardCursorType != nil || strings.HasPrefix(newStat.Process, MongosProcess)) if util.IsTruthy(oldStat.Mem.Supported) { if !returnVal.IsMongos { returnVal.Mapped = newStat.Mem.Mapped } returnVal.Virtual = newStat.Mem.Virtual returnVal.Resident = newStat.Mem.Resident if !returnVal.IsMongos && all { returnVal.NonMapped = newStat.Mem.Virtual - newStat.Mem.Mapped } } if newStat.Repl != nil { setName, isReplSet := newStat.Repl.SetName.(string) if isReplSet { returnVal.ReplSetName = setName } if util.IsTruthy(newStat.Repl.IsMaster) { returnVal.NodeType = "PRI" } else if util.IsTruthy(newStat.Repl.Secondary) { returnVal.NodeType = "SEC" } else if util.IsTruthy(newStat.Repl.IsReplicaSet) { returnVal.NodeType = "REC" } else if util.IsTruthy(newStat.Repl.ArbiterOnly) { returnVal.NodeType = "ARB" } else if util.SliceContains(newStat.Repl.Passives, newStat.Repl.Me) { returnVal.NodeType = "PSV" } else if isReplSet { returnVal.NodeType = "UNK" } else { returnVal.NodeType = "SLV" } } else if returnVal.IsMongos { returnVal.NodeType = "RTR" } if oldStat.ExtraInfo != nil && newStat.ExtraInfo != nil && oldStat.ExtraInfo.PageFaults != nil && newStat.ExtraInfo.PageFaults != nil { returnVal.Faults = diff(*(newStat.ExtraInfo.PageFaults), *(oldStat.ExtraInfo.PageFaults), sampleSecs) } if !returnVal.IsMongos && oldStat.Locks != nil && oldStat.Locks != nil { globalCheck, hasGlobal := oldStat.Locks["Global"] if hasGlobal && globalCheck.AcquireCount != nil { // This appears to be a 3.0+ server so the data in these fields do *not* refer to // actual namespaces and thus we can't compute lock %. returnVal.HighestLocked = nil // Check if it's a 3.0+ MMAP server so we can still compute collection locks collectionCheck, hasCollection := oldStat.Locks["Collection"] if hasCollection && collectionCheck.AcquireWaitCount != nil { readWaitCountDiff := newStat.Locks["Collection"].AcquireWaitCount.Read - oldStat.Locks["Collection"].AcquireWaitCount.Read readTotalCountDiff := newStat.Locks["Collection"].AcquireCount.Read - oldStat.Locks["Collection"].AcquireCount.Read writeWaitCountDiff := newStat.Locks["Collection"].AcquireWaitCount.Write - oldStat.Locks["Collection"].AcquireWaitCount.Write writeTotalCountDiff := newStat.Locks["Collection"].AcquireCount.Write - oldStat.Locks["Collection"].AcquireCount.Write readAcquireTimeDiff := newStat.Locks["Collection"].TimeAcquiringMicros.Read - oldStat.Locks["Collection"].TimeAcquiringMicros.Read writeAcquireTimeDiff := newStat.Locks["Collection"].TimeAcquiringMicros.Write - oldStat.Locks["Collection"].TimeAcquiringMicros.Write returnVal.CollectionLocks = &CollectionLockStatus{ ReadAcquireWaitsPercentage: percentageInt64(readWaitCountDiff, readTotalCountDiff), WriteAcquireWaitsPercentage: percentageInt64(writeWaitCountDiff, writeTotalCountDiff), ReadAcquireTimeMicros: averageInt64(readAcquireTimeDiff, readWaitCountDiff), WriteAcquireTimeMicros: averageInt64(writeAcquireTimeDiff, writeWaitCountDiff), } } } else { prevLocks := parseLocks(oldStat) curLocks := parseLocks(newStat) lockdiffs := computeLockDiffs(prevLocks, curLocks) if len(lockdiffs) == 0 { if newStat.GlobalLock != nil { returnVal.HighestLocked = &LockStatus{ DBName: "", Percentage: percentageInt64(newStat.GlobalLock.LockTime, newStat.GlobalLock.TotalTime), Global: true, } } } else { // Get the entry with the highest lock highestLocked := lockdiffs[len(lockdiffs)-1] var timeDiffMillis int64 timeDiffMillis = newStat.UptimeMillis - oldStat.UptimeMillis lockToReport := highestLocked.Writes // if the highest locked namespace is not '.' if highestLocked.Namespace != "." { for _, namespaceLockInfo := range lockdiffs { if namespaceLockInfo.Namespace == "." { lockToReport += namespaceLockInfo.Writes } } } // lock data is in microseconds and uptime is in milliseconds - so // divide by 1000 so that they units match lockToReport /= 1000 returnVal.HighestLocked = &LockStatus{ DBName: highestLocked.Namespace, Percentage: percentageInt64(lockToReport, timeDiffMillis), Global: false, } } } } else { returnVal.HighestLocked = nil } if newStat.GlobalLock != nil { hasWT := (newStat.WiredTiger != nil && oldStat.WiredTiger != nil) //If we have wiredtiger stats, use those instead if newStat.GlobalLock.CurrentQueue != nil { if hasWT { returnVal.QueuedReaders = newStat.GlobalLock.CurrentQueue.Readers + newStat.GlobalLock.ActiveClients.Readers - newStat.WiredTiger.Concurrent.Read.Out returnVal.QueuedWriters = newStat.GlobalLock.CurrentQueue.Writers + newStat.GlobalLock.ActiveClients.Writers - newStat.WiredTiger.Concurrent.Write.Out if returnVal.QueuedReaders < 0 { returnVal.QueuedReaders = 0 } if returnVal.QueuedWriters < 0 { returnVal.QueuedWriters = 0 } } else { returnVal.QueuedReaders = newStat.GlobalLock.CurrentQueue.Readers returnVal.QueuedWriters = newStat.GlobalLock.CurrentQueue.Writers } } if hasWT { returnVal.ActiveReaders = newStat.WiredTiger.Concurrent.Read.Out returnVal.ActiveWriters = newStat.WiredTiger.Concurrent.Write.Out } else if newStat.GlobalLock.ActiveClients != nil { returnVal.ActiveReaders = newStat.GlobalLock.ActiveClients.Readers returnVal.ActiveWriters = newStat.GlobalLock.ActiveClients.Writers } } if oldStat.Network != nil && newStat.Network != nil { returnVal.NetIn = diff(newStat.Network.BytesIn, oldStat.Network.BytesIn, sampleSecs) returnVal.NetOut = diff(newStat.Network.BytesOut, oldStat.Network.BytesOut, sampleSecs) } if newStat.Connections != nil { returnVal.NumConnections = newStat.Connections.Current } return returnVal }
//NewStatLine constructs a StatLine object from two ServerStatus objects. func NewStatLine(oldStat, newStat ServerStatus, all bool) *StatLine { returnVal := &StatLine{ Host: newStat.Host, Mapped: -1, Virtual: -1, Resident: -1, NonMapped: -1, Faults: -1, } if newStat.Opcounters != nil && oldStat.Opcounters != nil { returnVal.Insert = newStat.Opcounters.Insert - oldStat.Opcounters.Insert returnVal.Query = newStat.Opcounters.Query - oldStat.Opcounters.Query returnVal.Update = newStat.Opcounters.Update - oldStat.Opcounters.Update returnVal.Delete = newStat.Opcounters.Delete - oldStat.Opcounters.Delete returnVal.GetMore = newStat.Opcounters.GetMore - oldStat.Opcounters.GetMore returnVal.Command = newStat.Opcounters.Command - oldStat.Opcounters.Command } if newStat.BackgroundFlushing != nil && oldStat.BackgroundFlushing != nil { returnVal.Flushes = newStat.BackgroundFlushing.Flushes - oldStat.BackgroundFlushing.Flushes } returnVal.Time = newStat.SampleTime returnVal.IsMongos = (newStat.ShardCursorType != nil || newStat.Process == MongosProcess) if util.IsTruthy(oldStat.Mem.Supported) { if !returnVal.IsMongos { returnVal.Mapped = newStat.Mem.Mapped } returnVal.Virtual = newStat.Mem.Virtual returnVal.Resident = newStat.Mem.Resident if !returnVal.IsMongos && all { returnVal.NonMapped = newStat.Mem.Virtual - newStat.Mem.Mapped } } if newStat.Repl != nil { setName, isReplSet := newStat.Repl.SetName.(string) if isReplSet { returnVal.ReplSetName = setName } if util.IsTruthy(newStat.Repl.IsMaster) { returnVal.NodeType = "PRI" } else if util.IsTruthy(newStat.Repl.Secondary) { returnVal.NodeType = "SEC" } else if util.IsTruthy(newStat.Repl.IsReplicaSet) { returnVal.NodeType = "REC" } else if util.IsTruthy(newStat.Repl.ArbiterOnly) { returnVal.NodeType = "ARB" } else if util.SliceContains(newStat.Repl.Me, newStat.Repl.Passives) { returnVal.NodeType = "PSV" } else if isReplSet { returnVal.NodeType = "UNK" } else { returnVal.NodeType = "SLV" } } else if returnVal.IsMongos { returnVal.NodeType = "RTR" } if oldStat.ExtraInfo != nil && newStat.ExtraInfo != nil && oldStat.ExtraInfo.PageFaults != nil && newStat.ExtraInfo.PageFaults != nil { returnVal.Faults = *(newStat.ExtraInfo.PageFaults) - *(oldStat.ExtraInfo.PageFaults) } if !returnVal.IsMongos { prevLocks := parseLocks(oldStat) curLocks := parseLocks(newStat) lockdiffs := computeLockDiffs(prevLocks, curLocks) if len(lockdiffs) == 0 { if newStat.GlobalLock != nil { returnVal.HighestLocked = &LockStatus{ DBName: "", Percentage: percentageInt64(newStat.GlobalLock.LockTime, newStat.GlobalLock.TotalTime), Global: true, } } } if len(lockdiffs) > 0 { //Get the entry with the highest lock highestLocked := lockdiffs[len(lockdiffs)-1] //TODO use server uptime since the previous sampling? //var timeDiffMillis int64 //timeDiffMillis = newStat.UptimeMillis - stat.UptimeMillis lockToReport := highestLocked.Writes //if the highest locked namespace is not '.' if highestLocked.Namespace != "." { for _, namespaceLockInfo := range lockdiffs { if namespaceLockInfo.Namespace == "." { lockToReport += namespaceLockInfo.Writes } } } //lock data is in microseconds and uptime is in milliseconds - so //divide by 1000 so that they units match lockToReport /= 1000 returnVal.HighestLocked = &LockStatus{ DBName: highestLocked.Namespace, Percentage: percentageInt64(lockToReport, 1000), Global: false, } } } else { returnVal.HighestLocked = nil } if newStat.GlobalLock != nil { if newStat.GlobalLock.CurrentQueue != nil { returnVal.QueuedReaders = newStat.GlobalLock.CurrentQueue.Readers returnVal.QueuedWriters = newStat.GlobalLock.CurrentQueue.Writers } if newStat.GlobalLock.ActiveClients != nil { returnVal.ActiveReaders = newStat.GlobalLock.ActiveClients.Readers returnVal.ActiveWriters = newStat.GlobalLock.ActiveClients.Writers } } if oldStat.Network != nil && newStat.Network != nil { returnVal.NetIn = newStat.Network.BytesIn - oldStat.Network.BytesIn returnVal.NetOut = newStat.Network.BytesOut - oldStat.Network.BytesOut } if newStat.Connections != nil { returnVal.NumConnections = newStat.Connections.Current } return returnVal }
// constructWCObject takes in a write concern and attempts to construct an // mgo.Safe object from it. It returns an error if it is unable to parse the // string or if a parsed write concern field value is invalid. func constructWCObject(writeConcern string) (sessionSafety *mgo.Safe, err error) { sessionSafety = &mgo.Safe{} defer func() { // If the user passes a w value of 0, we set the session to use the // unacknowledged write concern but only if journal commit acknowledgment, // is not required. If commit acknowledgment is required, it prevails, // and the server will require that mongod acknowledge the write operation if sessionSafety.WMode == "" && sessionSafety.W == 0 && !sessionSafety.J { sessionSafety = nil } }() jsonWriteConcern := map[string]interface{}{} if err = json.Unmarshal([]byte(writeConcern), &jsonWriteConcern); err != nil { // if the writeConcern string can not be unmarshaled into JSON, this // allows a default to the old behavior wherein the entire argument // passed in is assigned to the 'w' field - thus allowing users pass // a write concern that looks like: "majority", 0, "4", etc. wValue, err := strconv.Atoi(writeConcern) if err != nil { sessionSafety.WMode = writeConcern } else { sessionSafety.W = wValue if wValue < 0 { return sessionSafety, fmt.Errorf("invalid '%v' argument: %v", w, wValue) } } return sessionSafety, nil } if jVal, ok := jsonWriteConcern[j]; ok && util.IsTruthy(jVal) { sessionSafety.J = true } if fsyncVal, ok := jsonWriteConcern[fSync]; ok && util.IsTruthy(fsyncVal) { sessionSafety.FSync = true } if wtimeout, ok := jsonWriteConcern[wTimeout]; ok { wtimeoutValue, err := util.ToInt(wtimeout) if err != nil { return sessionSafety, fmt.Errorf("invalid '%v' argument: %v", wTimeout, wtimeout) } sessionSafety.WTimeout = wtimeoutValue } if wInterface, ok := jsonWriteConcern[w]; ok { wValue, err := util.ToInt(wInterface) if err != nil { // if the argument is neither a string nor int, error out wStrVal, ok := wInterface.(string) if !ok { return sessionSafety, fmt.Errorf("invalid '%v' argument: %v", w, wInterface) } sessionSafety.WMode = wStrVal } else { sessionSafety.W = wValue if wValue < 0 { return sessionSafety, fmt.Errorf("invalid '%v' argument: %v", w, wValue) } } } return sessionSafety, nil }