diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData01.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData01.txt new file mode 100644 index 0000000000..d263d8a9a8 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData01.txt @@ -0,0 +1,34 @@ +04:56:24.570 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.571 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +04:56:24.571 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.571 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +04:56:24.571 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Sending query of namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.572 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Query completed +04:56:24.572 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.572 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +04:56:24.572 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.573 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +04:56:24.573 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Sending query of namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.573 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Query completed +04:56:24.573 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.574 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +04:56:24.574 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Sending query of namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.574 [Thread-16] DEBUG org.mongodb.driver.protocol.query - Query completed +04:56:24.574 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Updating documents in namespace ycsb.usertable on connection [connectionId{localValue:17, serverValue:34}] to server localhost:27017 +04:56:24.575 [Thread-16] DEBUG org.mongodb.driver.protocol.update - Update completed +08:32:27.881 [cluster-ClusterId{value='654b468a9bbaa7610f22769c', description='null'}-localhost:27017] DEBUG org.mongodb.driver.connection - Closing connection connectionId{localValue:1, serverValue:1} +[OVERALL], RunTime(ms), 188018.0 +[OVERALL], Throughput(ops/sec), 5318.639704709123 +[CLEANUP], Operations, 16.0 +[CLEANUP], AverageLatency(us), 628.3125 +[CLEANUP], MinLatency(us), 1.0 +[CLEANUP], MaxLatency(us), 10031.0 +[CLEANUP], 95thPercentileLatency(us), 5.0 +[CLEANUP], 99thPercentileLatency(us), 10031.0 +[INSERT], Operations, 1000000.0 +[INSERT], AverageLatency(us), 2962.514545 +[INSERT], MinLatency(us), 176.0 +[INSERT], MaxLatency(us), 659967.0 +[INSERT], 95thPercentileLatency(us), 5859.0 +[INSERT], 99thPercentileLatency(us), 9199.0 +[INSERT], Return=OK, 1000000 \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData02.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData02.txt new file mode 100644 index 0000000000..a7d9d50f20 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoInsertData02.txt @@ -0,0 +1,16 @@ +08:32:27.881 [cluster-ClusterId{value='654b468a9bbaa7610f22769c', description='null'}-localhost:27017] DEBUG org.mongodb.driver.connection - Closing connection connectionId{localValue:1, serverValue:1} +[OVERALL], RunTime(ms), 273802.0 +[OVERALL], Throughput(ops/sec), 3652.274271188669 +[CLEANUP], Operations, 16.0 +[CLEANUP], AverageLatency(us), 1170.75 +[CLEANUP], MinLatency(us), 2.0 +[CLEANUP], MaxLatency(us), 18671.0 +[CLEANUP], 95thPercentileLatency(us), 36.0 +[CLEANUP], 99thPercentileLatency(us), 18671.0 +[INSERT], Operations, 1000000.0 +[INSERT], AverageLatency(us), 4326.729145 +[INSERT], MinLatency(us), 264.0 +[INSERT], MaxLatency(us), 829439.0 +[INSERT], 95thPercentileLatency(us), 8567.0 +[INSERT], 99thPercentileLatency(us), 14039.0 +[INSERT], Return=OK, 1000000 \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData01.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData01.txt new file mode 100644 index 0000000000..1cf085b2f0 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData01.txt @@ -0,0 +1,22 @@ +[OVERALL], RunTime(ms), 210252.0 +[OVERALL], Throughput(ops/sec), 4756.197325114625 +[READ], Operations, 500209.0 +[READ], AverageLatency(us), 3193.3359775613794 +[READ], MinLatency(us), 139.0 +[READ], MaxLatency(us), 306431.0 +[READ], 95thPercentileLatency(us), 6959.0 +[READ], 99thPercentileLatency(us), 13231.0 +[READ], Return=OK, 500209 +[CLEANUP], Operations, 16.0 +[CLEANUP], AverageLatency(us), 648.5625 +[CLEANUP], MinLatency(us), 1.0 +[CLEANUP], MaxLatency(us), 10351.0 +[CLEANUP], 95thPercentileLatency(us), 7.0 +[CLEANUP], 99thPercentileLatency(us), 10351.0 +[UPDATE], Operations, 499791.0 +[UPDATE], AverageLatency(us), 3456.1790108265254 +[UPDATE], MinLatency(us), 242.0 +[UPDATE], MaxLatency(us), 333567.0 +[UPDATE], 95thPercentileLatency(us), 7631.0 +[UPDATE], 99thPercentileLatency(us), 14175.0 +[UPDATE], Return=OK, 499791 diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData02.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData02.txt new file mode 100644 index 0000000000..ebea574967 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData02.txt @@ -0,0 +1,22 @@ +[OVERALL], RunTime(ms), 212686.0 +[OVERALL], Throughput(ops/sec), 4701.766924010043 +[READ], Operations, 499980.0 +[READ], AverageLatency(us), 3222.582387295492 +[READ], MinLatency(us), 147.0 +[READ], MaxLatency(us), 462335.0 +[READ], 95thPercentileLatency(us), 7003.0 +[READ], 99thPercentileLatency(us), 12895.0 +[READ], Return=OK, 499980 +[CLEANUP], Operations, 16.0 +[CLEANUP], AverageLatency(us), 602.4375 +[CLEANUP], MinLatency(us), 1.0 +[CLEANUP], MaxLatency(us), 9615.0 +[CLEANUP], 95thPercentileLatency(us), 7.0 +[CLEANUP], 99thPercentileLatency(us), 9615.0 +[UPDATE], Operations, 500020.0 +[UPDATE], AverageLatency(us), 3499.336832526699 +[UPDATE], MinLatency(us), 236.0 +[UPDATE], MaxLatency(us), 356351.0 +[UPDATE], 95thPercentileLatency(us), 7775.0 +[UPDATE], 99thPercentileLatency(us), 13983.0 +[UPDATE], Return=OK, 500020 \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData03.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData03.txt new file mode 100644 index 0000000000..b725fdee65 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/MongoDB/MongoOperationsData03.txt @@ -0,0 +1,22 @@ +[OVERALL], RunTime(ms), 705.0 +[OVERALL], Throughput(ops/sec), 141.84397163120568 +[READ], Operations, 48.0 +[READ], AverageLatency(us), 25801.666666666668 +[READ], MinLatency(us), 917.0 +[READ], MaxLatency(us), 115839.0 +[READ], 95thPercentileLatency(us), 111551.0 +[READ], 99thPercentileLatency(us), 115839.0 +[READ], Return=NOT_FOUND, 48 +[CLEANUP], Operations, 16.0 +[CLEANUP], AverageLatency(us), 828.625 +[CLEANUP], MinLatency(us), 0.0 +[CLEANUP], MaxLatency(us), 13247.0 +[CLEANUP], 95thPercentileLatency(us), 7.0 +[CLEANUP], 99thPercentileLatency(us), 13247.0 +[UPDATE], Operations, 52.0 +[UPDATE], AverageLatency(us), 27971.326923076922 +[UPDATE], MinLatency(us), 1482.0 +[UPDATE], MaxLatency(us), 116799.0 +[UPDATE], 95thPercentileLatency(us), 116223.0 +[UPDATE], 99thPercentileLatency(us), 116351.0 +[UPDATE], Return=NOT_FOUND, 52 \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBExecutorTests.cs new file mode 100644 index 0000000000..02c3ad0223 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBExecutorTests.cs @@ -0,0 +1,101 @@ + +namespace VirtualClient.Actions +{ + using Moq; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.InteropServices; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using VirtualClient.Common; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + + [TestFixture] + [Category("Unit")] + public class MongoDBExecutorTests + { + private MockFixture mockFixture; + private DependencyPath mockPackage, mockYCSBPackage; + private ConcurrentBuffer defaultOutput = new ConcurrentBuffer(); + + + [SetUp] + public void Setup() + { + + this.mockFixture = new MockFixture(); + this.mockFixture.Setup(PlatformID.Unix); + this.mockPackage = new DependencyPath("mongodb", this.mockFixture.PlatformSpecifics.GetPackagePath("mongodb")); + this.mockYCSBPackage = new DependencyPath("ycsb", this.mockFixture.PlatformSpecifics.GetPackagePath("ycsb")); + this.mockFixture.PackageManager.OnGetPackage().ReturnsAsync(this.mockPackage); + this.mockFixture.Parameters = new Dictionary() + { + { nameof(MongoDBExecutor.Scenario), "" }, + { nameof(MongoDBExecutor.PackageName), "mongodb" }, + { nameof(MongoDBExecutor.YCSBPackageName), "ycsb" } + }; + + string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string resultsPath = Path.Combine(currentDirectory, "Examples", "MongoDB", "MongoInsertData01.txt"); + string results = File.ReadAllText(resultsPath); + this.defaultOutput.Clear(); + this.defaultOutput.Append(results); + } + + [Test] + [TestCase(PlatformID.Unix, Architecture.X64)] + public async Task MongoDBExecutorInitializesItsDependenciesAsExpected(PlatformID platform, Architecture architecture) + { + this.Setup(); + using (TestMongoDBExecutor executor = new TestMongoDBExecutor(this.mockFixture)) + { + this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) => + { + return this.mockFixture.Process; + }; + + await executor.InitializeAsync(EventContext.None, CancellationToken.None) + .ConfigureAwait(false); + + string mongoDBExpectedPath = this.mockFixture.PlatformSpecifics.Combine(this.mockPackage.Path) ; + + Assert.AreEqual(mongoDBExpectedPath, executor.GetMongoProcessPath); + } + } + + + + private class TestMongoDBExecutor : MongoDBExecutor + { + public TestMongoDBExecutor(MockFixture fixture) + : base(fixture.Dependencies, fixture.Parameters) + { + } + + public new Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + return base.InitializeAsync(telemetryContext, cancellationToken); + } + + public new Task ExecuteAsync(EventContext context, CancellationToken cancellationToken) + { + this.InitializeAsync(context, cancellationToken).GetAwaiter().GetResult(); + return base.ExecuteAsync(context, cancellationToken); + } + + public string GetMongoProcessPath => base.MongoDBPackagePath; + + + + } + + } + + +} diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBMetricsParserTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBMetricsParserTests.cs new file mode 100644 index 0000000000..9dd79071d7 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/MongoDB/MongoDBMetricsParserTests.cs @@ -0,0 +1,32 @@ +namespace VirtualClient.Actions +{ + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + + public class MongoDBMetricsParserTests + { + private string rawText; + private MongoDBMetricsParser testParser; + + [SetUp] + public void Setup() + { + string workingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string outputPath = Path.Combine(workingDirectory, @"Examples\MongoDB\MongoOperationsData03.txt"); + this.rawText = File.ReadAllText(outputPath); + this.testParser = new MongoDBMetricsParser(this.rawText); + } + [Test] + public void MongoDBMetricsParserParsesAsExpected() + { + this.testParser.Parse(); + Assert.IsNotNull(this.testParser.Sections["Metrics"]); + } + } +} diff --git a/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBExecutor.cs b/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBExecutor.cs new file mode 100644 index 0000000000..65544ce4bb --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBExecutor.cs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.IO.Abstractions; + using System.IO.Packaging; + using System.Reflection.Metadata.Ecma335; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Identity.Client; + using Polly; + using VirtualClient.Common; + using VirtualClient.Common.Extensions; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + using VirtualClient.Contracts.Metadata; + + /// + /// The MongoDB workload executor. + /// + public class MongoDBExecutor : VirtualClientComponent + { + private IPackageManager packageManager; + private ISystemManagement systemManager; + + /// + /// Constructor for + /// + /// Provides required dependencies to the component. + /// Parameters defined in the profile or supplied on the command line. + public MongoDBExecutor(IServiceCollection dependencies, IDictionary parameters) + : base(dependencies, parameters) + { + this.packageManager = dependencies.GetService(); + this.systemManager = this.Dependencies.GetService(); + } + + /// + /// The retry policy to apply to package install for handling transient errors. + /// + public IAsyncPolicy InstallRetryPolicy { get; set; } = Policy + .Handle(exc => exc.Reason == ErrorReason.DependencyInstallationFailed) + .WaitAndRetryAsync(5, (retries) => TimeSpan.FromSeconds(retries * 2)); + + /// + /// Defines the name of the YCSB package. + /// + public string YCSBPackageName + { + get + { + return this.Parameters.GetValue(nameof(this.YCSBPackageName)); + } + } + + /// + /// Java Development Kit package name. + /// + public string JdkPackageName + { + get + { + return this.Parameters.GetValue(nameof(MongoDBExecutor.JdkPackageName)); + } + } + + /// + /// The file path for YCSB workloads. + /// + protected string YcsbPackagePath { get; set; } + + /// + /// The file path for MongoDB + /// + protected string MongoDBPackagePath { get; set; } + + /// + /// The file path for YCSB workloads. + /// + protected string JDKPackagePath { get; set; } + + /// + /// MongoDB mount path + /// + protected string DBPath { get; set; } + + /// + /// Initializes the environment for execution of the MongoDB YCSB workload. + /// + protected override async Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + DependencyPath javaPackage = await this.GetPackageAsync( + this.JdkPackageName, cancellationToken); + if (javaPackage == null) + { + throw new DependencyException( + $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", + ErrorReason.WorkloadDependencyMissing); + } + + this.JDKPackagePath = javaPackage.Path; + + DependencyPath mongoDBPackage = await this.packageManager.GetPackageAsync(this.PackageName, CancellationToken.None); + + if (mongoDBPackage == null) + { + throw new DependencyException( + $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", + ErrorReason.WorkloadDependencyMissing); + } + + this.MongoDBPackagePath = mongoDBPackage.Path; + + DependencyPath ycsbPackage = await this.packageManager.GetPackageAsync(this.YCSBPackageName, CancellationToken.None); + + if (mongoDBPackage == null) + { + throw new DependencyException( + $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", + ErrorReason.WorkloadDependencyMissing); + } + + this.YcsbPackagePath = ycsbPackage.Path; + + // string pathtobin = this.PlatformSpecifics.Combine(this.PackageDirectory, "bin"); + + } + + /// + /// Executes the MongoDB YCSB workload. + /// + protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + this.DBPath = this.PlatformSpecifics.Combine(this.MongoDBPackagePath, "mongodb-linux-x86_64-ubuntu2004-5.0.15", "bin", "mongod"); + + try + { + string ycsbPath = this.PlatformSpecifics.Combine(this.YcsbPackagePath, "ycsb-0.5.0", "bin", "ycsb"); + string javaExecutablePath = this.PlatformSpecifics.Combine(this.JDKPackagePath, "bin", "java"); + string ycsbExecutablePath = this.PlatformSpecifics.Combine(this.YcsbPackagePath, "ycsb-0.5.0", "bin"); + + string convertToExeArgument = this.GetCommandLineArguments("convertExecutable"); + string createDBArgument = this.GetCommandLineArguments("createDB"); + string createLogArgument = this.GetCommandLineArguments("createLog"); + string runMongoArgument = this.GetCommandLineArguments("runMongo"); + string insertDataArgument = this.GetCommandLineArguments("insertData"); + string updateDataArgument = this.GetCommandLineArguments("updateData"); + + this.SetEnvironmentVariable(EnvironmentVariable.JAVA_HOME, javaExecutablePath, EnvironmentVariableTarget.Process); + + await this.ExecuteCommandAsync("chmod", convertToExeArgument, ycsbExecutablePath, telemetryContext, cancellationToken).ConfigureAwait(false); + await this.ExecuteCommandAsync("mkdir", createDBArgument, this.MongoDBPackagePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + await this.ExecuteCommandAsync("mkdir", createLogArgument, this.MongoDBPackagePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + await this.ExecuteCommandAsync($"{this.DBPath}", runMongoArgument, this.MongoDBPackagePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + + /* ./ycsb load mongodb -s -P /home/azureuser/vc/content/linux-x64/packages/ycsb/ycsb-0.5.0/workloads/workloada -p recordcount=1000000 -threads 16 + */ + DateTime loadStartTime = DateTime.UtcNow; + var loadOutput = await this.ExecuteCommandAsync($"{ycsbPath}", insertDataArgument, this.YcsbPackagePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + DateTime loadFinishTime = DateTime.UtcNow; + + await this.CaptureMetricsAsync(loadOutput, insertDataArgument, loadStartTime, loadFinishTime, telemetryContext, cancellationToken) + .ConfigureAwait(false); + + /*./ycsb run mongodb -s -P /home/azureuser/vc/content/linux-x64/packages/ycsb/ycsb-0.5.0/workloads/workloada -p operationcount=1000000 -p recordcount=1000000 -threads 16 + */ + DateTime runStartTime = DateTime.UtcNow; + var runOutput = await this.ExecuteCommandAsync($"{ycsbPath}", updateDataArgument, this.YcsbPackagePath, telemetryContext, cancellationToken) + .ConfigureAwait(false); + DateTime runFinishTime = DateTime.UtcNow; + + await this.CaptureMetricsAsync(runOutput, updateDataArgument, runStartTime, runFinishTime, telemetryContext, cancellationToken) + .ConfigureAwait(false); + + await this.ShutDownMongoDB(telemetryContext, cancellationToken); + } + catch (OperationCanceledException ex) + { + await this.ShutDownMongoDB(telemetryContext, cancellationToken); + telemetryContext.AddError(ex); + this.Logger.LogTraceMessage($"{nameof(ExampleWorkloadExecutor)}.Exception", telemetryContext); + } + } + + private async Task ExecuteCommandAsync(string command, string commandArguments, string workingDirectory, EventContext telemetryContext, CancellationToken cancellationToken) + { + EventContext relatedContext = EventContext.Persisted() + .AddContext(nameof(command), command) + .AddContext(nameof(commandArguments), commandArguments); + + using (IProcessProxy process = this.systemManager.ProcessManager.CreateElevatedProcess(this.Platform, command, commandArguments, workingDirectory)) + { + this.CleanupTasks.Add(() => process.SafeKill()); + this.LogProcessTrace(process); + + await process.StartAndWaitAsync(cancellationToken); + + if (!cancellationToken.IsCancellationRequested) + { + if (process.IsErrored()) + { + await this.LogProcessDetailsAsync(process, relatedContext, logToFile: true); + process.ThrowIfWorkloadFailed(); + } + } + + return process.StandardOutput.ToString(); + } + } + + private string GetCommandLineArguments(string commandType) + { + string workloadPath = this.PlatformSpecifics.Combine(this.YcsbPackagePath, "ycsb-0.5.0", "workloads", "workloada"); + string mongoDBPath = "/tmp/mongod"; + string mongoLogFilePath = "/tmp/mongolog"; + string deleteFilePath = "/tmp/mong*"; + + switch (commandType) + { + case "convertExecutable": + return "+x ycsb"; + + case "createDB": + return $"-p {mongoDBPath}"; + + case "createLog": + return $"-p {mongoLogFilePath}"; + + case "runMongo": + return $"--fork --dbpath {mongoDBPath} --logpath {mongoLogFilePath}/mongod.log"; + + case "shutdown": + return $"--dbpath {mongoDBPath} --shutdown"; + + case "insertData": + return $"load mongodb -s -P {workloadPath} -p recordcount={this.Parameters["RecordCount"]} -threads {this.Parameters["ThreadCount"]}"; + + case "updateData": + return $"run mongodb -s -P {workloadPath} -p operationcount={this.Parameters["OperationCount"]} -p recordcount={this.Parameters["RecordCount"]} -threads {this.Parameters["ThreadCount"]}"; + + case "deleteFiles": + return $"-rf {deleteFilePath}"; + + default: + return string.Empty; + + } + } + + private async Task CaptureMetricsAsync(string results, string commandArguments, DateTime startTime, DateTime endtime, EventContext telemetryContext, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + try + { + this.MetadataContract.AddForScenario( + "MongoDB", + commandArguments, + toolVersion: null); + + this.MetadataContract.Apply(telemetryContext); + + results.ThrowIfNullOrWhiteSpace(nameof(results)); + this.Logger.LogMessage($"{nameof(MongoDBExecutor)}.CaptureMetrics", telemetryContext.Clone() + .AddContext("results", results)); + + MongoDBMetricsParser resultsParser = new MongoDBMetricsParser(results); + IList workloadMetrics = resultsParser.Parse(); + + this.Logger.LogMetrics( + toolName: nameof(MongoDBExecutor), + scenarioName: this.Scenario, + scenarioStartTime: startTime, + scenarioEndTime: endtime, + metrics: workloadMetrics, + metricCategorization: null, + scenarioArguments: commandArguments, + this.Tags, + telemetryContext); + } + catch (SchemaException ex) + { + await this.ShutDownMongoDB(telemetryContext, cancellationToken); + telemetryContext.AddError(ex); + throw new WorkloadResultsException($"Failed to parse workload results.", ex, ErrorReason.WorkloadResultsParsingFailed); + } + + } + } + + private async Task ShutDownMongoDB(EventContext telemetryContext, CancellationToken cancellationToken) + { + /* sudo /home/azureuser/vc/content/linux-x64/packages/mongodb/mongodb-linux-x86_64-ubuntu2004-5.0.15/bin/mongod--dbpath /tmp/mongodb--shutdown + */ + string shutMongoArgument = this.GetCommandLineArguments("shutdown"); + string deleteDBFilesArgument = this.GetCommandLineArguments("deleteFiles"); + string deleteFilePath = "/tmp"; + await this.ExecuteCommandAsync($"{this.DBPath}", shutMongoArgument, this.MongoDBPackagePath, telemetryContext, cancellationToken).ConfigureAwait(false); + + await this.ExecuteCommandAsync("rm", deleteDBFilesArgument, deleteFilePath, telemetryContext, cancellationToken).ConfigureAwait(false); + + /* Adding delay of 1minute for graceful shutdown of mongodb */ + await Task.Delay(60000).ConfigureAwait(false); + + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBMetricsParser.cs new file mode 100644 index 0000000000..b13f1ccdef --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/MongoDB/MongoDBMetricsParser.cs @@ -0,0 +1,160 @@ +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json; + using System.Text.RegularExpressions; + using VirtualClient.Contracts; + + /// + /// Parser for MongoDB benchmark output. + /// + public class MongoDBMetricsParser : MetricsParser + { + /// + /// Sectionize by one or more empty lines. + /// + private static readonly Regex MongoDBSectionDelimiter = new (@"(\n)(\s)*(\n)", RegexOptions.ExplicitCapture); + + /// + /// Initializes a new instance of the class. + /// + /// Raw text which is output of the MongoDB workload + public MongoDBMetricsParser(string rawText) + : base(rawText) + { + } + + /// + /// Logic to parse and read metrics8 + /// + public override IList Parse() + { + try + { + this.Preprocess(); + Dictionary> metricsMap = GetMetricsMap(this.Sections["Metrics"]); + List metrics = new List(); + var metricRelativity = MetricRelativity.Undefined; + + foreach (var entry in metricsMap) + { + if (entry.Key == "OVERALL-Throughput") + { + metricRelativity = MetricRelativity.HigherIsBetter; + } + else if (entry.Key.Contains("AverageLatency")) + { + metricRelativity = MetricRelativity.LowerIsBetter; + } + else if (entry.Key.Contains("95thPercentileLatency")) + { + metricRelativity = MetricRelativity.LowerIsBetter; + } + + metrics.Add(new Metric(entry.Key, entry.Value.Item2, entry.Value.Item1, metricRelativity)); + + } + + return metrics; + } + catch (JsonException exc) + { + throw new WorkloadResultsException("Workload results parsing failure. The example workload results are not valid or are not formatted in a valid JSON.", exc, ErrorReason.WorkloadResultsParsingFailed); + } + } + + /// + /// Logic for preprocessing + /// + protected override void Preprocess() + { + RegexOptions options = RegexOptions.None; + var regex = new Regex("[ ]{2,}", options); + this.PreprocessedText = regex.Replace(this.RawText, " "); + string pattern = @"(?=\[OVERALL\])"; // Pattern to find the position before the word "[OVERALL]" + string newSection = $"{Environment.NewLine}Metrics{Environment.NewLine}"; + + Regex rgx = new Regex(pattern); + this.PreprocessedText = rgx.Replace(this.PreprocessedText, newSection, 1); + + this.Sections = TextParsingExtensions.Sectionize(this.PreprocessedText, MongoDBSectionDelimiter); + if (!this.Sections.ContainsKey("Metrics") || string.IsNullOrWhiteSpace(this.Sections["Metrics"])) + { + throw new WorkloadException( + $"Benchmarking metrics are not present", ErrorReason.WorkloadResultsParsingFailed); + } + + this.Sections["Metrics"] = this.Sections["Metrics"] + .Replace("[OVERALL], RunTime", "OVERALL-RunTime") + .Replace("[OVERALL], Throughput", "OVERALL-Throughput") + .Replace("[CLEANUP], Operations", "CLEANUP-Operations") + .Replace("[CLEANUP], AverageLatency", "CLEANUP-AverageLatency") + .Replace("[CLEANUP], MinLatency", "CLEANUP-MinLatency") + .Replace("[CLEANUP], MaxLatency", "CLEANUP-MaxLatency") + .Replace("[CLEANUP], 95thPercentileLatency", "CLEANUP-95thPercentileLatency") + .Replace("[CLEANUP], 99thPercentileLatency", "CLEANUP-99thPercentileLatency") + .Replace("[INSERT], Operations", "INSERT-Operations") + .Replace("[INSERT], AverageLatency", "INSERT-AverageLatency") + .Replace("[INSERT], MinLatency", "INSERT-MinLatency") + .Replace("[INSERT], MaxLatency", "INSERT-MaxLatency") + .Replace("[INSERT], 95thPercentileLatency", "INSERT-95thPercentileLatency") + .Replace("[INSERT], 99thPercentileLatency", "INSERT-99thPercentileLatency") + .Replace("[INSERT], Return=OK", "INSERT-Count") + .Replace("[INSERT], Return=ERROR", "INSERT-Error-Count") + .Replace("[READ], Operations", "SELECT-Operations") + .Replace("[READ], AverageLatency", "SELECT-AverageLatency") + .Replace("[READ], MinLatency", "SELECT-MinLatency") + .Replace("[READ], MaxLatency", "SELECT-MaxLatency") + .Replace("[READ], 95thPercentileLatency", "SELECT-95thPercentileLatency") + .Replace("[READ], 99thPercentileLatency", "SELECT-99thPercentileLatency") + .Replace("[READ], Return=OK", "SELECT-Count") + .Replace("[READ], Return=NOT_FOUND", "READ-NotFound-Count") + .Replace("[UPDATE], Operations", "UPDATE-Operations") + .Replace("[UPDATE], AverageLatency", "UPDATE-AverageLatency") + .Replace("[UPDATE], MinLatency", "UPDATE-MinLatency") + .Replace("[UPDATE], MaxLatency", "UPDATE-MaxLatency") + .Replace("[UPDATE], 95thPercentileLatency", "UPDATE-95thPercentileLatency") + .Replace("[UPDATE], 99thPercentileLatency", "UPDATE-99thPercentileLatency") + .Replace("[UPDATE], Return=OK", "UPDATE-Count") + .Replace("[UPDATE], Return=NOT_FOUND", "UPDATE-NotFound-Count"); + } + + private static Dictionary> GetMetricsMap(string metrics) + { + char seperator = '\n'; + string[] results = metrics.Split(seperator, StringSplitOptions.RemoveEmptyEntries); + Dictionary> metricMap = new Dictionary>(); + foreach (string item in results) + { + string[] metricData = item.Split(',', StringSplitOptions.RemoveEmptyEntries); + if (metricData.Length > 0) + { + string[] metricNameList = metricData[0].Split('(', StringSplitOptions.RemoveEmptyEntries); + if (metricNameList.Length > 0) + { + string metricName = metricNameList[0]; + double metricValue = 0.0; + string metricUnit = string.Empty; + if (metricData.Length > 1 && metricData[1] != null) + { + metricValue = Convert.ToDouble(metricData[1]); + } + + if (metricNameList.Length > 1 && metricNameList[1] != null) + { + metricUnit = metricNameList[1].Split(')', StringSplitOptions.RemoveEmptyEntries)[0]; + } + + Tuple metricTuple = new Tuple(metricUnit, metricValue); + metricMap.Add(metricName, metricTuple); + } + } + + } + + return metricMap; + } + } +} diff --git a/src/VirtualClient/VirtualClient.Main/VirtualClient.Main.csproj b/src/VirtualClient/VirtualClient.Main/VirtualClient.Main.csproj index 28335c317f..a07a2e6bc8 100644 --- a/src/VirtualClient/VirtualClient.Main/VirtualClient.Main.csproj +++ b/src/VirtualClient/VirtualClient.Main/VirtualClient.Main.csproj @@ -133,6 +133,7 @@ + diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MONGODB.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MONGODB.json new file mode 100644 index 0000000000..9cc7463560 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MONGODB.json @@ -0,0 +1,72 @@ +{ + "Description": "MongoDB Workload", + "Metadata": { + "RecommendedMinimumExecutionTime": "00:05:00", + "SupportedPlatforms": "linux-x64", + "SupportedOperatingSystems": "Ubuntu" + }, + "Parameters": { + "OperationCount": 100, + "RecordCount": 100, + "ThreadCount": "{calculate({LogicalCoreCount}/2)}" + }, + "Actions": [ + { + "Type": "MongoDBExecutor", + "Parameters": { + "Scenario": "SingleClientMongoDB", + "PackageName": "mongodb", + "YCSBPackageName": "ycsb", + "JdkPackageName": "javadevelopmentkit", + "OperationCount": "$.Parameters.OperationCount", + "RecordCount": "$.Parameters.RecordCount", + "ThreadCount": "$.Parameters.ThreadCount", + "Tags": "MongoDB, YCSB, Update heavy workload" + } + } + ], + "Dependencies": [ + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallJDKPackage", + "BlobContainer": "packages", + "BlobName": "microsoft-jdk-17.0.9.zip", + "PackageName": "javadevelopmentkit", + "Extract": true + } + }, + { + "Type": "JavaDevelopmentKitInstallation", + "Parameters": { + "Scenario": "InstallJDK", + "PackageName": "javadevelopmentkit" + } + }, + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallYCSBPackage", + "BlobContainer": "packages", + "BlobName": "ycsb-0.5.0.zip", + "PackageName": "ycsb", + "Extract": true + } + }, + { + "Type": "LinuxPackageInstallation", + "Parameters": { + "Scenario": "InstallLinuxPackages", + "Packages": "python3-pip" + } + }, + { + "Type": "WgetPackageInstallation", + "Parameters": { + "Scenario": "InstallMongoDBPackage", + "PackageName": "mongodb", + "PackageUri": "http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-5.0.15.tgz", + } + } + ] +} \ No newline at end of file