Ejemplo n.º 1
0
        private static async Task MainAsync(string[] args, CancellationToken cancel)
        {
            #region Options Parsing...

            string clusterFile = null;
            var    partition   = FdbPath.Root;
            bool   showHelp    = false;
            int    timeout     = 30;
            int    maxRetries  = 10;
            string execCommand = null;

            var opts = new OptionSet()
            {
                {
                    "c|C|connfile=",
                    "The path of a file containing the connection string for the FoundationDB cluster.",
                    v => clusterFile = v
                },
                {
                    "p|partition=",
                    "The name of the database partition to open.",
                    v => partition = FdbPath.Parse(v.Trim())
                },
                {
                    "t|timeout=",
                    "Default timeout (in seconds) for failed transactions.",
                    (int v) => timeout = v
                },
                {
                    "r|retries=",
                    "Default max retry count for failed transactions.",
                    (int v) => maxRetries = v
                },
                {
                    "exec=",
                    "Execute this command, and exits immediately.",
                    v => execCommand = v
                },
                {
                    "h|help",
                    "Show this help and exit.",
                    v => showHelp = v != null
                }
            };

            var extra = opts.Parse(args);

            if (showHelp)
            {
                //TODO!
                opts.WriteOptionDescriptions(Console.Out);
                return;
            }

            string startCommand = null;
            if (!string.IsNullOrEmpty(execCommand))
            {
                startCommand = execCommand;
            }
            else if (extra.Count > 0)
            {             // the remainder of the command line will be the first command to execute
                startCommand = String.Join(" ", extra);
            }

            #endregion

            bool stop = false;
            Db = null;
            try
            {
                var cnxOptions = new FdbConnectionOptions
                {
                    ClusterFile = clusterFile,
                    Root        = partition
                };
                Db = await ChangeDatabase(cnxOptions, cancel);

                Db.DefaultTimeout    = Math.Max(0, timeout) * 1000;
                Db.DefaultRetryLimit = Math.Max(0, maxRetries);

                StdOut("Using API v" + Fdb.ApiVersion + " (max " + Fdb.GetMaxApiVersion() + ")", ConsoleColor.Gray);
                StdOut("Cluster file: " + (clusterFile ?? "<default>"), ConsoleColor.Gray);
                StdOut("");
                StdOut("FoundationDB Shell menu:");
                StdOut("\tcd\tChange the current directory");
                StdOut("\tdir\tList the sub-directories the current directory");
                StdOut("\tshow\tShow the content of the current directory");
                StdOut("\ttree\tShow all the directories under the current directory");
                StdOut("\tsampling\tDisplay statistics on random shards from the database");
                StdOut("\tcoordinators\tShow the current coordinators for the cluster");
                //StdOut("\thelp\tShow all the commands");
                StdOut("\tquit\tQuit");
                StdOut("");

                try
                {
                    var cf = await Fdb.System.GetCoordinatorsAsync(Db, cancel);

                    Description = cf.Description;
                    StdOut("Ready...", ConsoleColor.DarkGreen);
                }
                catch (Exception e)
                {
                    StdErr("Failed to get coordinators state from cluster: " + e.Message, ConsoleColor.DarkRed);
                    Description = "???";
                }

                StdOut("");

                var le = new LineEditor("FDBShell");

                string[] cmds = new string[]
                {
                    "cd",
                    "clear",
                    "coordinators",
                    "count",
                    "dir",
                    "dump",
                    "exit",
                    "gc",
                    "help",
                    "layer",
                    "map",
                    "mem",
                    "mkdir",
                    "mv",
                    "partition",
                    "pwd",
                    "quit",
                    "ren",
                    "rmdir",
                    "sampling",
                    "shards",
                    "show",
                    "status",
                    "topology",
                    "tree",
                    "version",
                    "wide",
                };

                le.AutoCompleteEvent = (txt, pos) =>
                {
                    string[] res;
                    int      p = txt.IndexOf(' ');
                    if (p > 0)
                    {
                        string cmd = txt.Substring(0, p);
                        string arg = txt.Substring(p + 1).Trim();

                        if (cmd == "cd" || cmd == "rmdir")
                        {                         // handle completion for directories
                            // txt: "cd foo" => prefix = "foo"
                            // txt: "cd foobar/b" => prefix = "b"

                            bool   hasLeadingSlash = arg.EndsWith("/");
                            var    path            = FdbPath.Parse(hasLeadingSlash ? (arg + "!") : arg);
                            var    parent          = path.Count > 1 ? path.GetParent() : path.IsAbsolute ? FdbPath.Root : FdbPath.Empty;
                            string search          = hasLeadingSlash ? "" : path.Name;

                            var subdirs = RunAsyncCommand((db, log, ct) => AutoCompleteDirectories(CombinePath(CurrentDirectoryPath, parent.ToString()), db, log, ct), cancel).GetAwaiter().GetResult();

                            if (!subdirs.HasValue || subdirs.Value == null)
                            {
                                return(new LineEditor.Completion(txt, null));
                            }

                            res = subdirs.Value
                                  .Where(s => s.StartsWith(search, StringComparison.Ordinal))
                                  .Select(s => (cmd + " " + parent[s]).Substring(txt.Length))
                                  .ToArray();

                            if (res.Length == 1 && res[0] == string.Empty)
                            {                             // someone was at "cd /Foo/Bar", pressed TAB again, and there is no other match
                                // => we interpret it as "want to go in the sub-folder

                                res = new[] { "/" };                                 // add a "slash"
                            }

                            return(new LineEditor.Completion(txt, res));
                        }

                        // unknown command
                        return(new LineEditor.Completion(txt, null));
                    }

                    // list of commands
                    res = cmds
                          .Where(cmd => cmd.StartsWith(txt, StringComparison.OrdinalIgnoreCase))
                          .Select(cmd => cmd.Substring(txt.Length))
                          .ToArray();
                    return(new LineEditor.Completion(txt, res));
                };
                le.TabAtStartCompletes = true;

                string prompt = null;

                void UpdatePrompt(FdbPath path)
                {
                    prompt = $"[fdb:{Description} {path.ToString()}]# ";
                }
                le.PromptColor = ConsoleColor.Cyan;
                UpdatePrompt(CurrentDirectoryPath);

                while (!stop)
                {
                    string s;
                    if (startCommand != null)
                    {
                        s            = startCommand;
                        startCommand = null;
                    }
                    else
                    {
                        s = startCommand ?? le.Edit(prompt, "");
                    }

                    if (s == null)
                    {
                        break;
                    }

                    //TODO: we need a tokenizer that recognizes binary keys, tuples, escaped paths, etc...
                    var    tokens = Tokenize(s);
                    string cmd    = tokens.Count > 0 ? tokens.Get <string>(0) : string.Empty;
                    var    extras = tokens.Count > 1 ? tokens.Substring(1) : STuple.Empty;

                    var trimmedCommand = cmd.Trim().ToLowerInvariant();
                    try
                    {
                        switch (trimmedCommand)
                        {
                        case "":
                        {
                            continue;
                        }

                        case "log":
                        {
                            string prm = PopParam(ref extras);
                            LogCommand(prm, extras, Console.Out);
                            break;
                        }

                        case "version":
                        {
                            await VersionCommand(extras, clusterFile, Console.Out, cancel);

                            break;
                        }

                        case "tree":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Tree(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "map":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Map(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "dir":
                        case "ls":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.Default, db, log, ct), cancel);

                            break;
                        }

                        case "ll":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.ShowCount, db, log, ct), cancel);

                            break;
                        }

                        case "count":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Count(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "show":
                        case "top":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Show(CurrentDirectoryPath, extras, false, db, log, ct), cancel);

                            break;
                        }

                        case "last":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Show(CurrentDirectoryPath, extras, true, db, log, ct), cancel);

                            break;
                        }

                        case "dump":
                        {
                            string output = PopParam(ref extras);
                            if (string.IsNullOrEmpty(output))
                            {
                                StdErr("You must specify a target file path.", ConsoleColor.Red);
                                break;
                            }
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dump(CurrentDirectoryPath, output, extras, db, log, ct), cancel);

                            break;
                        }

                        case "cd":
                        case "pwd":
                        {
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var newPath = CombinePath(CurrentDirectoryPath, prm);
                                var res     = await RunAsyncCommand(
                                    (db, log, ct) => db.ReadAsync(tr => BasicCommands.TryOpenCurrentDirectoryAsync(tr, newPath), ct),
                                    cancel
                                    );

                                if (res.Failed)
                                {
                                    StdErr($"# Failed to open Directory {newPath}: {res.Error.Message}", ConsoleColor.Red);
                                    Console.Beep();
                                }
                                else if (res.Value == null)
                                {
                                    StdOut($"# Directory {newPath} does not exist!", ConsoleColor.Red);
                                    Console.Beep();
                                }
                                else
                                {
                                    CurrentDirectoryPath = newPath;
                                    UpdatePrompt(CurrentDirectoryPath);
                                }
                            }
                            else
                            {
                                var res = await RunAsyncCommand(
                                    (db, log, ct) => db.ReadAsync(tr => BasicCommands.TryOpenCurrentDirectoryAsync(tr, CurrentDirectoryPath), ct),
                                    cancel
                                    );

                                if (res.Failed)
                                {
                                    StdErr($"# Failed to query Directory {Program.CurrentDirectoryPath}: {res.Error.Message}", ConsoleColor.Red);
                                }
                                else if (res.Value == null)
                                {
                                    StdOut($"# Directory {Program.CurrentDirectoryPath} does not exist anymore");
                                }
                            }

                            break;
                        }

                        case "mkdir":
                        case "md":
                        {                                 // "mkdir DIRECTORYNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "rmdir":
                        {                                 // "rmdir DIRECTORYNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.RemoveDirectory(path, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "mv":
                        case "ren":
                        {                                 // "mv SOURCE DESTINATION"
                            string prm     = PopParam(ref extras);
                            var    srcPath = CombinePath(CurrentDirectoryPath, prm);
                            var    dstPath = CombinePath(CurrentDirectoryPath, extras.Get <string>(0));
                            await RunAsyncCommand((db, log, ct) => BasicCommands.MoveDirectory(srcPath, dstPath, extras.Substring(1), db, log, ct), cancel);

                            break;
                        }

                        case "get":
                        {                                 // "get KEY"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify a key to read.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.Get(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "clear":
                        {                                 // "clear KEY"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify a key to clear.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.Clear(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "clearrange":
                        {                                 // "clear *" or "clear FROM TO"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify either '*', a prefix, or a key range.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.ClearRange(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "layer":
                        {
                            string prm = PopParam(ref extras);
                            if (string.IsNullOrEmpty(prm))
                            {                                     // displays the layer id of the current folder
                                await RunAsyncCommand((db, log, ct) => BasicCommands.ShowDirectoryLayer(CurrentDirectoryPath, extras, db, log, ct), cancel);
                            }
                            else
                            {                                     // change the layer id of the current folder
                                prm = prm.Trim();
                                // double or single quotes can be used to escape the value
                                if (prm.Length >= 2 && (prm.StartsWith("'") && prm.EndsWith("'")) || (prm.StartsWith("\"") && prm.EndsWith("\"")))
                                {
                                    prm = prm.Substring(1, prm.Length - 2);
                                }

                                await RunAsyncCommand((db, log, ct) => BasicCommands.ChangeDirectoryLayer(CurrentDirectoryPath, prm, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "mkpart":
                        {                                 // "mkpart PARTITIONNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, STuple.Create(FdbDirectoryPartition.LayerId).Concat(extras), db, log, ct), cancel);
                            }

                            break;
                        }

                        case "topology":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Topology(null, extras, db, log, ct), cancel);

                            break;
                        }

                        case "shards":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Shards(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "sampling":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Sampling(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "coordinators":
                        {
                            await RunAsyncCommand((db, log, ct) => CoordinatorsCommand(db, log, ct), cancel);

                            break;
                        }

                        case "partition":
                        {
                            string prm = PopParam(ref extras);
                            if (string.IsNullOrEmpty(prm))
                            {
                                StdOut($"# Current partition is {partition}");
                                //TODO: browse existing partitions ?
                                break;
                            }

                            var          newPartition = FdbPath.Parse(prm.Trim());
                            IFdbDatabase newDb        = null;
                            try
                            {
                                var options = new FdbConnectionOptions
                                {
                                    ClusterFile = clusterFile,
                                    Root        = newPartition
                                };
                                newDb = await ChangeDatabase(options, cancel);
                            }
                            catch (Exception)
                            {
                                newDb?.Dispose();
                                newDb = null;
                                throw;
                            }
                            finally
                            {
                                if (newDb != null)
                                {
                                    if (Db != null)
                                    {
                                        Db.Dispose();
                                        Db = null;
                                    }

                                    Db        = newDb;
                                    partition = newPartition;
                                    StdOut($"# Changed partition to {partition}");
                                }
                            }

                            break;
                        }

                        case "q":
                        case "x":
                        case "quit":
                        case "exit":
                        case "bye":
                        {
                            stop = true;
                            break;
                        }

                        case "gc":
                        {
                            long before = GC.GetTotalMemory(false);
                            Console.Write("Collecting garbage...");
                            GC.Collect();
                            GC.WaitForPendingFinalizers();
                            GC.Collect();
                            StdOut(" Done");
                            long after = GC.GetTotalMemory(false);
                            StdOut("- before = " + before.ToString("N0"));
                            StdOut("- after  = " + after.ToString("N0"));
                            StdOut("- delta  = " + (before - after).ToString("N0"));
                            break;
                        }

                        case "mem":
                        {
                            StdOut("Memory usage:");
                            StdOut("- Managed Mem  : " + GC.GetTotalMemory(false).ToString("N0"));
                            //TODO: how do we get these values on Linux/Mac?
#if !NETCOREAPP
                            StdOut("- Working Set  : " + PerfCounters.WorkingSet.NextValue().ToString("N0") + " (peak " + PerfCounters.WorkingSetPeak.NextValue().ToString("N0") + ")");
                            StdOut("- Virtual Bytes: " + PerfCounters.VirtualBytes.NextValue().ToString("N0") + " (peak " + PerfCounters.VirtualBytesPeak.NextValue().ToString("N0") + ")");
                            StdOut("- Private Bytes: " + PerfCounters.PrivateBytes.NextValue().ToString("N0"));
                            StdOut("- BytesInAlHeap: " + PerfCounters.ClrBytesInAllHeaps.NextValue().ToString("N0"));
#endif
                            break;
                        }

                        case "wide":
                        {
                            try
                            {
                                Console.WindowWidth = 160;
                            }
                            catch (Exception e)
                            {
                                StdErr("Failed to change console width: " + e.Message, ConsoleColor.DarkRed);
                            }

                            break;
                        }

                        case "status":
                        case "wtf":
                        {
                            var result = await RunAsyncCommand((_, log, ct) => FdbCliCommands.RunFdbCliCommand("status details", null, clusterFile, log, ct), cancel);

                            if (result.Failed)
                            {
                                break;
                            }
                            if (result.Value.ExitCode != 0)
                            {
                                StdErr($"# fdbcli exited with code {result.Value.ExitCode}", ConsoleColor.DarkRed);
                                StdOut("> StdErr:", ConsoleColor.DarkGray);
                                StdOut(result.Value.StdErr);
                                StdOut("> StdOut:", ConsoleColor.DarkGray);
                            }

                            StdOut(result.Value.StdOut);
                            break;
                        }

                        default:
                        {
                            StdErr($"Unknown command : '{trimmedCommand}'", ConsoleColor.Red);
                            break;
                        }
                        }
                    }
                    catch (Exception e)
                    {
                        StdErr($"Failed to execute command '{trimmedCommand}': " + e.Message, ConsoleColor.Red);
#if DEBUG
                        StdErr(e.ToString(), ConsoleColor.DarkRed);
#endif
                    }

                    if (!string.IsNullOrEmpty(execCommand))
                    {                     // only run one command, and then exit
                        break;
                    }
                }
            }
            finally
            {
                Program.Db?.Dispose();
            }
        }