Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cli parser fixups #116

Merged
merged 9 commits into from
Sep 29, 2020
9 changes: 6 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"preLaunchTask": "", //"build",
"program": "${workspaceFolder}/out/dotnet/source/Mlos.Agent.Server/objd/AnyCPU/Mlos.Agent.Server.dll",
"args": [
//"--optimizer-uri",
//"http://localhost:50051",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixup for the default debugger experience.
If this options are enabled by default, the command will fail.

In the future we can consider to add a preLaunchTask that starts the optimizer service.

"--executable",
"${workspaceFolder}/out/cmake/Debug/source/Mlos.UnitTest/Mlos.UnitTest"
],
"env": {
Expand All @@ -42,10 +45,10 @@
"preLaunchTask": "", //"build",
"program": "${workspaceFolder}/out/dotnet/source/Mlos.Agent.Server/objd/AnyCPU/Mlos.Agent.Server.dll",
"args": [
//"--optimizer-uri",
//"http://localhost:50051",
"--executable",
"${workspaceFolder}/out/cmake/Debug/source/Examples/SmartCache/SmartCache",
"--optimizer-uri",
"http://localhost:50051"
"${workspaceFolder}/out/cmake/Debug/source/Examples/SmartCache/SmartCache"
],
"env": {
"MLOS_SETTINGS_REGISTRY_PATH": "${workspaceFolder}/out/dotnet/source/Examples/SmartCache/SmartCache.SettingsRegistry/objd/AnyCPU"
Expand Down
94 changes: 89 additions & 5 deletions source/Mlos.Agent.Server/MlosAgentServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using CommandLine;
using CommandLine.Text;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
Expand Down Expand Up @@ -57,21 +60,30 @@ public static void Main(string[] args)
{
string executableFilePath = null;
Uri optimizerAddressUri = null;
IEnumerable<string> extraArgs = null;

var cliOptsParseResult = CommandLine.Parser.Default.ParseArguments<CliOptions>(args)
var cliOptsParser = new Parser(with => with.HelpWriter = null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add custom help output handling with ShowUsageHelp now.

var cliOptsParseResult = cliOptsParser.ParseArguments<CliOptions>(args)
.WithParsed(parsedOptions =>
{
executableFilePath = parsedOptions.Executable;
optimizerAddressUri = parsedOptions.OptimizerUri;
extraArgs = parsedOptions.ExtraArgs;
});
if (cliOptsParseResult.Tag == ParserResultType.NotParsed)
{
// CommandLine already prints the help text for us in this case.
//
Console.Error.WriteLine("Failed to parse command line options.");
Environment.Exit(1);
cliOptsParseResult.WithNotParsed(errs => ShowUsageHelp(
cliOptsParseResult,
errors: errs,
msg: "Failed to parse command line options."));
}
else if (extraArgs != null && extraArgs.Any())
{
ShowUsageHelp(cliOptsParseResult, msg: "Extra arguments found.");
bpkroth marked this conversation as resolved.
Show resolved Hide resolved
}

cliOptsParser.Dispose();

// Check for the executable before setting up any shared memory to
// reduce cleanup issues.
//
Expand Down Expand Up @@ -220,6 +232,71 @@ public static void Main(string[] args)
Console.WriteLine("Mlos.Agent exited.");
}

private static void ShowUsageHelp<T>(ParserResult<T> parserResult, IEnumerable<Error> errors = null, string msg = null)
{
if (msg != null)
{
Console.Error.WriteLine(msg);
Console.Error.WriteLine(string.Empty);
bpkroth marked this conversation as resolved.
Show resolved Hide resolved
}

if (errors == null)
{
errors = new List<Error>();
}

HelpText helpText = null;
if (errors.IsVersion())
{
helpText = HelpText.AutoBuild(parserResult);
}
else
{
helpText = HelpText.AutoBuild(
parserResult,
onError: ht =>
{
return HelpText.DefaultParsingErrorsHandler(parserResult, ht);
},
e => e);
helpText.AddNewLineBetweenHelpSections = true;
helpText.AddPreOptionsLines(new[]
{
// Use a single long line of text to let the help output get wrapped automatically for us.
bpkroth marked this conversation as resolved.
Show resolved Hide resolved
"The Mlos.Agent.Server acts as an external agent for MLOS integrated components, allowing them to "
+ "send it messages over shared memory, which it can process and use to interface with an optimizer "
+ "service to tune the components over their shared memory communication channels.",
string.Empty,

// Indent the actual commands to make them stand out a bit more.
// Note: The help out preserves the indent across wrapping.
"usage mode 1: Wait for an application to register over global shared memory, without an optimizer.",
" dotnet Mlos.Agent.Server.dll",
string.Empty,

"usage mode 2: Wait for an application to register over global shared memory, and prepare to "
+ "communicate with an MLOS optimizer listening at the given Grpc URI.",
" dotnet Mlos.Agent.Server.dll --optimizer-uri http://localhost:50051",
string.Empty,

"usage mode 3: Start an executable to communicate over freshly prepared global shared memory.",
" dotnet Mlos.Agent.Server.dll --executable path/to/executable",
string.Empty,

"usage mode 4: Start an executable to communicate over freshly prepared global shared memory and "
+ "prepare to communicate with an MLOS optimizer listening at the given Grpc URI.",
" dotnet Mlos.Agent.Server.dll --executable path/to/executable --optimizer-uri http://localhost:50051",
string.Empty,

"Note: the optimizer service used in these examples can be started using the 'start_optimizer_microservice "
+ "launch --port 50051' command from the mlos Python module.",
});
}

Console.WriteLine(helpText);
Environment.Exit(1);
}

/// <summary>
/// The command line options for this application.
/// </summary>
Expand All @@ -230,6 +307,13 @@ private class CliOptions

[Option("optimizer-uri", Required = false, Default = null, HelpText = "A URI to connect to the MLOS Optimizer service over GRPC (e.g. 'http://localhost:50051').")]
public Uri OptimizerUri { get; set; }

/// <remarks>
/// Just used to detect any extra arguments so we can throw a warning.
/// See Also: https://github.com/microsoft/MLOS/issues/112.
/// </remarks>
[CommandLine.Value(0)]
public IEnumerable<string> ExtraArgs { get; set; }
Copy link
Contributor Author

@bpkroth bpkroth Sep 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option unfortunately isn't hidden and leaves a weird value pos. 0 text at the end of the help output, but it does allow us to detect the extra arguments.

}
}
}