1// Copyright (C) MongoDB, Inc. 2014-present. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7package status 8 9import ( 10 "fmt" 11 "regexp" 12 "sort" 13 "time" 14 15 "github.com/mongodb/mongo-tools/common/text" 16 "github.com/mongodb/mongo-tools/common/util" 17) 18 19type ReaderConfig struct { 20 HumanReadable bool 21 TimeFormat string 22} 23 24type LockUsage struct { 25 Namespace string 26 Reads int64 27 Writes int64 28} 29 30type lockUsages []LockUsage 31 32func (slice lockUsages) Len() int { 33 return len(slice) 34} 35 36func (slice lockUsages) Less(i, j int) bool { 37 return slice[i].Reads+slice[i].Writes < slice[j].Reads+slice[j].Writes 38} 39 40func (slice lockUsages) Swap(i, j int) { 41 slice[i], slice[j] = slice[j], slice[i] 42} 43 44func formatBits(should bool, amt int64) string { 45 if should { 46 return text.FormatBits(amt) 47 } 48 return fmt.Sprintf("%v", amt) 49} 50 51func formatMegabyteAmount(should bool, amt int64) string { 52 if should { 53 return text.FormatMegabyteAmount(amt) 54 } 55 return fmt.Sprintf("%v", amt*1024*1024) 56} 57 58func numberToInt64(num interface{}) (int64, bool) { 59 switch n := num.(type) { 60 case int64: 61 return n, true 62 case int32: 63 return int64(n), true 64 case int: 65 return int64(n), true 66 } 67 return 0, false 68} 69 70func percentageInt64(value, outOf int64) float64 { 71 if value == 0 || outOf == 0 { 72 return 0 73 } 74 return 100 * (float64(value) / float64(outOf)) 75} 76 77func averageInt64(value, outOf int64) int64 { 78 if value == 0 || outOf == 0 { 79 return 0 80 } 81 return value / outOf 82} 83 84func parseLocks(stat *ServerStatus) map[string]LockUsage { 85 returnVal := map[string]LockUsage{} 86 for namespace, lockInfo := range stat.Locks { 87 returnVal[namespace] = LockUsage{ 88 namespace, 89 lockInfo.TimeLockedMicros.Read + lockInfo.TimeLockedMicros.ReadLower, 90 lockInfo.TimeLockedMicros.Write + lockInfo.TimeLockedMicros.WriteLower, 91 } 92 } 93 return returnVal 94} 95 96func computeLockDiffs(prevLocks, curLocks map[string]LockUsage) []LockUsage { 97 lockUsages := lockUsages(make([]LockUsage, 0, len(curLocks))) 98 for namespace, curUsage := range curLocks { 99 prevUsage, hasKey := prevLocks[namespace] 100 if !hasKey { 101 // This namespace didn't appear in the previous batch of lock info, 102 // so we can't compute a diff for it - skip it. 103 continue 104 } 105 // Calculate diff of lock usage for this namespace and add to the list 106 lockUsages = append(lockUsages, 107 LockUsage{ 108 namespace, 109 curUsage.Reads - prevUsage.Reads, 110 curUsage.Writes - prevUsage.Writes, 111 }) 112 } 113 // Sort the array in order of least to most locked 114 sort.Sort(lockUsages) 115 return lockUsages 116} 117 118func diff(newVal, oldVal int64, sampleSecs float64) int64 { 119 return int64(float64(newVal-oldVal) / sampleSecs) 120} 121 122func diffOp(newStat, oldStat *ServerStatus, f func(*OpcountStats) int64, both bool) string { 123 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 124 var opcount int64 125 var opcountRepl int64 126 if newStat.Opcounters != nil && oldStat.Opcounters != nil { 127 opcount = diff(f(newStat.Opcounters), f(oldStat.Opcounters), sampleSecs) 128 } 129 if newStat.OpcountersRepl != nil && oldStat.OpcountersRepl != nil { 130 opcountRepl = diff(f(newStat.OpcountersRepl), f(oldStat.OpcountersRepl), sampleSecs) 131 } 132 switch { 133 case both || opcount > 0 && opcountRepl > 0: 134 return fmt.Sprintf("%v|%v", opcount, opcountRepl) 135 case opcount > 0: 136 return fmt.Sprintf("%v", opcount) 137 case opcountRepl > 0: 138 return fmt.Sprintf("*%v", opcountRepl) 139 default: 140 return "*0" 141 } 142} 143 144func getStorageEngine(stat *ServerStatus) string { 145 val := "mmapv1" 146 if stat.StorageEngine != nil && stat.StorageEngine["name"] != "" { 147 val = stat.StorageEngine["name"] 148 } 149 return val 150} 151 152// mongosProcessRE matches mongos not followed by any slashes before next whitespace 153var mongosProcessRE = regexp.MustCompile(`^.*\bmongos\b[^\\\/]*(\s.*)?$`) 154 155func IsMongos(stat *ServerStatus) bool { 156 return stat.ShardCursorType != nil || mongosProcessRE.MatchString(stat.Process) 157} 158 159func HasLocks(stat *ServerStatus) bool { 160 return ReadLockedDB(nil, stat, stat) != "" 161} 162 163func IsReplSet(stat *ServerStatus) (res bool) { 164 if stat.Repl != nil { 165 isReplSet, ok := stat.Repl.IsReplicaSet.(bool) 166 res = (ok && isReplSet) || len(stat.Repl.SetName) > 0 167 } 168 return 169} 170 171func IsMMAP(stat *ServerStatus) bool { 172 return getStorageEngine(stat) == "mmapv1" 173} 174 175func IsWT(stat *ServerStatus) bool { 176 return getStorageEngine(stat) == "wiredTiger" 177} 178 179func ReadHost(_ *ReaderConfig, newStat, _ *ServerStatus) string { 180 return newStat.Host 181} 182 183func ReadStorageEngine(_ *ReaderConfig, newStat, _ *ServerStatus) string { 184 return getStorageEngine(newStat) 185} 186 187func ReadInsert(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 188 return diffOp(newStat, oldStat, func(o *OpcountStats) int64 { 189 return o.Insert 190 }, false) 191} 192 193func ReadQuery(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 194 return diffOp(newStat, oldStat, func(s *OpcountStats) int64 { 195 return s.Query 196 }, false) 197} 198 199func ReadUpdate(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 200 return diffOp(newStat, oldStat, func(s *OpcountStats) int64 { 201 return s.Update 202 }, false) 203} 204 205func ReadDelete(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 206 return diffOp(newStat, oldStat, func(s *OpcountStats) int64 { 207 return s.Delete 208 }, false) 209} 210 211func ReadGetMore(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 212 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 213 return fmt.Sprintf("%d", diff(newStat.Opcounters.GetMore, oldStat.Opcounters.GetMore, sampleSecs)) 214} 215 216func ReadCommand(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 217 return diffOp(newStat, oldStat, func(s *OpcountStats) int64 { 218 return s.Command 219 }, true) 220} 221 222func ReadDirty(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 223 if newStat.WiredTiger != nil { 224 bytes := float64(newStat.WiredTiger.Cache.TrackedDirtyBytes) 225 max := float64(newStat.WiredTiger.Cache.MaxBytesConfigured) 226 if max != 0 { 227 val = fmt.Sprintf("%.1f", 100*bytes/max) 228 if c.HumanReadable { 229 val = val + "%" 230 } 231 } 232 } 233 return 234} 235 236func ReadUsed(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 237 if newStat.WiredTiger != nil { 238 bytes := float64(newStat.WiredTiger.Cache.CurrentCachedBytes) 239 max := float64(newStat.WiredTiger.Cache.MaxBytesConfigured) 240 if max != 0 { 241 val = fmt.Sprintf("%.1f", 100*bytes/max) 242 if c.HumanReadable { 243 val = val + "%" 244 } 245 } 246 } 247 return 248} 249 250func ReadFlushes(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 251 var val int64 252 if newStat.WiredTiger != nil && oldStat.WiredTiger != nil { 253 val = newStat.WiredTiger.Transaction.TransCheckpoints - oldStat.WiredTiger.Transaction.TransCheckpoints 254 } else if newStat.BackgroundFlushing != nil && oldStat.BackgroundFlushing != nil { 255 val = newStat.BackgroundFlushing.Flushes - oldStat.BackgroundFlushing.Flushes 256 } 257 return fmt.Sprintf("%d", val) 258} 259 260func ReadMapped(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 261 if util.IsTruthy(newStat.Mem.Supported) && IsMongos(newStat) { 262 val = formatMegabyteAmount(c.HumanReadable, newStat.Mem.Mapped) 263 } 264 return 265} 266 267func ReadVSize(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 268 if util.IsTruthy(newStat.Mem.Supported) { 269 val = formatMegabyteAmount(c.HumanReadable, newStat.Mem.Virtual) 270 } 271 return 272} 273 274func ReadRes(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 275 if util.IsTruthy(newStat.Mem.Supported) { 276 val = formatMegabyteAmount(c.HumanReadable, newStat.Mem.Resident) 277 } 278 return 279} 280 281func ReadNonMapped(c *ReaderConfig, newStat, _ *ServerStatus) (val string) { 282 if util.IsTruthy(newStat.Mem.Supported) && !IsMongos(newStat) { 283 val = formatMegabyteAmount(c.HumanReadable, newStat.Mem.Virtual-newStat.Mem.Mapped) 284 } 285 return 286} 287 288func ReadFaults(_ *ReaderConfig, newStat, oldStat *ServerStatus) string { 289 if !IsMMAP(newStat) { 290 return "n/a" 291 } 292 var val int64 = -1 293 if oldStat.ExtraInfo != nil && newStat.ExtraInfo != nil && 294 oldStat.ExtraInfo.PageFaults != nil && newStat.ExtraInfo.PageFaults != nil { 295 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 296 val = diff(*(newStat.ExtraInfo.PageFaults), *(oldStat.ExtraInfo.PageFaults), sampleSecs) 297 } 298 return fmt.Sprintf("%d", val) 299} 300 301func ReadLRW(_ *ReaderConfig, newStat, oldStat *ServerStatus) (val string) { 302 if !IsMongos(newStat) && newStat.Locks != nil && oldStat.Locks != nil { 303 global, ok := oldStat.Locks["Global"] 304 if ok && global.AcquireCount != nil { 305 newColl, inNew := newStat.Locks["Collection"] 306 oldColl, inOld := oldStat.Locks["Collection"] 307 if inNew && inOld && newColl.AcquireWaitCount != nil && oldColl.AcquireWaitCount != nil { 308 rWait := newColl.AcquireWaitCount.Read - oldColl.AcquireWaitCount.Read 309 wWait := newColl.AcquireWaitCount.Write - oldColl.AcquireWaitCount.Write 310 rTotal := newColl.AcquireCount.Read - oldColl.AcquireCount.Read 311 wTotal := newColl.AcquireCount.Write - oldColl.AcquireCount.Write 312 r := percentageInt64(rWait, rTotal) 313 w := percentageInt64(wWait, wTotal) 314 val = fmt.Sprintf("%.1f%%|%.1f%%", r, w) 315 } 316 } 317 } 318 return 319} 320 321func ReadLRWT(_ *ReaderConfig, newStat, oldStat *ServerStatus) (val string) { 322 if !IsMongos(newStat) && newStat.Locks != nil && oldStat.Locks != nil { 323 global, ok := oldStat.Locks["Global"] 324 if ok && global.AcquireCount != nil { 325 newColl, inNew := newStat.Locks["Collection"] 326 oldColl, inOld := oldStat.Locks["Collection"] 327 if inNew && inOld && newColl.AcquireWaitCount != nil && oldColl.AcquireWaitCount != nil { 328 rWait := newColl.AcquireWaitCount.Read - oldColl.AcquireWaitCount.Read 329 wWait := newColl.AcquireWaitCount.Write - oldColl.AcquireWaitCount.Write 330 rAcquire := newColl.TimeAcquiringMicros.Read - oldColl.TimeAcquiringMicros.Read 331 wAcquire := newColl.TimeAcquiringMicros.Write - oldColl.TimeAcquiringMicros.Write 332 r := averageInt64(rAcquire, rWait) 333 w := averageInt64(wAcquire, wWait) 334 val = fmt.Sprintf("%v|%v", r, w) 335 } 336 } 337 } 338 return 339} 340 341func ReadLockedDB(_ *ReaderConfig, newStat, oldStat *ServerStatus) (val string) { 342 if !IsMongos(newStat) && newStat.Locks != nil && oldStat.Locks != nil { 343 global, ok := oldStat.Locks["Global"] 344 if !ok || global.AcquireCount == nil { 345 prevLocks := parseLocks(oldStat) 346 curLocks := parseLocks(newStat) 347 lockdiffs := computeLockDiffs(prevLocks, curLocks) 348 db := "" 349 var percentage string 350 if len(lockdiffs) == 0 { 351 if newStat.GlobalLock != nil { 352 percentage = fmt.Sprintf("%.1f", percentageInt64(newStat.GlobalLock.LockTime, newStat.GlobalLock.TotalTime)) 353 } 354 } else { 355 // Get the entry with the highest lock 356 highestLocked := lockdiffs[len(lockdiffs)-1] 357 timeDiffMillis := newStat.UptimeMillis - oldStat.UptimeMillis 358 lockToReport := highestLocked.Writes 359 360 // if the highest locked namespace is not '.' 361 if highestLocked.Namespace != "." { 362 for _, namespaceLockInfo := range lockdiffs { 363 if namespaceLockInfo.Namespace == "." { 364 lockToReport += namespaceLockInfo.Writes 365 } 366 } 367 } 368 369 // lock data is in microseconds and uptime is in milliseconds - so 370 // divide by 1000 so that the units match 371 lockToReport /= 1000 372 373 db = highestLocked.Namespace 374 percentage = fmt.Sprintf("%.1f", percentageInt64(lockToReport, timeDiffMillis)) 375 } 376 if percentage != "" { 377 val = fmt.Sprintf("%s:%s%%", db, percentage) 378 } 379 } 380 } 381 return 382} 383 384func ReadQRW(_ *ReaderConfig, newStat, _ *ServerStatus) string { 385 var qr int64 386 var qw int64 387 gl := newStat.GlobalLock 388 if gl != nil && gl.CurrentQueue != nil { 389 // If we have wiredtiger stats, use those instead 390 if newStat.WiredTiger != nil { 391 qr = gl.CurrentQueue.Readers + gl.ActiveClients.Readers - newStat.WiredTiger.Concurrent.Read.Out 392 qw = gl.CurrentQueue.Writers + gl.ActiveClients.Writers - newStat.WiredTiger.Concurrent.Write.Out 393 if qr < 0 { 394 qr = 0 395 } 396 if qw < 0 { 397 qw = 0 398 } 399 } else { 400 qr = gl.CurrentQueue.Readers 401 qw = gl.CurrentQueue.Writers 402 } 403 } 404 return fmt.Sprintf("%v|%v", qr, qw) 405} 406 407func ReadARW(_ *ReaderConfig, newStat, _ *ServerStatus) string { 408 var ar int64 409 var aw int64 410 if gl := newStat.GlobalLock; gl != nil { 411 if newStat.WiredTiger != nil { 412 ar = newStat.WiredTiger.Concurrent.Read.Out 413 aw = newStat.WiredTiger.Concurrent.Write.Out 414 } else if newStat.GlobalLock.ActiveClients != nil { 415 ar = gl.ActiveClients.Readers 416 aw = gl.ActiveClients.Writers 417 } 418 } 419 return fmt.Sprintf("%v|%v", ar, aw) 420} 421 422func ReadNetIn(c *ReaderConfig, newStat, oldStat *ServerStatus) string { 423 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 424 val := diff(newStat.Network.BytesIn, oldStat.Network.BytesIn, sampleSecs) 425 return formatBits(c.HumanReadable, val) 426} 427 428func ReadNetOut(c *ReaderConfig, newStat, oldStat *ServerStatus) string { 429 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 430 val := diff(newStat.Network.BytesOut, oldStat.Network.BytesOut, sampleSecs) 431 return formatBits(c.HumanReadable, val) 432} 433 434func ReadConn(_ *ReaderConfig, newStat, _ *ServerStatus) string { 435 return fmt.Sprintf("%d", newStat.Connections.Current) 436} 437 438func ReadSet(_ *ReaderConfig, newStat, _ *ServerStatus) (name string) { 439 if newStat.Repl != nil { 440 name = newStat.Repl.SetName 441 } 442 return 443} 444 445func ReadRepl(_ *ReaderConfig, newStat, _ *ServerStatus) string { 446 switch { 447 case newStat.Repl == nil && IsMongos(newStat): 448 return "RTR" 449 case newStat.Repl == nil: 450 return "" 451 case util.IsTruthy(newStat.Repl.IsMaster): 452 return "PRI" 453 case util.IsTruthy(newStat.Repl.Secondary): 454 return "SEC" 455 case util.IsTruthy(newStat.Repl.IsReplicaSet): 456 return "REC" 457 case util.IsTruthy(newStat.Repl.ArbiterOnly): 458 return "ARB" 459 case util.SliceContains(newStat.Repl.Passives, newStat.Repl.Me): 460 return "PSV" 461 default: 462 if !IsReplSet(newStat) { 463 return "UNK" 464 } 465 return "SLV" 466 } 467} 468 469func ReadTime(c *ReaderConfig, newStat, _ *ServerStatus) string { 470 if c.TimeFormat != "" { 471 return newStat.SampleTime.Format(c.TimeFormat) 472 } 473 if c.HumanReadable { 474 return newStat.SampleTime.Format(time.StampMilli) 475 } 476 return newStat.SampleTime.Format(time.RFC3339) 477} 478 479func ReadStatField(field string, stat *ServerStatus) string { 480 val, ok := stat.Flattened[field] 481 if ok { 482 return fmt.Sprintf("%v", val) 483 } 484 return "INVALID" 485} 486 487func ReadStatDiff(field string, newStat, oldStat *ServerStatus) string { 488 new, validNew := newStat.Flattened[field] 489 old, validOld := oldStat.Flattened[field] 490 if validNew && validOld { 491 new, validNew := numberToInt64(new) 492 old, validOld := numberToInt64(old) 493 if validNew && validOld { 494 return fmt.Sprintf("%v", new-old) 495 } 496 } 497 return "INVALID" 498} 499 500func ReadStatRate(field string, newStat, oldStat *ServerStatus) string { 501 sampleSecs := float64(newStat.SampleTime.Sub(oldStat.SampleTime).Seconds()) 502 new, validNew := newStat.Flattened[field] 503 old, validOld := oldStat.Flattened[field] 504 if validNew && validOld { 505 new, validNew := numberToInt64(new) 506 old, validOld := numberToInt64(old) 507 if validNew && validOld { 508 return fmt.Sprintf("%v", diff(new, old, sampleSecs)) 509 } 510 } 511 return "INVALID" 512} 513 514var literalRE = regexp.MustCompile(`^(.*?)(\.(\w+)\(\))?$`) 515 516func InterpretField(field string, newStat, oldStat *ServerStatus) string { 517 match := literalRE.FindStringSubmatch(field) 518 if len(match) == 4 { 519 switch match[3] { 520 case "diff": 521 return ReadStatDiff(match[1], newStat, oldStat) 522 case "rate": 523 return ReadStatRate(match[1], newStat, oldStat) 524 } 525 } 526 return ReadStatField(field, newStat) 527} 528