Snapshot of the state of a FoundationDB cluster
Inheritance: MetricsBase
		private static void ShowProcessesScreen(FdbSystemStatus status, HistoryMetric current, bool repaint)
		{
			const int BARSZ = 15;

			const int COL0 = 1;
			const int COL1 = COL0 + 18;
			const int COL2 = COL1 + 12;
			const int COL3 = COL2 + 15;
			const int COL4 = COL3 + 11 + BARSZ;
			const int COL5 = COL4 + 8;
			const int COL6 = COL5 + 12 + BARSZ;
			const int COL7 = COL6 + 10 + BARSZ;

			if (repaint)
			{
				Console.Title = "fdbtop - Processes";
				RepaintTopBar();

				Console.ForegroundColor = ConsoleColor.DarkCyan;
				WriteAt(COL0, 5, "Address (port)");
				WriteAt(COL1, 5, "Network in / out (MB/s)");
				WriteAt(COL3, 5, "CPU (%core)");
				WriteAt(COL4, 5, "Memory Free / Total (GB)");
				WriteAt(COL6, 5, "HDD (%busy)");
				WriteAt(COL7, 5, "Roles");

#if DEBUG
				Console.ForegroundColor = ConsoleColor.DarkGray;
				WriteAt(COL0, 6, "0 - - - - - -");
				WriteAt(COL1, 6, "1 - - - - - -");
				WriteAt(COL2, 6, "2 - - - - - -");
				WriteAt(COL3, 6, "3 - - - - - -");
				WriteAt(COL4, 6, "4 - - - - - -");
				WriteAt(COL5, 6, "5 - - - - - -");
				WriteAt(COL6, 6, "6 - - - - - -");
				WriteAt(COL7, 6, "7 - - - - - -");
#endif
			}

			UpdateTopBar(status, current);

			if (status.Cluster.Machines.Count == 0)
			{
				//TODO display error message?
				return;
			}

			var maxVersion = status.Cluster.Processes.Values.Max(p => p.Version);

			int y = 7;
			foreach(var machine in status.Cluster.Machines.Values.OrderBy(x => x.Address, StringComparer.Ordinal))
			{
				var procs = status.Cluster.Processes.Values
					.Where(p => p.MachineId == machine.Id)
					.OrderBy(p => p.Address, StringComparer.Ordinal)
					.ToList();

				var map = new RoleMap();
				foreach(var proc in procs)
				{
					foreach(var role in proc.Roles)
					{
						map.Add(role.Value);
					}
				}

				Console.ForegroundColor = ConsoleColor.DarkGray;
				WriteAt(1, y,
					"{0,15} | {0,8} in {0,8} out | {0,6}% {0,15} | {0,5} / {0,5} GB {0,15} | {0,22} | {0,11} |",
					""
				);

				Console.ForegroundColor = ConsoleColor.White;
				//"{0,-15} | net {2,8:N3} in {3,8:N3} out | cpu {4,5:N1}% | mem {5,5:N1} / {7,5:N1} GB {8,-20} | hdd {9,5:N1}% {10,-20}",
				WriteAt(COL0, y, machine.Address);
				WriteAt(COL1, y, "{0,8:N3}", MegaBytes(machine.Network.MegabitsReceived.Hz * 125000));
				WriteAt(COL2, y, "{0,8:N3}", MegaBytes(machine.Network.MegabitsSent.Hz * 125000));
				WriteAt(COL3, y, "{0,6:N1}", machine.Cpu.LogicalCoreUtilization * 100);
				WriteAt(COL4, y, "{0,5:N1}", GigaBytes(machine.Memory.CommittedBytes));
				WriteAt(COL5, y, "{0,5:N1}", GigaBytes(machine.Memory.TotalBytes));
				//WriteAt(COL6, y, "{0,5:N1}", totalDiskBusy * 100);
				WriteAt(COL7, y, "{0,11}", map);

				Console.ForegroundColor = machine.Cpu.LogicalCoreUtilization >= 0.9 ? ConsoleColor.Red : ConsoleColor.Green;
				WriteAt(COL3 + 8, y, "{0,-15}", new string('|', Bar(machine.Cpu.LogicalCoreUtilization, 1, BARSZ))); // 1 = all the (logical) cores

				Console.ForegroundColor = machine.Memory.CommittedBytes >= 0.95 * machine.Memory.TotalBytes ? ConsoleColor.Red : ConsoleColor.Green;
				WriteAt(COL5 + 9, y, "{0,-15}", new string('|', Bar(machine.Memory.CommittedBytes, machine.Memory.TotalBytes, BARSZ)));

				//Console.ForegroundColor = totalDiskBusy >= 0.95 ? ConsoleColor.Red : ConsoleColor.Green;
				//WriteAt(COL6 + 7, y, "{0,-15}", new string('|', Bar(totalDiskBusy, 1, BARSZ)));

				++y;

				//TODO: use a set to map procs ot machines? Where(..) will be slow if there are a lot of machines x processes
				foreach (var proc in procs)
				{
					int p = proc.Address.IndexOf(':');
					string port = p >= 0 ? proc.Address.Substring(p + 1) : proc.Address;

					map = new RoleMap();
					foreach (var role in proc.Roles)
					{
						map.Add(role.Value);
					}
					Console.ForegroundColor = ConsoleColor.DarkGray;
					WriteAt(1, y,
						"{0,7} | {0,5} | {0,8} in {0,8} out | {0,6}% {0,15} | {0,5} / {0,5} GB {0,15} | {0,5}% {0,15} | {0,11} |",
						""
					);

					Console.ForegroundColor = proc.Version != maxVersion ? ConsoleColor.DarkCyan : ConsoleColor.Gray;
					WriteAt(1 +  10, y, "{0,5}", proc.Version);

					Console.ForegroundColor = proc.Excluded ? ConsoleColor.DarkRed : ConsoleColor.Gray;
					WriteAt(COL0, y, "{0,7}", port);
					Console.ForegroundColor = ConsoleColor.Gray;
					WriteAt(COL1, y, "{0,8:N3}", MegaBytes(proc.Network.MegabitsReceived.Hz * 125000));
					WriteAt(COL2, y, "{0,8:N3}", MegaBytes(proc.Network.MegabitsSent.Hz * 125000));
					WriteAt(COL3, y, "{0,6:N1}", proc.Cpu.UsageCores * 100);
					WriteAt(COL4, y, "{0,5:N1}", GigaBytes(proc.Memory.UsedBytes));
					WriteAt(COL5, y, "{0,5:N1}", GigaBytes(proc.Memory.AvailableBytes));
					WriteAt(COL6, y, "{0,5:N1}", Math.Min(proc.Disk.Busy * 100, 100)); // 1 == 1 core, but a process can go a little bit higher
					WriteAt(COL7, y, "{0,11}", map);

					Console.ForegroundColor = proc.Cpu.UsageCores >= 0.95 ? ConsoleColor.DarkRed : ConsoleColor.DarkGreen;
					WriteAt(COL3 + 8, y, "{0,-15}", new string('|', Bar(proc.Cpu.UsageCores, 1, BARSZ)));

					Console.ForegroundColor = proc.Memory.UsedBytes >= 0.95 * proc.Memory.AvailableBytes ? ConsoleColor.DarkRed : ConsoleColor.DarkGreen;
					WriteAt(COL5 + 9, y, "{0,-15}", new string('|', Bar(proc.Memory.UsedBytes, proc.Memory.AvailableBytes, BARSZ)));

					Console.ForegroundColor = proc.Disk.Busy >= 0.95 ? ConsoleColor.DarkRed : ConsoleColor.DarkGreen;
					WriteAt(COL6 + 7, y, "{0,-15}", new string('|', Bar(proc.Disk.Busy, 1, BARSZ)));

					++y;
				}
				++y;
			}

		}
		private static void ShowMetricsScreen(FdbSystemStatus status, HistoryMetric current, bool repaint)
		{
			const int COL0 = 1;
			const int COL1 = COL0 + 11;
			const int COL2 = COL1 + 12 + MAX_RW_WIDTH;
			const int COL3 = COL2 + 12 + MAX_RW_WIDTH;

			if (repaint)
			{
				Console.Title = "fdbtop - Metrics";
				RepaintTopBar();

				Console.ForegroundColor = ConsoleColor.DarkCyan;
				WriteAt(COL0, 5, "Elapsed");
				WriteAt(COL1, 5, "   Reads (Hz)");
				WriteAt(COL2, 5, "  Writes (Hz)");
				WriteAt(COL3, 5, "Disk Speed (MB/s)");
			}

			UpdateTopBar(status, current);

			double maxRead = GetMax(History, (m) => m.ReadsPerSecond);
			double maxWrite = GetMax(History, (m) => m.WritesPerSecond);
			double maxSpeed = GetMax(History, (m) => m.WrittenBytesPerSecond);
			double scaleRead = GetMaxScale(maxRead);
			double scaleWrite = GetMaxScale(maxWrite);
			double scaleSpeed = GetMaxScale(maxSpeed);

			Console.ForegroundColor = ConsoleColor.DarkGreen;
			WriteAt(COL1 + 14, 5, "{0,35:N0}", maxRead);
			WriteAt(COL2 + 14, 5, "{0,35:N0}", maxWrite);
			WriteAt(COL3 + 18, 5, "{0,13:N3}", MegaBytes(maxSpeed));

			int y = 7 + History.Count - 1;
			foreach (var metric in History)
			{
				Console.ForegroundColor = ConsoleColor.DarkGray;
				WriteAt(1, y,
					"{0} | {1,8} {1,40} | {1,8} {1,40} | {1,10} {1,20} |",
					TimeSpan.FromSeconds(Math.Round(metric.LocalTime.TotalSeconds, MidpointRounding.AwayFromZero)),
					""
				);

				if (metric.Available)
				{
					bool isMaxRead = maxRead > 0 && metric.ReadsPerSecond == maxRead;
					bool isMaxWrite = maxWrite > 0 && metric.WritesPerSecond == maxWrite;
					bool isMaxSpeed = maxSpeed > 0 && metric.WrittenBytesPerSecond == maxSpeed;

					Console.ForegroundColor = isMaxRead ? ConsoleColor.Cyan : FrenquencyColor(metric.ReadsPerSecond);
					WriteAt(COL1, y, "{0,8:N0}", metric.ReadsPerSecond);
					Console.ForegroundColor = isMaxWrite ? ConsoleColor.Cyan : FrenquencyColor(metric.WritesPerSecond);
					WriteAt(COL2, y, "{0,8:N0}", metric.WritesPerSecond);
					Console.ForegroundColor = isMaxSpeed ? ConsoleColor.Cyan : DiskSpeedColor(metric.WrittenBytesPerSecond);
					WriteAt(COL3, y, "{0,10:N3}", MegaBytes(metric.WrittenBytesPerSecond));

					Console.ForegroundColor = metric.ReadsPerSecond > 10 ? ConsoleColor.Green : ConsoleColor.DarkCyan;
					WriteAt(COL1 + 9, y, metric.ReadsPerSecond == 0 ? "-" : new string(GetBarChar(metric.ReadsPerSecond), Bar(metric.ReadsPerSecond, scaleRead, MAX_RW_WIDTH)));
					Console.ForegroundColor = metric.WritesPerSecond > 10 ? ConsoleColor.Green : ConsoleColor.DarkCyan;
					WriteAt(COL2 + 9, y, metric.WritesPerSecond == 0 ? "-" : new string(GetBarChar(metric.WritesPerSecond), Bar(metric.WritesPerSecond, scaleWrite, MAX_RW_WIDTH)));
					Console.ForegroundColor = metric.WrittenBytesPerSecond > 1000 ? ConsoleColor.Green : ConsoleColor.DarkCyan;
					WriteAt(COL3 + 11, y, metric.WrittenBytesPerSecond == 0 ? "-" : new string(GetBarChar(metric.WrittenBytesPerSecond / 1000), Bar(metric.WrittenBytesPerSecond, scaleSpeed, MAX_WS_WIDTH)));
				}
				else
				{
					Console.ForegroundColor = ConsoleColor.DarkRed;
					WriteAt(COL1, y, "{0,8}", "x");
					WriteAt(COL2, y, "{0,8}", "x");
					WriteAt(COL3, y, "{0,8}", "x");
				}
				--y;
			}

			Console.ForegroundColor = ConsoleColor.Gray;
		}
		private static void ShowTransactionScreen(FdbSystemStatus status, HistoryMetric current, bool repaint)
		{
			const int COL0 = 1;
			const int COL1 = COL0 + 11;
			const int COL2 = COL1 + 12 + MAX_RW_WIDTH;
			const int COL3 = COL2 + 12 + MAX_RW_WIDTH;

			if (repaint)
			{
				Console.Title = "fdbtop - Transactions";
				RepaintTopBar();

				Console.ForegroundColor = ConsoleColor.DarkCyan;
				WriteAt(COL0, 5, "Elapsed");
				WriteAt(COL1, 5, "Started (tps)");
				WriteAt(COL2, 5, "Committed (tps)");
				WriteAt(COL3, 5, "Conflicted (tps)");
			}

			UpdateTopBar(status, current);

			double maxStarted = GetMax(History, (m) => m.TransStarted);
			double maxCommitted = GetMax(History, (m) => m.TransCommitted);
			double maxConflicted = GetMax(History, (m) => m.TransConflicted);
			double scaleStarted = GetMaxScale(maxStarted);
			double scaleComitted = GetMaxScale(maxCommitted);
			double scaleConflicted = GetMaxScale(maxConflicted);

			Console.ForegroundColor = ConsoleColor.DarkGreen;
			WriteAt(COL1 + 14, 5, "{0,35:N0}", maxStarted);
			WriteAt(COL2 + 16, 5, "{0,33:N0}", maxCommitted);
			WriteAt(COL3 + 16, 5, "{0,15:N0}", maxConflicted);

			int y = 7 + History.Count - 1;
			foreach (var metric in History)
			{
				Console.ForegroundColor = ConsoleColor.DarkGray;
				WriteAt(1, y,
					"{0} | {1,8} {1,40} | {1,8} {1,40} | {1,10} {1,20} |",
					TimeSpan.FromSeconds(Math.Round(metric.LocalTime.TotalSeconds, MidpointRounding.AwayFromZero)),
					""
				);

				if (metric.Available)
				{
					bool isMaxRead = maxStarted > 0 && metric.LatencyCommit == maxStarted;
					bool isMaxWrite = maxCommitted > 0 && metric.LatencyRead == maxCommitted;
					bool isMaxSpeed = maxConflicted > 0 && metric.LatencyStart == maxConflicted;

					Console.ForegroundColor = isMaxRead ? ConsoleColor.Cyan : FrenquencyColor(metric.TransStarted);
					WriteAt(COL1, y, "{0,8:N0}", metric.TransStarted);
					Console.ForegroundColor = isMaxWrite ? ConsoleColor.Cyan : FrenquencyColor(metric.TransCommitted);
					WriteAt(COL2, y, "{0,8:N0}", metric.TransCommitted);
					Console.ForegroundColor = isMaxSpeed ? ConsoleColor.Cyan : FrenquencyColor(metric.TransConflicted);
					WriteAt(COL3, y, "{0,8:N1}", metric.TransConflicted);

					Console.ForegroundColor = metric.TransStarted > 10 ? ConsoleColor.Green : ConsoleColor.DarkGreen;
					WriteAt(COL1 + 9, y, metric.TransStarted == 0 ? "-" : new string('|', Bar(metric.TransStarted, scaleStarted, MAX_RW_WIDTH)));
					Console.ForegroundColor = metric.TransCommitted > 10 ? ConsoleColor.Green : ConsoleColor.DarkGreen;
					WriteAt(COL2 + 9, y, metric.TransCommitted == 0 ? "-" : new string('|', Bar(metric.TransCommitted, scaleComitted, MAX_RW_WIDTH)));
					Console.ForegroundColor = metric.TransConflicted > 1000 ? ConsoleColor.Red : metric.TransConflicted > 10 ? ConsoleColor.Green : ConsoleColor.DarkGreen;
					WriteAt(COL3 + 9, y, metric.TransConflicted == 0 ? "-" : new string('|', Bar(metric.TransConflicted, scaleConflicted, MAX_WS_WIDTH)));
				}
				else
				{
					Console.ForegroundColor = ConsoleColor.DarkRed;
					WriteAt(COL1, y, "{0,8}", "x");
					WriteAt(COL2, y, "{0,8}", "x");
					WriteAt(COL3, y, "{0,8}", "x");
				}
				--y;
			}

			Console.ForegroundColor = ConsoleColor.Gray;
		}
		private static void UpdateTopBar(FdbSystemStatus status, HistoryMetric current)
		{
			Console.ForegroundColor = ConsoleColor.White;
			WriteAt(TOP_COL0 + 9, 1, "{0,8:N0}", current.ReadsPerSecond);
			WriteAt(TOP_COL0 + 9, 2, "{0,8:N0}", current.WritesPerSecond);
			WriteAt(TOP_COL0 + 9, 3, "{0,8:N2}", MegaBytes(current.WrittenBytesPerSecond));

			WriteAt(TOP_COL1 + 11, 1, "{0,10:N1}", MegaBytes(status.Cluster.Data.TotalKVUsedBytes));
			WriteAt(TOP_COL1 + 11, 2, "{0,10:N1}", MegaBytes(status.Cluster.Data.TotalDiskUsedBytes));
			WriteAt(TOP_COL1 + 8, 3, "{0,5:N0}", status.Cluster.Data.PartitionsCount);
			WriteAt(TOP_COL1 + 15, 3, "{0,6:N1}", MegaBytes(status.Cluster.Data.AveragePartitionSizeBytes));

			var serverTime = Epoch.AddSeconds(current.Timestamp);
			var clientTime = Epoch.AddSeconds(status.Client.Timestamp);
			WriteAt(TOP_COL2 + 14, 1, "{0,19}", serverTime.ToString("u"));
			if (Math.Abs((serverTime - clientTime).TotalSeconds) >= 20) Console.ForegroundColor = ConsoleColor.Red;
			WriteAt(TOP_COL2 + 14, 2, "{0,19}", clientTime.ToString("u"));
			Console.ForegroundColor = ConsoleColor.White;
			WriteAt(TOP_COL2 + 14, 3, "{0:N0}", current.ReadVersion);

			Console.ForegroundColor = ConsoleColor.White;
			WriteAt(TOP_COL3 + 12, 1, "{0,-10}", status.Cluster.Configuration.CoordinatorsCount);
			WriteAt(TOP_COL3 + 12, 2, "{0,-10}", status.Cluster.Configuration.StorageEngine);
			WriteAt(TOP_COL3 + 12, 3, "{0,-10}", status.Cluster.Configuration.RedundancyFactor);

			if (!status.Client.DatabaseAvailable)
			{
				Console.ForegroundColor = ConsoleColor.Red;
				WriteAt(TOP_COL4 + 7, 1, "UNAVAILABLE");
			}
			else
			{
				Console.ForegroundColor = ConsoleColor.Green;
				WriteAt(TOP_COL4 + 7, 1, "Available  ");
			}
			Console.ForegroundColor = ConsoleColor.White;
			WriteAt(TOP_COL4 + 7, 2, "{0,-40}", status.Cluster.Data.StateName);
			WriteAt(TOP_COL4 + 7, 3, "{0,-40}", status.Cluster.Qos.PerformanceLimitedBy.Name);

			//Console.ForegroundColor = ConsoleColor.Gray;
			//var msgs = status.Cluster.Messages.Concat(status.Client.Messages).ToArray();
			//for (int i = 0; i < 4; i++)
			//{
			//	string msg = msgs.Length > i ? msgs[i].Name : "";
			//	WriteAt(118, i + 1, "{0,-40}", msg.Length < 50 ? msg : msg.Substring(0, 40));
			//}

		}